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
43 changes: 32 additions & 11 deletions tool/tctl/common/plugin/awsic.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type awsICInstallArgs struct {
region string
arn string
useSystemCredentials bool
oidcIntegration string
assumeRoleARN string
userOrigins []string
userLabels []string
Expand All @@ -67,7 +68,7 @@ type awsICInstallArgs struct {
excludeAccountIDFilters []string
}

func (a *awsICInstallArgs) validate(ctx context.Context, log *slog.Logger) error {
func (a *awsICInstallArgs) validate(ctx context.Context, auth authClient, log *slog.Logger) error {
if !awsregion.IsKnownRegion(a.region) {
return trace.BadParameter("unknown AWS region: %s", a.region)
}
Expand All @@ -76,7 +77,7 @@ func (a *awsICInstallArgs) validate(ctx context.Context, log *slog.Logger) error
return trace.BadParameter("SCIM token must not be empty")
}

if err := a.validateSystemCredentialInput(); err != nil {
if err := a.validateCredentialInput(); err != nil {
return trace.Wrap(err)
}

Expand All @@ -87,17 +88,28 @@ func (a *awsICInstallArgs) validate(ctx context.Context, log *slog.Logger) error
return nil
}

func (a *awsICInstallArgs) validateSystemCredentialInput() error {
if !a.useSystemCredentials {
return trace.BadParameter("--use-system-credentials must be set. The tctl-based AWS IAM Identity Center plugin installation only supports AWS local system credentials")
func (a *awsICInstallArgs) validateCredentialInput() error {
if a.useSystemCredentials {
if a.assumeRoleARN == "" {
return trace.BadParameter("--assume-role-arn must be set when --use-system-credentials is configured")
}

if a.oidcIntegration != "" {
return trace.BadParameter("--oidc-integration must not be set when --use-system-credentials is configured")
}

if _, err := awsutils.ParseRoleARN(a.assumeRoleARN); err != nil {
return trace.Wrap(err)
}
return nil
}

if a.assumeRoleARN == "" {
return trace.BadParameter("--assume-role-arn must be set when --use-system-credentials is configured")
if a.oidcIntegration == "" {
return trace.BadParameter("--oidc-integration must be set when --no-use-system-credentials is configured")
}

if _, err := awsutils.ParseRoleARN(a.assumeRoleARN); err != nil {
return trace.Wrap(err)
if a.assumeRoleARN != "" {
return trace.BadParameter("--assume-role-arn must not be set when --no-use-system-credentials is configured")
}

return nil
Expand All @@ -108,7 +120,6 @@ func (a *awsICInstallArgs) validateSCIMBaseURL(ctx context.Context, log *slog.Lo
if err == nil {
a.scimURL = validatedBaseUrl
return nil

}

if a.forceSCIMURL {
Expand Down Expand Up @@ -216,6 +227,8 @@ func (p *PluginsCommand) initInstallAWSIC(parent *kingpin.CmdClause) {
BoolVar(&p.install.awsIC.useSystemCredentials)
cmd.Flag("assume-role-arn", "ARN of a role that the system credential should assume.").
StringVar(&p.install.awsIC.assumeRoleARN)
cmd.Flag("oidc-integration", "Name of the Teleport OIDC integration to use when authenticating with AWS. Must be supplied when --no-use-system-credentials is set.").
StringVar(&p.install.awsIC.oidcIntegration)

cmd.Flag("user-origin", fmt.Sprintf(`Shorthand for "--user-label %s=ORIGIN"`, types.OriginLabel)).
PlaceHolder("ORIGIN").
Expand Down Expand Up @@ -246,7 +259,7 @@ func (p *PluginsCommand) initInstallAWSIC(parent *kingpin.CmdClause) {
// InstallAWSIC installs AWS Identity Center plugin.
func (p *PluginsCommand) InstallAWSIC(ctx context.Context, args pluginServices) error {
awsICArgs := p.install.awsIC
if err := awsICArgs.validate(ctx, p.config.Logger); err != nil {
if err := awsICArgs.validate(ctx, args.authClient, p.config.Logger); err != nil {
return trace.Wrap(err)
}

Expand Down Expand Up @@ -299,6 +312,14 @@ func (p *PluginsCommand) InstallAWSIC(ctx context.Context, args pluginServices)
// Set the deprecated CredentialsSource to the legacy value to allow old
// versions of Teleport to handle the record. DELETE in Teleport 19
settings.CredentialsSource = types.AWSICCredentialsSource_AWSIC_CREDENTIALS_SOURCE_SYSTEM
} else {
settings.Credentials = &types.AWSICCredentials{
Source: &types.AWSICCredentials_Oidc{
Oidc: &types.AWSICCredentialSourceOIDC{
IntegrationName: awsICArgs.oidcIntegration,
},
},
}
}

req := &pluginspb.CreatePluginRequest{
Expand Down
81 changes: 67 additions & 14 deletions tool/tctl/common/plugin/awsic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,35 +277,79 @@ func TestSCIMBaseURLValidation(t *testing.T) {
}
}

func TestUseSystemCredentialsInput(t *testing.T) {
type mockIntegrationGetter struct {
mock.Mock
}

func maybeGet[T any](args mock.Arguments, index int) T {
value := args.Get(index)
if value == nil {
var zero T
return zero
}
return value.(T)
}

func (m *mockIntegrationGetter) GetIntegration(ctx context.Context, name string) (types.Integration, error) {
result := m.Called(ctx, name)
return maybeGet[types.Integration](result, 0), result.Error(1)
}

func TestCredentialsInput(t *testing.T) {
testCases := []struct {
name string
useSystemCredential bool
assumeRoleARN string
integrationName string
integrationExists bool
expectError require.ErrorAssertionFunc
}{
{
name: "valid system credential config",
name: "use system credentials",
useSystemCredential: true,
assumeRoleARN: "arn:aws:iam::026000000023:role/assume1",
expectError: require.NoError,
},
{
name: "no useSystemCredential",
useSystemCredential: false,
name: "use system credentials without assumeRoleARN is an error",
useSystemCredential: true,
assumeRoleARN: "",
expectError: require.Error,
},
{
name: "useSystemCredential without assumeRoleARN",
name: "use system credentials with a malformed assumeRoleARN is an error",
useSystemCredential: true,
assumeRoleARN: "",
assumeRoleARN: "i am not an arn",
expectError: require.Error,
},
{
name: "useSystemCredential with invalid assumeRoleARN",
name: "use system credentials with integration is an error",
useSystemCredential: true,
assumeRoleARN: "example-credential",
assumeRoleARN: "arn:aws:iam::026000000023:role/assume1",
integrationName: "some-integration",
expectError: require.Error,
},
{
name: "use oidc credentials",
useSystemCredential: false,
assumeRoleARN: "",
integrationName: "some-integration",
integrationExists: true,
expectError: require.NoError,
},
{
name: "use oidc credentials with no integration set",
useSystemCredential: false,
assumeRoleARN: "",
integrationName: "",
expectError: require.Error,
},
{
name: "use oidc credentials and setting assumeRoleARN is an error",
useSystemCredential: false,
assumeRoleARN: "arn:aws:iam::026000000023:role/assume1",
integrationName: "some-integration",
integrationExists: true,
expectError: require.Error,
},
}
Expand All @@ -315,9 +359,22 @@ func TestUseSystemCredentialsInput(t *testing.T) {
cliArgs := awsICInstallArgs{
useSystemCredentials: tc.useSystemCredential,
assumeRoleARN: tc.assumeRoleARN,
oidcIntegration: tc.integrationName,
}

integrations := &mockIntegrationGetter{}
if !tc.useSystemCredential {
var integrationErr error
if !tc.integrationExists {
integrationErr = trace.NotFound("yes, we have no bananas")
}

integrations.
On("GetIntegration", anyContext, mock.AnythingOfType("string")).
Return(nil, integrationErr)
}

err := cliArgs.validateSystemCredentialInput()
err := cliArgs.validateCredentialInput()
tc.expectError(t, err)
})
}
Expand All @@ -330,11 +387,7 @@ type mockRoundTripper struct {
// RoundTrip implements the [http.RoundTripper] interface for the mockRoundTripper
func (m *mockRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
args := m.Called(request)
maybeResponse := args.Get(0)
if maybeResponse == nil {
return nil, args.Error(1)
}
return args.Get(0).(*http.Response), args.Error(1)
return maybeGet[*http.Response](args, 0), args.Error(1)
}

func TestRotateAWSICSCIMToken(t *testing.T) {
Expand Down
Loading