Skip to content

Commit bb5706e

Browse files
justinasjakule
andauthored
[v15] Entra ID integration (#42555)
* Entra ID reconciler: directory reconciler prerequisites (#40778) * Add Entra ID resource origin * Ignore ID and Revision from `header` in cmp * Add e_imports for MS Graph SDK * Entra ID integration: add proto definitions (#40997) * Entra ID integration boilerplate (#40998) * Add e imports for MS Graph SDK * Add ability to sign Entra ID OIDC JWTs, rework KID handling - Synthesize Key IDs for our JWT keys. For backwards compatibility, also include the same keys with an empty `kid` in JWKS. - Sign AWS OIDC tokens with a `kid=""` header claim, rather than omitting the `kid` claim altogether. See comment for details. * Add validation for Entra ID plugin * Fix typo in assertion function name * Update the OIDC JWKS test to expect the same key twice * Add Entra ID plugin type constant * go mod tidy * Fix expected JWKS size in integration test * Add basic tests for KeyID * Move Azure auth settings from Plugin to Integration * Address review comments * Add a unit test to ensure KeyID compatibility * Add license header to token_generator.go * Rename validation function per new conventions * Access Graph: sync AWS identity providers (#41368) * Add AWSSAMLProviderV1 to access graph proto * Access Graph: sync AWS SAML Providers * Parse SAML entity descriptor before sending to TAG * Add protos for AWS OIDC providers * Fetch AWS OIDC providers * Fetch signing certificates for AWS SAML providers * Deflake identity provider fetch test The concrete implementation of IAM mock uses a map, resulting in non-deterministic iteration order. Sort the results before comparing to alleviate. * Update lib/srv/discovery/fetchers/aws-sync/iam_test.go Co-authored-by: Jakub Nyckowski <jakub.nyckowski@goteleport.com> --------- Co-authored-by: Jakub Nyckowski <jakub.nyckowski@goteleport.com> * Access Graph: Entra ID application sync prerequisites (#41650) * Add access graph settings to Entra ID plugin * Move Entra ID labels to OSS * Add Entra resources and RPC to Access Graph proto * Add azure-oidc integration to web. Current code assumes that Integration is always either AwsOidc, or an external audit storage integration * Change app sso cache to a repeated field * Entra ID integration: add onboarding script (#41811) * Add Entra ID integration onboarding script * Adapt after proto update * Validate names in azure script handler, add test * Add license headers * Update Entra plugin test with SSO connector field * Fix lint * Remove leftover panics * Adjust success message * Downgrade log message level * Expect exactly 1 SP for MS Graph, improve errors * Properly extract hostname for enterprise app name * Comment on assuming the first subscription * Address review nits * Factor out sso info fetch into a function * fixup refactor * Add retry logic to app role assignment * Make godoc conventional * Entra ID integration: integration script updates and web onboarding prerequisites (#42172) * Remove integration name validation from web script Not used by the script. It is validated by the "plugins/validate" endpoint. * Add required frontend constants for Entra ID * Support Azure/Entra integrations in the list * Add IsPolicyEnabled to web config * Allow custom URL for ButtonLockedFeature * Add CTA_ENTRA_ID event type * Expose TAGInfoCache for use in e * Add LackingIgs option * Add Entra ID icon * Add Entra ID plugin to storybook * Bump e for dev build * Return underlying error in getPrivateAPIToken * Find default Azure subscription instead of the first one * Require user to re-login when provisioning Azure OIDC * Update prehog protos with Entra ID values From https://github.com/gravitational/cloud/pull/9111 * Suppress verbose warnings / information from az * Add an additional message after successful auth Lets user know that `az login` has completed and `teleport` is continuing its work. * Move EntraId constant to the bottom * Revert unintended changes to usageevents CTA is 1-to-1 with prehog, but IntegrationEnrollKind is not. * Remove integrationName validation asserts from test This parameter is no longer accepted by the endpoint * Revert "Bump e for dev build" This reverts commit fc747a0. * `go mod tidy` secondary modules --------- Co-authored-by: Jakub Nyckowski <jakub.nyckowski@goteleport.com>
1 parent 27bc869 commit bb5706e

File tree

63 files changed

+8271
-3367
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+8271
-3367
lines changed

api/client/webclient/webconfig.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ type WebConfig struct {
8282
IsTeam bool `json:"isTeam"`
8383
// IsIGSEnabled is true if [Features.IdentityGovernance] = true
8484
IsIGSEnabled bool `json:"isIgsEnabled"`
85+
// IsPolicyEnabled is true if [Features.Policy] = true
86+
IsPolicyEnabled bool `json:"isPolicyEnabled"`
8587
// featureLimits define limits for features.
8688
// Typically used with feature teasers if feature is not enabled for the
8789
// product type eg: Team product contains teasers to upgrade to Enterprise.

api/gen/proto/go/usageevents/v1/usageevents.pb.go

Lines changed: 308 additions & 301 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/proto/teleport/legacy/types/types.proto

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5773,6 +5773,8 @@ message PluginSpecV1 {
57735773
PluginServiceNowSettings serviceNow = 10;
57745774
// Settings for the Gitlab plugin.
57755775
PluginGitlabSettings gitlab = 12;
5776+
// Settings for the Entra ID plugin
5777+
PluginEntraIDSettings entra_id = 13;
57765778
// Settings for the SCIM plugin
57775779
PluginSCIMSettings scim = 14;
57785780
}
@@ -5953,6 +5955,53 @@ message PluginDiscordSettings {
59535955
map<string, DiscordChannels> role_to_recipients = 1;
59545956
}
59555957

5958+
// PluginEntraIDSettings defines settings for the Entra ID sync plugin
5959+
message PluginEntraIDSettings {
5960+
option (gogoproto.equal) = true;
5961+
5962+
// SyncSettings controls the user and access list sync settings for EntraID.
5963+
PluginEntraIDSyncSettings sync_settings = 1;
5964+
5965+
// AccessGraphSettings controls settings for syncing access graph specific data.
5966+
// When this is null, Entra ID integration with Access Graph is disabled.
5967+
PluginEntraIDAccessGraphSettings access_graph_settings = 2;
5968+
}
5969+
5970+
// Defines settings for syncing users and access lists from Entra ID.
5971+
message PluginEntraIDSyncSettings {
5972+
option (gogoproto.equal) = true;
5973+
5974+
// DefaultOwners are the default owners for all imported access lists.
5975+
repeated string default_owners = 1;
5976+
5977+
// SSOConnectorID is the name of the Teleport SSO connector created and used by the Entra ID plugin
5978+
string sso_connector_id = 2;
5979+
}
5980+
5981+
// AccessGraphSettings controls settings for syncing access graph specific data.
5982+
message PluginEntraIDAccessGraphSettings {
5983+
option (gogoproto.equal) = true;
5984+
5985+
// AppSsoSettingsCache is an array of single sign-on settings for Entra enterprise applications.
5986+
//
5987+
// This data is stored here because it is not available through traditional methods (MS Graph API).
5988+
// Instead, it is fetched once during the plugin's set up using the user's credentials to connect to Azure's private API.
5989+
repeated PluginEntraIDAppSSOSettings app_sso_settings_cache = 1;
5990+
}
5991+
5992+
// PluginEntraIDAppSSOSettings is a container for a single Entra ID enterprise application's
5993+
// cached SSO settings.
5994+
// As this data is only parsed by TAG, each value is stored as an opaque JSON blob.
5995+
message PluginEntraIDAppSSOSettings {
5996+
option (gogoproto.equal) = true;
5997+
5998+
// AppID is the `AppID` property of Entra application.
5999+
string app_id = 1;
6000+
6001+
// FederatedSSOV2 contains the cached, gzip-compressed payload from the /ApplicationSso/{servicePrincipalId}/FederatedSSOV2 endpoint.
6002+
bytes federated_sso_v2 = 2;
6003+
}
6004+
59566005
// PluginSCIMSettings defines the settings for a SCIM integration plugin
59576006
message PluginSCIMSettings {
59586007
option (gogoproto.equal) = true;
@@ -6422,6 +6471,8 @@ message IntegrationSpecV1 {
64226471
oneof SubKindSpec {
64236472
// AWSOIDC contains the specific fields to handle the AWS OIDC Integration subkind
64246473
AWSOIDCIntegrationSpecV1 AWSOIDC = 1 [(gogoproto.jsontag) = "aws_oidc,omitempty"];
6474+
// AzureOIDC contains the specific fields to handle the Azure OIDC Integration subkind
6475+
AzureOIDCIntegrationSpecV1 AzureOIDC = 2 [(gogoproto.jsontag) = "azure_oidc,omitempty"];
64256476
}
64266477
}
64276478

@@ -6440,6 +6491,17 @@ message AWSOIDCIntegrationSpecV1 {
64406491
string IssuerS3URI = 2 [(gogoproto.jsontag) = "issuer_s3_uri,omitempty"];
64416492
}
64426493

6494+
// AzureOIDCIntegrationSpecV1 contains the spec properties for the Azure OIDC SubKind Integration.
6495+
message AzureOIDCIntegrationSpecV1 {
6496+
// TenantID specifies the ID of Entra Tenant (Directory)
6497+
// that this plugin integrates with.
6498+
string TenantID = 1 [(gogoproto.jsontag) = "tenant_id,omitempty"];
6499+
6500+
// ClientID specifies the ID of Azure enterprise application (client)
6501+
// that corresponds to this plugin.
6502+
string ClientID = 2 [(gogoproto.jsontag) = "client_id,omitempty"];
6503+
}
6504+
64436505
// HeadlessAuthentication holds data for an ongoing headless authentication attempt.
64446506
message HeadlessAuthentication {
64456507
// Header is the resource header.

api/proto/teleport/usageevents/v1/usageevents.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ enum CTA {
241241
CTA_ACCESS_MONITORING = 9;
242242
CTA_EXTERNAL_AUDIT_STORAGE = 10;
243243
CTA_OKTA_USER_SYNC = 11;
244+
CTA_ENTRA_ID = 12;
244245
}
245246

246247
// UIDiscoverDeployServiceEvent is emitted after the user installs a Teleport Agent.
@@ -563,6 +564,7 @@ enum IntegrationEnrollKind {
563564
INTEGRATION_ENROLL_KIND_MACHINE_ID_JENKINS = 16;
564565
INTEGRATION_ENROLL_KIND_MACHINE_ID_ANSIBLE = 17;
565566
INTEGRATION_ENROLL_KIND_SERVICENOW = 18;
567+
INTEGRATION_ENROLL_KIND_ENTRA_ID = 19;
566568
}
567569

568570
// IntegrationEnrollMetadata contains common metadata

api/types/common/constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ const (
6565
// OriginDiscoveryKubernetes indicates that the resource was imported
6666
// from kubernetes cluster by discovery service.
6767
OriginDiscoveryKubernetes = "discovery-kubernetes"
68+
69+
// OriginEntraID indicates that the resource was imported
70+
// from the Entra ID directory.
71+
OriginEntraID = "entra-id"
6872
)
6973

7074
// OriginValues lists all possible origin values.
@@ -77,4 +81,5 @@ var OriginValues = []string{
7781
OriginOkta,
7882
OriginSCIM,
7983
OriginDiscoveryKubernetes,
84+
OriginEntraID,
8085
}

api/types/constants.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,10 @@ const (
635635
// from kubernetes cluster by discovery service.
636636
OriginDiscoveryKubernetes = common.OriginDiscoveryKubernetes
637637

638+
// OriginEntraID indicates that the resource was imported
639+
// from the Entra ID directory.
640+
OriginEntraID = common.OriginEntraID
641+
638642
// IntegrationLabel is a resource metadata label name used to identify the integration name that created the resource.
639643
IntegrationLabel = TeleportNamespace + "/integration"
640644

@@ -974,6 +978,22 @@ const (
974978

975979
// OktaRoleNameLabel is the human readable name for a role sourced from Okta.
976980
OktaRoleNameLabel = TeleportInternalLabelPrefix + "okta-role-name"
981+
982+
// EntraTenantIDLabel is the label for the Entra tenant ID.
983+
EntraTenantIDLabel = TeleportInternalLabelPrefix + "entra-tenant"
984+
985+
// EntraUniqueIDLabel is the label for the unique identifier of the object in the Entra ID directory.
986+
EntraUniqueIDLabel = TeleportInternalLabelPrefix + "entra-unique-id"
987+
988+
// EntraUPNLabel is the label for the user principal name in Entra ID.
989+
EntraUPNLabel = TeleportInternalLabelPrefix + "entra-upn"
990+
991+
// EntraDisplayNameLabel is the label for the display name of the object in the Entra ID directory.
992+
// The display name may not be unique.
993+
EntraDisplayNameLabel = TeleportInternalLabelPrefix + "entra-display-name"
994+
995+
// EntraSAMAccountNameLabel is the label for user's on-premises sAMAccountName.
996+
EntraSAMAccountNameLabel = TeleportInternalLabelPrefix + "entra-sam-account-name"
977997
)
978998

979999
const (

api/types/integration.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import (
2929
const (
3030
// IntegrationSubKindAWSOIDC is an integration with AWS that uses OpenID Connect as an Identity Provider.
3131
IntegrationSubKindAWSOIDC = "aws-oidc"
32+
33+
// IntegrationSubKindAzureOIDC is an integration with Azure that uses OpenID Connect as an Identity Provider.
34+
IntegrationSubKindAzureOIDC = "azure-oidc"
3235
)
3336

3437
// Integration specifies is a connection configuration between Teleport and a 3rd party system.
@@ -47,6 +50,9 @@ type Integration interface {
4750
// SetAWSOIDCIssuerS3URI sets the IssuerS3URI of the AWS OIDC Spec.
4851
// Eg, s3://my-bucket/my-prefix
4952
SetAWSOIDCIssuerS3URI(string)
53+
54+
// GetAzureOIDCIntegrationSpec returns the `azure-oidc` spec fields.
55+
GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpecV1
5056
}
5157

5258
var _ ResourceWithLabels = (*IntegrationV1)(nil)
@@ -72,6 +78,27 @@ func NewIntegrationAWSOIDC(md Metadata, spec *AWSOIDCIntegrationSpecV1) (*Integr
7278
return ig, nil
7379
}
7480

81+
// NewIntegrationAzureOIDC returns a new `azure-oidc` subkind Integration
82+
func NewIntegrationAzureOIDC(md Metadata, spec *AzureOIDCIntegrationSpecV1) (*IntegrationV1, error) {
83+
ig := &IntegrationV1{
84+
ResourceHeader: ResourceHeader{
85+
Metadata: md,
86+
Kind: KindIntegration,
87+
Version: V1,
88+
SubKind: IntegrationSubKindAzureOIDC,
89+
},
90+
Spec: IntegrationSpecV1{
91+
SubKindSpec: &IntegrationSpecV1_AzureOIDC{
92+
AzureOIDC: spec,
93+
},
94+
},
95+
}
96+
if err := ig.CheckAndSetDefaults(); err != nil {
97+
return nil, trace.Wrap(err)
98+
}
99+
return ig, nil
100+
}
101+
75102
// String returns the integration string representation.
76103
func (ig *IntegrationV1) String() string {
77104
return fmt.Sprintf("IntegrationV1(Name=%v, SubKind=%s, Labels=%v)",
@@ -128,14 +155,19 @@ func (s *IntegrationSpecV1) CheckAndSetDefaults() error {
128155
if err != nil {
129156
return trace.Wrap(err)
130157
}
158+
case *IntegrationSpecV1_AzureOIDC:
159+
err := integrationSubKind.Validate()
160+
if err != nil {
161+
return trace.Wrap(err)
162+
}
131163
default:
132164
return trace.BadParameter("unknown integration subkind: %T", integrationSubKind)
133165
}
134166

135167
return nil
136168
}
137169

138-
// CheckAndSetDefaults validates an agent mesh integration.
170+
// CheckAndSetDefaults validates the configuration for AWS OIDC integration subkind.
139171
func (s *IntegrationSpecV1_AWSOIDC) CheckAndSetDefaults() error {
140172
if s == nil || s.AWSOIDC == nil {
141173
return trace.BadParameter("aws_oidc is required for %q subkind", IntegrationSubKindAWSOIDC)
@@ -160,6 +192,21 @@ func (s *IntegrationSpecV1_AWSOIDC) CheckAndSetDefaults() error {
160192
return nil
161193
}
162194

195+
// Validate validates the configuration for Azure OIDC integration subkind.
196+
func (s *IntegrationSpecV1_AzureOIDC) Validate() error {
197+
if s == nil || s.AzureOIDC == nil {
198+
return trace.BadParameter("azure_oidc is required for %q subkind", IntegrationSubKindAzureOIDC)
199+
}
200+
if s.AzureOIDC.TenantID == "" {
201+
return trace.BadParameter("tenant_id must be set")
202+
}
203+
if s.AzureOIDC.ClientID == "" {
204+
return trace.BadParameter("client_id must be set")
205+
}
206+
207+
return nil
208+
}
209+
163210
// GetAWSOIDCIntegrationSpec returns the specific spec fields for `aws-oidc` subkind integrations.
164211
func (ig *IntegrationV1) GetAWSOIDCIntegrationSpec() *AWSOIDCIntegrationSpecV1 {
165212
return ig.Spec.GetAWSOIDC()
@@ -198,6 +245,11 @@ func (ig *IntegrationV1) SetAWSOIDCIssuerS3URI(issuerS3URI string) {
198245
}
199246
}
200247

248+
// GetAzureOIDCIntegrationSpec returns the specific spec fields for `azure-oidc` subkind integrations.
249+
func (ig *IntegrationV1) GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpecV1 {
250+
return ig.Spec.GetAzureOIDC()
251+
}
252+
201253
// Integrations is a list of Integration resources.
202254
type Integrations []Integration
203255

@@ -247,7 +299,8 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {
247299
d := struct {
248300
ResourceHeader `json:""`
249301
Spec struct {
250-
AWSOIDC json.RawMessage `json:"aws_oidc"`
302+
AWSOIDC json.RawMessage `json:"aws_oidc"`
303+
AzureOIDC json.RawMessage `json:"azure_oidc"`
251304
} `json:"spec"`
252305
}{}
253306

@@ -270,6 +323,17 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {
270323

271324
integration.Spec.SubKindSpec = subkindSpec
272325

326+
case IntegrationSubKindAzureOIDC:
327+
subkindSpec := &IntegrationSpecV1_AzureOIDC{
328+
AzureOIDC: &AzureOIDCIntegrationSpecV1{},
329+
}
330+
331+
if err := json.Unmarshal(d.Spec.AzureOIDC, subkindSpec.AzureOIDC); err != nil {
332+
return trace.Wrap(err)
333+
}
334+
335+
integration.Spec.SubKindSpec = subkindSpec
336+
273337
default:
274338
return trace.BadParameter("invalid subkind %q", integration.ResourceHeader.SubKind)
275339
}
@@ -290,7 +354,8 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
290354
d := struct {
291355
ResourceHeader `json:""`
292356
Spec struct {
293-
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc"`
357+
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc,omitempty"`
358+
AzureOIDC AzureOIDCIntegrationSpecV1 `json:"azure_oidc,omitempty"`
294359
} `json:"spec"`
295360
}{}
296361

@@ -303,6 +368,12 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
303368
}
304369

305370
d.Spec.AWSOIDC = *ig.GetAWSOIDCIntegrationSpec()
371+
case IntegrationSubKindAzureOIDC:
372+
if ig.GetAzureOIDCIntegrationSpec() == nil {
373+
return nil, trace.BadParameter("missing subkind data for %q subkind", ig.SubKind)
374+
}
375+
376+
d.Spec.AzureOIDC = *ig.GetAzureOIDCIntegrationSpec()
306377
default:
307378
return nil, trace.BadParameter("invalid subkind %q", ig.SubKind)
308379
}

0 commit comments

Comments
 (0)