diff --git a/lib/auth/app_access_aws_credentials.go b/lib/auth/app_access_aws_credentials.go index 661ea61860fcf..952c720b997a1 100644 --- a/lib/auth/app_access_aws_credentials.go +++ b/lib/auth/app_access_aws_credentials.go @@ -30,22 +30,22 @@ import ( "github.com/gravitational/teleport/lib/integrations/awsra" ) -var errNotIntegrationApp = errors.New("target resource is not an application with an associated integration") +var errAppWithoutAWSClientSideCredentials = errors.New("target resource is not an application that sends credentials to the client") -// generateAWSConfigCredentialProcessCredentials generates AWS Credentials for the given application. -// If the target resource is not an application with an associated integration, it returns errNotIntegrationApp error, -// which should be handled gracefully by the caller. +// generateAWSClientSideCredentials generates AWS Credentials for the given application. +// If the target resource is not an application with an associated AWS Roles Anywhere integration, +// it returns errAppWithoutAWSClientSideCredentials error, which should be handled gracefully by the caller. // // The AWS Credentials returned are in the format expected by the AWS CLI/SDK config file `credential_process` (usually located at ~/.aws/config). // Expected format documentation: https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html -func generateAWSConfigCredentialProcessCredentials( +func generateAWSClientSideCredentials( ctx context.Context, a *Server, req certRequest, notAfter time.Time, ) (string, error) { if req.appName == "" || req.awsRoleARN == "" { - return "", errNotIntegrationApp + return "", errAppWithoutAWSClientSideCredentials } appInfo, err := getAppServerByName(ctx, a, req.appName) @@ -56,7 +56,7 @@ func generateAWSConfigCredentialProcessCredentials( // Only integrations can generate credentials. integrationName := appInfo.GetIntegration() if integrationName == "" { - return "", errNotIntegrationApp + return "", errAppWithoutAWSClientSideCredentials } integration, err := a.GetIntegration(ctx, integrationName) @@ -69,6 +69,10 @@ func generateAWSConfigCredentialProcessCredentials( // Only AWS Roles Anywhere integrations can generate credentials. return generateAWSRolesAnywhereCredentials(ctx, a, req, appInfo, integration, notAfter) + case types.IntegrationSubKindAWSOIDC: + // AWS access using AWS OIDC integration will proxy requests, but will not send any credentials to the client. + return "", errAppWithoutAWSClientSideCredentials + default: return "", trace.BadParameter("application %q is using integration %q for access, which does not support AWS credential generation", req.appName, integrationName) } diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 2bf3e8437b92b..dc73036a9255c 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -3508,10 +3508,10 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. return nil, trace.Wrap(err) } - // Generate AWS credential process credentials if the user is trying to access an App with an AWS Roles Anywhere Integration. - awsCredentialProcessCredentials, err := generateAWSConfigCredentialProcessCredentials(ctx, a, req, notAfter) + // Generate AWS client side credentials if the user is trying to access an AWS App with an AWS Roles Anywhere Integration. + awsCredentialProcessCredentials, err := generateAWSClientSideCredentials(ctx, a, req, notAfter) switch { - case errors.Is(err, errNotIntegrationApp): + case errors.Is(err, errAppWithoutAWSClientSideCredentials): case err != nil: return nil, trace.Wrap(err) } diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index 241d223cbd726..0ae7c595034fc 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -870,6 +870,84 @@ func TestAWSRolesAnywhereCredentialGenerationForApps(t *testing.T) { require.JSONEq(t, `{"Version":1,"AccessKeyId":"aki","SecretAccessKey":"sak","SessionToken":"st","Expiration":"2025-06-25T12:07:02.474135Z"}`, identity.RouteToApp.AWSCredentialProcessCredentials) } +func TestAppAccessUsingAWSOIDC_doesntGenerateClientCredentials(t *testing.T) { + ctx := t.Context() + srv := newTestTLSServer(t) + + // Set up a user with the necessary roles to access the AWS App. + username := "aws-access-user" + roleARN := "arn:aws:iam::123456789012:role/MyRole" + awsAccessRole, err := authtest.CreateRole(ctx, srv.Auth(), "aws-access", types.RoleSpecV6{ + Allow: types.RoleConditions{ + AppLabels: types.Labels{ + types.Wildcard: []string{types.Wildcard}, + }, + AWSRoleARNs: []string{roleARN}, + }, + }) + require.NoError(t, err) + + awsOIDCIntegration := "aws-oidc-integration" + ig, err := types.NewIntegrationAWSOIDC(types.Metadata{Name: awsOIDCIntegration}, &types.AWSOIDCIntegrationSpecV1{ + RoleARN: "arn:aws:iam::123456789012:role/MyRole", + }) + require.NoError(t, err) + _, err = srv.Auth().Integrations.CreateIntegration(ctx, ig) + require.NoError(t, err) + + // Set up a new AWS App created from a Roles Anywhere Profile. + appName := "my-aws-access-using-awsoidc" + app, err := types.NewAppServerV3( + types.Metadata{Name: appName}, + types.AppServerSpecV3{ + HostID: uuid.NewString(), + App: &types.AppV3{ + Metadata: types.Metadata{Name: appName}, + Spec: types.AppSpecV3{ + URI: constants.AWSConsoleURL, + Integration: awsOIDCIntegration, + PublicAddr: "my-app.example.com", + }, + }, + }, + ) + require.NoError(t, err) + _, err = srv.Auth().UpsertApplicationServer(ctx, app) + require.NoError(t, err) + + user, err := authtest.CreateUser(ctx, srv.Auth(), username, awsAccessRole) + require.NoError(t, err) + + client, err := srv.NewClient(authtest.TestUser(user.GetName())) + require.NoError(t, err) + + // Create credentials for the AWS App + priv, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) + require.NoError(t, err) + pub, err := keys.MarshalPublicKey(priv.Public()) + require.NoError(t, err) + + certs, err := client.GenerateUserCerts(ctx, proto.UserCertsRequest{ + TLSPublicKey: pub, + Username: user.GetName(), + Expires: time.Now().Add(time.Hour), + RouteToApp: proto.RouteToApp{ + Name: appName, + AWSRoleARN: roleARN, + }, + }) + require.NoError(t, err) + + // Parse the Identity and check the AWS Role ARN and credentials. + tlsCert, err := tlsca.ParseCertificatePEM(certs.TLS) + require.NoError(t, err) + identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter) + require.NoError(t, err) + + require.Equal(t, roleARN, identity.AWSRoleARNs[0], "Expected AWS Role ARN to match the one requested") + require.Empty(t, identity.RouteToApp.AWSCredentialProcessCredentials) +} + func TestSSODiagnosticInfo(t *testing.T) { ctx := context.Background() srv := newTestTLSServer(t)