Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions lib/auth/app_access_aws_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
}
Expand Down
6 changes: 3 additions & 3 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
78 changes: 78 additions & 0 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading