diff --git a/go.mod b/go.mod index e3c1b7ba998dc..2a4a787e7f89a 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( connectrpc.com/connect v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.2.0 diff --git a/go.sum b/go.sum index c89d025f9523b..a1066d274d2d7 100644 --- a/go.sum +++ b/go.sum @@ -662,6 +662,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLC github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.1/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0 h1:qtRcg5Y7jNJ4jEzPq4GpWLfTspHdNe2ZK6LjwGcjgmU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0/go.mod h1:lPneRe3TwsoDRKY4O6YDLXHhEWrD+TIRa8XrV/3/fqw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.1 h1:H3g2mkmu105ON0c/Gqx3Bm+bzoIijLom8LmV9Gjn7X0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.1/go.mod h1:EAc3kjhZf9soch7yLID8PeKcE6VfKvQTllSBHYVdXd8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 h1:1u/K2BFv0MwkG6he8RYuUcbbeK22rkoZbg4lKa/msZU= diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 6d32dce7243ea..05f1efd5ae21c 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -251,6 +251,8 @@ type CommandLineFlags struct { // `teleport integration configure access-graph aws-iam` command IntegrationConfAccessGraphAWSSyncArguments IntegrationConfAccessGraphAWSSync + IntegrationConfAccessGraphAzureSyncArguments IntegrationConfAccessGraphAzureSync + // IntegrationConfAzureOIDCArguments contains the arguments of // `teleport integration configure azure-oidc` command IntegrationConfAzureOIDCArguments IntegrationConfAzureOIDC @@ -283,6 +285,19 @@ type IntegrationConfAccessGraphAWSSync struct { AutoConfirm bool } +// IntegrationConfAccessGraphAzureSync contains the arguments of +// `teleport integration configure access-graph azure` command. +type IntegrationConfAccessGraphAzureSync struct { + // ManagedIdentity is the principal performing the discovery + ManagedIdentity string + // Role is the Azure Role associated with the integration + Role string + // SubscriptionID is the Azure subscription containing resources for sync + SubscriptionID string + // AutoConfirm skips user confirmation of the operation plan if true. + AutoConfirm bool +} + // IntegrationConfAzureOIDC contains the arguments of // `teleport integration configure azure-oidc` command type IntegrationConfAzureOIDC struct { diff --git a/lib/integrations/awsoidc/access_graph_aws_sync.go b/lib/integrations/awsoidc/accessgraph_sync.go similarity index 100% rename from lib/integrations/awsoidc/access_graph_aws_sync.go rename to lib/integrations/awsoidc/accessgraph_sync.go diff --git a/lib/integrations/awsoidc/access_graph_aws_sync_test.go b/lib/integrations/awsoidc/accessgraph_sync_test.go similarity index 100% rename from lib/integrations/awsoidc/access_graph_aws_sync_test.go rename to lib/integrations/awsoidc/accessgraph_sync_test.go diff --git a/lib/integrations/azureoidc/accessgraph_sync.go b/lib/integrations/azureoidc/accessgraph_sync.go new file mode 100644 index 0000000000000..e4411710ecafc --- /dev/null +++ b/lib/integrations/azureoidc/accessgraph_sync.go @@ -0,0 +1,107 @@ +package azureoidc + +import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" + "github.com/google/uuid" + "github.com/gravitational/teleport/lib/cloud/provisioning" + "github.com/gravitational/teleport/lib/config" + "github.com/gravitational/trace" + "log/slog" + "os" +) + +func newManagedIdAction(cred *azidentity.DefaultAzureCredential, subId string, name string) (*provisioning.Action, error) { + runnerFn := func(ctx context.Context) error { + // Create the managed identity + userIdCli, err := armmsi.NewUserAssignedIdentitiesClient(subId, cred, nil) + if err != nil { + return trace.Wrap(fmt.Errorf("could not create managed identity client: %v", err)) + } + id := armmsi.Identity{} + mgdIdRes, err := userIdCli.CreateOrUpdate(ctx, "", name, id, nil) + if err != nil { + return trace.Wrap(fmt.Errorf("could not create managed identity: %v", err)) + } + slog.InfoContext(ctx, fmt.Sprintf( + "Managed identity created, Name: %s, ID: %s", *mgdIdRes.Name, *mgdIdRes.ID)) + + // Create the role + roleDefCli, err := armauthorization.NewRoleDefinitionsClient(cred, nil) + if err != nil { + return trace.Wrap(fmt.Errorf("failed to create role definitions client: %v", err)) + } + roleDefId := uuid.New().String() + customRole := "CustomRole" + // TODO(mbrock): Determine scope + scope := "" + roleDefinition := armauthorization.RoleDefinition{ + Name: &roleDefId, + Properties: &armauthorization.RoleDefinitionProperties{ + RoleName: &name, + RoleType: &customRole, + Permissions: []*armauthorization.Permission{ + // TODO(mbrock): Add permissions + }, + AssignableScopes: []*string{&scope}, // Scope must be provided + }, + } + roleRes, err := roleDefCli.CreateOrUpdate(ctx, scope, roleDefId, roleDefinition, nil) + if err != nil { + return trace.Wrap(fmt.Errorf("failed to create custom role: %v", err)) + } + + // Assign the role to the managed identity + roleAssignCli, err := armauthorization.NewRoleAssignmentsClient(subId, cred, nil) + if err != nil { + return fmt.Errorf("failed to create role assignments client: %v", err) + } + assignName := uuid.New().String() + if err != nil { + return trace.Wrap(fmt.Errorf("failed to create role assignments client: %v", err)) + } + roleAssignParams := armauthorization.RoleAssignmentCreateParameters{ + Properties: &armauthorization.RoleAssignmentProperties{ + PrincipalID: mgdIdRes.ID, + RoleDefinitionID: roleRes.ID, + }, + } + _, err = roleAssignCli.Create(ctx, scope, assignName, roleAssignParams, nil) + if err != nil { + return fmt.Errorf("failed to create role assignment: %v", err) + } + + return nil + } + cfg := provisioning.ActionConfig{ + Name: "NewSyncManagedId", + Summary: "Creates a new Azure managed ID for the discovery service to use", + RunnerFn: runnerFn, + } + return provisioning.NewAction(cfg) +} + +// ConfigureAccessGraphSyncAzure sets up the managed identity and role required for Teleport to be able to pull +// AWS resources into Teleport. +func ConfigureAccessGraphSyncAzure(ctx context.Context, params config.IntegrationConfAccessGraphAzureSync) error { + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return trace.Wrap(err) + } + managedIdAction, err := newManagedIdAction(cred, params.SubscriptionID, params.ManagedIdentity) + if err != nil { + return trace.Wrap(err) + } + opCfg := provisioning.OperationConfig{ + Name: "access-graph-azure-sync", + Actions: []provisioning.Action{ + *managedIdAction, + }, + AutoConfirm: params.AutoConfirm, + Output: os.Stdout, + } + return trace.Wrap(provisioning.Run(ctx, opCfg)) +} diff --git a/tool/teleport/common/integration_configure.go b/tool/teleport/common/integration_configure.go index bfd762d1322ec..2db255306860f 100644 --- a/tool/teleport/common/integration_configure.go +++ b/tool/teleport/common/integration_configure.go @@ -241,6 +241,12 @@ func onIntegrationConfAccessGraphAWSSync(ctx context.Context, params config.Inte return trace.Wrap(awsoidc.ConfigureAccessGraphSyncIAM(ctx, clt, confReq)) } +func onIntegrationConfAccessGraphAzureSync(ctx context.Context, params config.IntegrationConfAccessGraphAzureSync) error { + // Ensure we print output to the user. LogLevel at this point was set to Error. + utils.InitLogger(utils.LoggingForDaemon, slog.LevelInfo) + return trace.Wrap(azureoidc.ConfigureAccessGraphSyncAzure(ctx, params)) +} + func onIntegrationConfAzureOIDCCmd(ctx context.Context, params config.IntegrationConfAzureOIDC) error { // Ensure we print output to the user. LogLevel at this point was set to Error. utils.InitLogger(utils.LoggingForDaemon, slog.LevelInfo) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 3ccaa6ad1928a..98317043112a6 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -507,10 +507,16 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con integrationConfEKSCmd.Flag("confirm", "Apply changes without confirmation prompt.").BoolVar(&ccf.IntegrationConfEKSIAMArguments.AutoConfirm) integrationConfAccessGraphCmd := integrationConfigureCmd.Command("access-graph", "Manages Access Graph configuration.") - integrationConfTAGSyncCmd := integrationConfAccessGraphCmd.Command("aws-iam", "Adds required IAM permissions for syncing data into Access Graph service.") - integrationConfTAGSyncCmd.Flag("role", "The AWS Role used by the AWS OIDC Integration.").Required().StringVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.Role) - integrationConfTAGSyncCmd.Flag("aws-account-id", "The AWS account ID.").StringVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.AccountID) - integrationConfTAGSyncCmd.Flag("confirm", "Apply changes without confirmation prompt.").BoolVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.AutoConfirm) + integrationConfAccessGraphAWSSyncCmd := integrationConfAccessGraphCmd.Command("aws-iam", "Adds required IAM permissions for syncing data into Access Graph service.") + integrationConfAccessGraphAWSSyncCmd.Flag("role", "The AWS Role used by the AWS OIDC Integration.").Required().StringVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.Role) + integrationConfAccessGraphAWSSyncCmd.Flag("aws-account-id", "The AWS account ID.").StringVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.AccountID) + integrationConfAccessGraphAWSSyncCmd.Flag("confirm", "Apply changes without confirmation prompt.").BoolVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.AutoConfirm) + + integrationConfAccessGraphAzureSyncCmd := integrationConfAccessGraphCmd.Command("azure", "Creates/updates permissions for syncing data into Access Graph service.") + integrationConfAccessGraphAzureSyncCmd.Flag("managed-identity", "The managed identity runs the discovery service.").Required() + integrationConfAccessGraphAzureSyncCmd.Flag("role", "The role attached to the managed identity with the discovery permissions.").Required() + integrationConfAccessGraphAzureSyncCmd.Flag("subscription-id", "The subscription ID in which to discovery resources.") + integrationConfAccessGraphAzureSyncCmd.Flag("confirm", "Apply changes without confirmation prompt.") integrationConfAWSOIDCIdPCmd := integrationConfigureCmd.Command("awsoidc-idp", "Creates an IAM IdP (OIDC) in your AWS account to allow the AWS OIDC Integration to access AWS APIs.") integrationConfAWSOIDCIdPCmd.Flag("cluster", "Teleport Cluster name.").Required().StringVar(&ccf. @@ -719,8 +725,10 @@ Examples: err = onIntegrationConfListDatabasesIAM(ctx, ccf.IntegrationConfListDatabasesIAMArguments) case integrationConfExternalAuditCmd.FullCommand(): err = onIntegrationConfExternalAuditCmd(ctx, ccf.IntegrationConfExternalAuditStorageArguments) - case integrationConfTAGSyncCmd.FullCommand(): + case integrationConfAccessGraphAWSSyncCmd.FullCommand(): err = onIntegrationConfAccessGraphAWSSync(ctx, ccf.IntegrationConfAccessGraphAWSSyncArguments) + case integrationConfAccessGraphAzureSyncCmd.FullCommand(): + err = onIntegrationConfAccessGraphAzureSync(ctx, ccf.IntegrationConfAccessGraphAzureSyncArguments) case integrationConfAzureOIDCCmd.FullCommand(): err = onIntegrationConfAzureOIDCCmd(ctx, ccf.IntegrationConfAzureOIDCArguments) case integrationSAMLIdPGCPWorkforce.FullCommand():