Skip to content

Commit

Permalink
Set entitlements when unsupported by auth (#45226)
Browse files Browse the repository at this point in the history
  • Loading branch information
michellescripts authored Aug 7, 2024
1 parent 6904d9e commit 87970ea
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 1 deletion.
71 changes: 70 additions & 1 deletion lib/service/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ import (
"github.com/gravitational/teleport/api/client/proto"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/retryutils"
"github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/authclient"
Expand Down Expand Up @@ -1135,7 +1137,10 @@ func (process *TeleportProcess) getConnector(clientIdentity, serverIdentity *sta
}

// Set cluster features and return successfully with a working connector.
process.setClusterFeatures(pingResponse.GetServerFeatures())
// TODO(michellescripts) remove clone & compatibility check in v18
cloned := apiutils.CloneProtoMsg(pingResponse.GetServerFeatures())
supportEntitlementsCompatibility(cloned)
process.setClusterFeatures(cloned)
process.setAuthSubjectiveAddr(pingResponse.RemoteAddr)
process.logger.InfoContext(process.ExitContext(), "features loaded from auth server", "identity", clientIdentity.ID.Role, "features", pingResponse.GetServerFeatures())

Expand All @@ -1146,6 +1151,70 @@ func (process *TeleportProcess) getConnector(clientIdentity, serverIdentity *sta
}, nil
}

// supportEntitlementsCompatibility ensures entitlements are backwards compatible
// If Entitlements are present, there are no changes
// If Entitlements are not present, sets the entitlements fields to legacy field values
// TODO(michellescripts) remove in v18
func supportEntitlementsCompatibility(features *proto.Features) {
if len(features.Entitlements) > 0 {
return
}

features.Entitlements = getBaseEntitlements(features.GetEntitlements())

// Entitlements: All records are {enabled: false}; update to equal legacy feature value
features.Entitlements[string(entitlements.ExternalAuditStorage)] = &proto.EntitlementInfo{Enabled: features.GetExternalAuditStorage()}
features.Entitlements[string(entitlements.FeatureHiding)] = &proto.EntitlementInfo{Enabled: features.GetFeatureHiding()}
features.Entitlements[string(entitlements.Identity)] = &proto.EntitlementInfo{Enabled: features.GetIdentityGovernance()}
features.Entitlements[string(entitlements.JoinActiveSessions)] = &proto.EntitlementInfo{Enabled: features.GetJoinActiveSessions()}
features.Entitlements[string(entitlements.MobileDeviceManagement)] = &proto.EntitlementInfo{Enabled: features.GetMobileDeviceManagement()}
features.Entitlements[string(entitlements.OIDC)] = &proto.EntitlementInfo{Enabled: features.GetOIDC()}
features.Entitlements[string(entitlements.Policy)] = &proto.EntitlementInfo{Enabled: features.GetPolicy().GetEnabled()}
features.Entitlements[string(entitlements.SAML)] = &proto.EntitlementInfo{Enabled: features.GetSAML()}
features.Entitlements[string(entitlements.K8s)] = &proto.EntitlementInfo{Enabled: features.GetKubernetes()}
features.Entitlements[string(entitlements.App)] = &proto.EntitlementInfo{Enabled: features.GetApp()}
features.Entitlements[string(entitlements.DB)] = &proto.EntitlementInfo{Enabled: features.GetDB()}
features.Entitlements[string(entitlements.Desktop)] = &proto.EntitlementInfo{Enabled: features.GetDesktop()}
features.Entitlements[string(entitlements.HSM)] = &proto.EntitlementInfo{Enabled: features.GetHSM()}

// set default Identity fields to legacy feature value
features.Entitlements[string(entitlements.AccessLists)] = &proto.EntitlementInfo{Enabled: true, Limit: features.GetAccessList().GetCreateLimit()}
features.Entitlements[string(entitlements.AccessMonitoring)] = &proto.EntitlementInfo{Enabled: features.GetAccessMonitoring().GetEnabled(), Limit: features.GetAccessMonitoring().GetMaxReportRangeLimit()}
features.Entitlements[string(entitlements.AccessRequests)] = &proto.EntitlementInfo{Enabled: features.GetAccessRequests().MonthlyRequestLimit > 0, Limit: features.GetAccessRequests().GetMonthlyRequestLimit()}
features.Entitlements[string(entitlements.DeviceTrust)] = &proto.EntitlementInfo{Enabled: features.GetDeviceTrust().GetEnabled(), Limit: features.GetDeviceTrust().GetDevicesUsageLimit()}
// override Identity Package features if Identity is enabled: set true and clear limit
if features.GetIdentityGovernance() {
features.Entitlements[string(entitlements.AccessLists)] = &proto.EntitlementInfo{Enabled: true}
features.Entitlements[string(entitlements.AccessMonitoring)] = &proto.EntitlementInfo{Enabled: true}
features.Entitlements[string(entitlements.AccessRequests)] = &proto.EntitlementInfo{Enabled: true}
features.Entitlements[string(entitlements.DeviceTrust)] = &proto.EntitlementInfo{Enabled: true}
features.Entitlements[string(entitlements.OktaSCIM)] = &proto.EntitlementInfo{Enabled: true}
features.Entitlements[string(entitlements.OktaUserSync)] = &proto.EntitlementInfo{Enabled: true}
features.Entitlements[string(entitlements.SessionLocks)] = &proto.EntitlementInfo{Enabled: true}
}
}

// getBaseEntitlements takes a cloud entitlement set and returns a modules Entitlement set
func getBaseEntitlements(protoEntitlements map[string]*proto.EntitlementInfo) map[string]*proto.EntitlementInfo {
all := entitlements.AllEntitlements
result := make(map[string]*proto.EntitlementInfo, len(all))

for _, e := range all {
al, ok := protoEntitlements[string(e)]
if !ok {
result[string(e)] = &proto.EntitlementInfo{}
continue
}

result[string(e)] = &proto.EntitlementInfo{
Enabled: al.Enabled,
Limit: al.Limit,
}
}

return result
}

// newClient attempts to connect to either the proxy server or auth server
// For config v3 and onwards, it will only connect to either the proxy (via tunnel) or the auth server (direct),
// depending on what was specified in the config.
Expand Down
283 changes: 283 additions & 0 deletions lib/service/connect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package service

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/client/proto"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/entitlements"
)

func Test_supportEntitlementsCompatibility(t *testing.T) {
tests := []struct {
name string
features *proto.Features
expected map[string]*proto.EntitlementInfo
}{
{
name: "entitlements present; keeps entitlement values",
features: &proto.Features{
DeviceTrust: nil,
AccessRequests: nil,
AccessList: nil,
AccessMonitoring: nil,
Policy: nil,
CustomTheme: "",
ProductType: 0,
SupportType: 0,
Kubernetes: false,
App: false,
DB: false,
OIDC: false,
SAML: false,
AccessControls: false,
AdvancedAccessWorkflows: false,
Cloud: false,
HSM: false,
Desktop: false,
RecoveryCodes: false,
Plugins: false,
AutomaticUpgrades: false,
IsUsageBased: false,
Assist: false,
FeatureHiding: false,
IdentityGovernance: false,
AccessGraph: false,
Questionnaire: false,
IsStripeManaged: false,
ExternalAuditStorage: false,
JoinActiveSessions: false,
MobileDeviceManagement: false,
AccessMonitoringConfigured: false,
Entitlements: map[string]*proto.EntitlementInfo{
string(entitlements.AccessLists): {Enabled: true, Limit: 111},
string(entitlements.AccessMonitoring): {Enabled: true, Limit: 2113},
string(entitlements.AccessRequests): {Enabled: true, Limit: 39},
string(entitlements.App): {Enabled: false},
string(entitlements.CloudAuditLogRetention): {Enabled: true},
string(entitlements.DB): {Enabled: true},
string(entitlements.Desktop): {Enabled: true},
string(entitlements.DeviceTrust): {Enabled: true, Limit: 103},
string(entitlements.ExternalAuditStorage): {Enabled: true},
string(entitlements.FeatureHiding): {Enabled: true},
string(entitlements.HSM): {Enabled: true},
string(entitlements.Identity): {Enabled: true},
string(entitlements.JoinActiveSessions): {Enabled: true},
string(entitlements.K8s): {Enabled: true},
string(entitlements.MobileDeviceManagement): {Enabled: true},
string(entitlements.OIDC): {Enabled: true},
string(entitlements.OktaSCIM): {Enabled: true},
string(entitlements.OktaUserSync): {Enabled: true},
string(entitlements.Policy): {Enabled: true},
string(entitlements.SAML): {Enabled: true},
string(entitlements.SessionLocks): {Enabled: true},
string(entitlements.UpsellAlert): {Enabled: true},
string(entitlements.UsageReporting): {Enabled: true},
},
},
expected: map[string]*proto.EntitlementInfo{
string(entitlements.AccessLists): {Enabled: true, Limit: 111},
string(entitlements.AccessMonitoring): {Enabled: true, Limit: 2113},
string(entitlements.AccessRequests): {Enabled: true, Limit: 39},
string(entitlements.App): {Enabled: false},
string(entitlements.CloudAuditLogRetention): {Enabled: true},
string(entitlements.DB): {Enabled: true},
string(entitlements.Desktop): {Enabled: true},
string(entitlements.DeviceTrust): {Enabled: true, Limit: 103},
string(entitlements.ExternalAuditStorage): {Enabled: true},
string(entitlements.FeatureHiding): {Enabled: true},
string(entitlements.HSM): {Enabled: true},
string(entitlements.Identity): {Enabled: true},
string(entitlements.JoinActiveSessions): {Enabled: true},
string(entitlements.K8s): {Enabled: true},
string(entitlements.MobileDeviceManagement): {Enabled: true},
string(entitlements.OIDC): {Enabled: true},
string(entitlements.OktaSCIM): {Enabled: true},
string(entitlements.OktaUserSync): {Enabled: true},
string(entitlements.Policy): {Enabled: true},
string(entitlements.SAML): {Enabled: true},
string(entitlements.SessionLocks): {Enabled: true},
string(entitlements.UpsellAlert): {Enabled: true},
string(entitlements.UsageReporting): {Enabled: true},
},
},
{
name: "entitlements not present; identity on - sets legacy fields & drops limits",
features: &proto.Features{
DeviceTrust: &proto.DeviceTrustFeature{
Enabled: true,
DevicesUsageLimit: 33,
},
AccessRequests: &proto.AccessRequestsFeature{
MonthlyRequestLimit: 22,
},
AccessList: &proto.AccessListFeature{
CreateLimit: 44,
},
AccessMonitoring: &proto.AccessMonitoringFeature{
Enabled: true,
MaxReportRangeLimit: 55,
},
Policy: &proto.PolicyFeature{
Enabled: true,
},
CustomTheme: "",
ProductType: 0,
SupportType: 0,
Kubernetes: true,
App: true,
DB: true,
OIDC: true,
SAML: true,
AccessControls: true,
AdvancedAccessWorkflows: true,
Cloud: true,
HSM: true,
Desktop: true,
RecoveryCodes: true,
Plugins: true,
AutomaticUpgrades: true,
IsUsageBased: true,
Assist: true,
FeatureHiding: true,
IdentityGovernance: true,
AccessGraph: true,
Questionnaire: true,
IsStripeManaged: true,
ExternalAuditStorage: true,
JoinActiveSessions: true,
MobileDeviceManagement: true,
AccessMonitoringConfigured: true,
},
expected: map[string]*proto.EntitlementInfo{
string(entitlements.AccessLists): {Enabled: true},
string(entitlements.AccessMonitoring): {Enabled: true},
string(entitlements.AccessRequests): {Enabled: true},
string(entitlements.App): {Enabled: true},
string(entitlements.DB): {Enabled: true},
string(entitlements.Desktop): {Enabled: true},
string(entitlements.DeviceTrust): {Enabled: true},
string(entitlements.ExternalAuditStorage): {Enabled: true},
string(entitlements.FeatureHiding): {Enabled: true},
string(entitlements.HSM): {Enabled: true},
string(entitlements.Identity): {Enabled: true},
string(entitlements.JoinActiveSessions): {Enabled: true},
string(entitlements.K8s): {Enabled: true},
string(entitlements.MobileDeviceManagement): {Enabled: true},
string(entitlements.OIDC): {Enabled: true},
string(entitlements.OktaSCIM): {Enabled: true},
string(entitlements.OktaUserSync): {Enabled: true},
string(entitlements.Policy): {Enabled: true},
string(entitlements.SAML): {Enabled: true},
string(entitlements.SessionLocks): {Enabled: true},
// defaults, no legacy equivalent
string(entitlements.UsageReporting): {Enabled: false},
string(entitlements.UpsellAlert): {Enabled: false},
string(entitlements.CloudAuditLogRetention): {Enabled: false},
},
},
{
name: "entitlements not present; identity off - sets legacy fields",
features: &proto.Features{
DeviceTrust: &proto.DeviceTrustFeature{
Enabled: true,
DevicesUsageLimit: 33,
},
AccessRequests: &proto.AccessRequestsFeature{
MonthlyRequestLimit: 22,
},
AccessList: &proto.AccessListFeature{
CreateLimit: 44,
},
AccessMonitoring: &proto.AccessMonitoringFeature{
Enabled: true,
MaxReportRangeLimit: 55,
},
Policy: &proto.PolicyFeature{
Enabled: true,
},
CustomTheme: "",
ProductType: 0,
SupportType: 0,
Kubernetes: true,
App: true,
DB: true,
OIDC: true,
SAML: true,
AccessControls: true,
AdvancedAccessWorkflows: true,
Cloud: true,
HSM: true,
Desktop: true,
RecoveryCodes: true,
Plugins: true,
AutomaticUpgrades: true,
IsUsageBased: true,
Assist: true,
FeatureHiding: true,
IdentityGovernance: false,
AccessGraph: true,
Questionnaire: true,
IsStripeManaged: true,
ExternalAuditStorage: true,
JoinActiveSessions: true,
MobileDeviceManagement: true,
AccessMonitoringConfigured: true,
},
expected: map[string]*proto.EntitlementInfo{
string(entitlements.AccessLists): {Enabled: true, Limit: 44},
string(entitlements.AccessMonitoring): {Enabled: true, Limit: 55},
string(entitlements.AccessRequests): {Enabled: true, Limit: 22},
string(entitlements.DeviceTrust): {Enabled: true, Limit: 33},
string(entitlements.App): {Enabled: true},
string(entitlements.DB): {Enabled: true},
string(entitlements.Desktop): {Enabled: true},
string(entitlements.ExternalAuditStorage): {Enabled: true},
string(entitlements.FeatureHiding): {Enabled: true},
string(entitlements.HSM): {Enabled: true},
string(entitlements.JoinActiveSessions): {Enabled: true},
string(entitlements.K8s): {Enabled: true},
string(entitlements.MobileDeviceManagement): {Enabled: true},
string(entitlements.OIDC): {Enabled: true},
string(entitlements.Policy): {Enabled: true},
string(entitlements.SAML): {Enabled: true},

// defaults, no legacy equivalent
string(entitlements.UsageReporting): {Enabled: false},
string(entitlements.UpsellAlert): {Enabled: false},
string(entitlements.CloudAuditLogRetention): {Enabled: false},
// Identity off, fields false
string(entitlements.Identity): {Enabled: false},
string(entitlements.SessionLocks): {Enabled: false},
string(entitlements.OktaSCIM): {Enabled: false},
string(entitlements.OktaUserSync): {Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cloned := apiutils.CloneProtoMsg(tt.features)

supportEntitlementsCompatibility(cloned)
require.Equal(t, tt.expected, cloned.Entitlements)
})
}
}

0 comments on commit 87970ea

Please sign in to comment.