diff --git a/pkg/recipes/terraform/config/providers/aws.go b/pkg/recipes/terraform/config/providers/aws.go index 25ca1fc771..98fe67bcf0 100644 --- a/pkg/recipes/terraform/config/providers/aws.go +++ b/pkg/recipes/terraform/config/providers/aws.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" + "github.com/google/uuid" ucp_datamodel "github.com/radius-project/radius/pkg/ucp/datamodel" "github.com/radius-project/radius/pkg/azure/tokencredentials" @@ -43,6 +44,17 @@ const ( awsRegionParam = "region" awsAccessKeyParam = "access_key" awsSecretKeyParam = "secret_key" + + // configs for AWS IRSA + // Ref: https://registry.terraform.io/providers/hashicorp/aws/latest/docs#assuming-an-iam-role-using-a-web-identity + awsIRSAProvider = "assume_role_with_web_identity" + awsRoleARN = "role_arn" + sessionName = "session_name" + tokenFile = "web_identity_token_file" + // The path used in Amazon Elastic Kubernetes Service (EKS) to store the service account token for a Kubernetes pod. + // Ref: https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html + tokenFilePath = "/var/run/secrets/eks.amazonaws.com/serviceaccount/token" + sessionPrefix = "radius-terraform-" ) var _ Provider = (*awsProvider)(nil) @@ -127,7 +139,10 @@ func fetchAWSCredentials(ctx context.Context, awsCredentialsProvider credentials return nil, nil } case ucp_datamodel.AWSIRSACredentialKind: - return nil, errors.New("AWS IRSA Credential is not supported yet.") + if credentials.IRSACredential == nil || credentials.IRSACredential.RoleARN == "" { + logger.Info("AWS IRSA credentials are not registered, skipping credentials configuration.") + return nil, nil + } } return credentials, nil @@ -139,10 +154,24 @@ func (p *awsProvider) generateProviderConfigMap(credentials *credentials.AWSCred config[awsRegionParam] = region } - if credentials != nil && credentials.AccessKeyCredential != nil && - credentials.AccessKeyCredential.AccessKeyID != "" && credentials.AccessKeyCredential.SecretAccessKey != "" { - config[awsAccessKeyParam] = credentials.AccessKeyCredential.AccessKeyID - config[awsSecretKeyParam] = credentials.AccessKeyCredential.SecretAccessKey + if credentials != nil { + switch credentials.Kind { + case ucp_datamodel.AWSAccessKeyCredentialKind: + if credentials.AccessKeyCredential != nil && + credentials.AccessKeyCredential.AccessKeyID != "" && credentials.AccessKeyCredential.SecretAccessKey != "" { + config[awsAccessKeyParam] = credentials.AccessKeyCredential.AccessKeyID + config[awsSecretKeyParam] = credentials.AccessKeyCredential.SecretAccessKey + } + + case ucp_datamodel.AWSIRSACredentialKind: + if credentials.IRSACredential != nil && credentials.IRSACredential.RoleARN != "" { + config[awsIRSAProvider] = map[string]any{ + awsRoleARN: credentials.IRSACredential.RoleARN, + sessionName: sessionPrefix + uuid.New().String(), + tokenFile: tokenFilePath, + } + } + } } return config diff --git a/pkg/recipes/terraform/config/providers/aws_test.go b/pkg/recipes/terraform/config/providers/aws_test.go index 63dcbb8911..79ecfd44c6 100644 --- a/pkg/recipes/terraform/config/providers/aws_test.go +++ b/pkg/recipes/terraform/config/providers/aws_test.go @@ -33,27 +33,44 @@ import ( ) var ( - testRegion = "test-region" - testAWSCredentials = ucp_credentials.AWSCredential{ + testRegion = "test-region" + testAWSAccessKeyCredentials = ucp_credentials.AWSCredential{ Kind: ucp_datamodel.AWSAccessKeyCredentialKind, AccessKeyCredential: &ucp_datamodel.AWSAccessKeyCredentialProperties{ AccessKeyID: "testAccessKey", SecretAccessKey: "testSecretKey", }, } + testAWSIRSACredentials = ucp_credentials.AWSCredential{ + Kind: ucp_datamodel.AWSIRSACredentialKind, + IRSACredential: &ucp_datamodel.AWSIRSACredentialProperties{ + RoleARN: "testRoleARN", + }, + } ) type mockAWSCredentialsProvider struct { testCredential *ucp_credentials.AWSCredential } -func newMockAWSCredentialsProvider() *mockAWSCredentialsProvider { +func newMockAWSAccessKeyCredentialsProvider() *mockAWSCredentialsProvider { return &mockAWSCredentialsProvider{ testCredential: &ucp_credentials.AWSCredential{ Kind: ucp_datamodel.AWSAccessKeyCredentialKind, AccessKeyCredential: &ucp_datamodel.AWSAccessKeyCredentialProperties{ - AccessKeyID: testAWSCredentials.AccessKeyCredential.AccessKeyID, - SecretAccessKey: testAWSCredentials.AccessKeyCredential.SecretAccessKey, + AccessKeyID: testAWSAccessKeyCredentials.AccessKeyCredential.AccessKeyID, + SecretAccessKey: testAWSAccessKeyCredentials.AccessKeyCredential.SecretAccessKey, + }, + }, + } +} + +func newMockAWSIRSACredentialsProvider() *mockAWSCredentialsProvider { + return &mockAWSCredentialsProvider{ + testCredential: &ucp_credentials.AWSCredential{ + Kind: ucp_datamodel.AWSIRSACredentialKind, + IRSACredential: &ucp_datamodel.AWSIRSACredentialProperties{ + RoleARN: testAWSIRSACredentials.IRSACredential.RoleARN, }, }, } @@ -66,12 +83,20 @@ func (p *mockAWSCredentialsProvider) Fetch(ctx context.Context, planeName, name return nil, &secret.ErrNotFound{} } - if p.testCredential.AccessKeyCredential.AccessKeyID == "" && p.testCredential.AccessKeyCredential.SecretAccessKey == "" { - return p.testCredential, nil - } + switch p.testCredential.Kind { + case ucp_datamodel.AWSAccessKeyCredentialKind: + if p.testCredential.AccessKeyCredential.AccessKeyID == "" && p.testCredential.AccessKeyCredential.SecretAccessKey == "" { + return p.testCredential, nil + } - if p.testCredential.AccessKeyCredential.AccessKeyID == "" { - return nil, errors.New("failed to fetch credential") + if p.testCredential.AccessKeyCredential.AccessKeyID == "" { + return nil, errors.New("failed to fetch credential") + } + + case ucp_datamodel.AWSIRSACredentialKind: + if p.testCredential.IRSACredential.RoleARN == "" { + return nil, errors.New("failed to fetch credential") + } } return p.testCredential, nil @@ -185,7 +210,7 @@ func TestAwsProvider_getCredentialsProvider(t *testing.T) { require.NoError(t, err) } -func TestAWSProvider_FetchCredentials(t *testing.T) { +func TestAWSProvider_FetchAcessKeyCredentials(t *testing.T) { tests := []struct { desc string credentialsProvider *mockAWSCredentialsProvider @@ -193,9 +218,9 @@ func TestAWSProvider_FetchCredentials(t *testing.T) { expectedErr bool }{ { - desc: "valid credentials", - credentialsProvider: newMockAWSCredentialsProvider(), - expectedCreds: &testAWSCredentials, + desc: "valid accesskey credentials", + credentialsProvider: newMockAWSAccessKeyCredentialsProvider(), + expectedCreds: &testAWSAccessKeyCredentials, expectedErr: false, }, { @@ -207,13 +232,13 @@ func TestAWSProvider_FetchCredentials(t *testing.T) { expectedErr: false, }, { - desc: "empty values - no error", + desc: "empty values aws access key - no error", credentialsProvider: &mockAWSCredentialsProvider{ &ucp_credentials.AWSCredential{ Kind: ucp_datamodel.AWSAccessKeyCredentialKind, AccessKeyCredential: &ucp_datamodel.AWSAccessKeyCredentialProperties{ - AccessKeyID: testAWSCredentials.AccessKeyCredential.AccessKeyID, - SecretAccessKey: testAWSCredentials.AccessKeyCredential.SecretAccessKey, + AccessKeyID: testAWSAccessKeyCredentials.AccessKeyCredential.AccessKeyID, + SecretAccessKey: testAWSAccessKeyCredentials.AccessKeyCredential.SecretAccessKey, }, }, }, @@ -221,13 +246,46 @@ func TestAWSProvider_FetchCredentials(t *testing.T) { expectedErr: false, }, { - desc: "fetch credential error", + desc: "fetch accesskey credential error", credentialsProvider: &mockAWSCredentialsProvider{ &ucp_credentials.AWSCredential{ Kind: ucp_datamodel.AWSAccessKeyCredentialKind, AccessKeyCredential: &ucp_datamodel.AWSAccessKeyCredentialProperties{ AccessKeyID: "", - SecretAccessKey: testAWSCredentials.AccessKeyCredential.SecretAccessKey, + SecretAccessKey: testAWSAccessKeyCredentials.AccessKeyCredential.SecretAccessKey, + }, + }, + }, + expectedCreds: nil, + expectedErr: true, + }, + + { + desc: "valid IRSA credentials", + credentialsProvider: newMockAWSIRSACredentialsProvider(), + expectedCreds: &testAWSIRSACredentials, + expectedErr: false, + }, + { + desc: "empty values aws IRSA - no error", + credentialsProvider: &mockAWSCredentialsProvider{ + &ucp_credentials.AWSCredential{ + Kind: ucp_datamodel.AWSIRSACredentialKind, + IRSACredential: &ucp_datamodel.AWSIRSACredentialProperties{ + RoleARN: testAWSIRSACredentials.IRSACredential.RoleARN, + }, + }, + }, + expectedCreds: nil, + expectedErr: false, + }, + { + desc: "fetch IRSA credential error", + credentialsProvider: &mockAWSCredentialsProvider{ + &ucp_credentials.AWSCredential{ + Kind: ucp_datamodel.AWSIRSACredentialKind, + IRSACredential: &ucp_datamodel.AWSIRSACredentialProperties{ + RoleARN: "", }, }, }, @@ -260,21 +318,35 @@ func TestAWSProvider_generateProviderConfigMap(t *testing.T) { expectedConfig map[string]any }{ { - desc: "valid config", + desc: "valid accesskey credential config", region: testRegion, - credentials: testAWSCredentials, + credentials: testAWSAccessKeyCredentials, expectedConfig: map[string]any{ awsRegionParam: testRegion, - awsAccessKeyParam: testAWSCredentials.AccessKeyCredential.AccessKeyID, - awsSecretKeyParam: testAWSCredentials.AccessKeyCredential.SecretAccessKey, + awsAccessKeyParam: testAWSAccessKeyCredentials.AccessKeyCredential.AccessKeyID, + awsSecretKeyParam: testAWSAccessKeyCredentials.AccessKeyCredential.SecretAccessKey, + }, + }, + + { + desc: "valid IRSA credential config", + region: testRegion, + credentials: testAWSIRSACredentials, + expectedConfig: map[string]any{ + awsRegionParam: testRegion, + awsIRSAProvider: map[string]any{ + awsRoleARN: testAWSIRSACredentials.IRSACredential.RoleARN, + sessionName: "radius-terraform-" + "test-uuid", + tokenFile: tokenFilePath, + }, }, }, { desc: "missing region", - credentials: testAWSCredentials, + credentials: testAWSAccessKeyCredentials, expectedConfig: map[string]any{ - awsAccessKeyParam: testAWSCredentials.AccessKeyCredential.AccessKeyID, - awsSecretKeyParam: testAWSCredentials.AccessKeyCredential.SecretAccessKey, + awsAccessKeyParam: testAWSAccessKeyCredentials.AccessKeyCredential.AccessKeyID, + awsSecretKeyParam: testAWSAccessKeyCredentials.AccessKeyCredential.SecretAccessKey, }, }, { @@ -285,12 +357,22 @@ func TestAWSProvider_generateProviderConfigMap(t *testing.T) { }, }, { - desc: "invalid credentials", + desc: "invalid accesskey credentials", credentials: ucp_credentials.AWSCredential{ Kind: ucp_datamodel.AWSAccessKeyCredentialKind, AccessKeyCredential: &ucp_datamodel.AWSAccessKeyCredentialProperties{ AccessKeyID: "", - SecretAccessKey: testAWSCredentials.AccessKeyCredential.SecretAccessKey, + SecretAccessKey: testAWSAccessKeyCredentials.AccessKeyCredential.SecretAccessKey, + }, + }, + expectedConfig: map[string]any{}, + }, + { + desc: "invalid IRSA credentials", + credentials: ucp_credentials.AWSCredential{ + Kind: ucp_datamodel.AWSIRSACredentialKind, + IRSACredential: &ucp_datamodel.AWSIRSACredentialProperties{ + RoleARN: "", }, }, expectedConfig: map[string]any{}, @@ -305,6 +387,14 @@ func TestAWSProvider_generateProviderConfigMap(t *testing.T) { require.Equal(t, tt.expectedConfig[awsRegionParam], config[awsRegionParam]) require.Equal(t, tt.expectedConfig[awsAccessKeyParam], config[awsAccessKeyParam]) require.Equal(t, tt.expectedConfig[awsSecretKeyParam], config[awsSecretKeyParam]) + + if tt.expectedConfig[awsIRSAProvider] != nil { + expectedAWSIRSAProvider := tt.expectedConfig[awsIRSAProvider].(map[string]any) + AWSIRSAProvider := config[awsIRSAProvider].(map[string]any) + require.Equal(t, expectedAWSIRSAProvider[awsRoleARN], AWSIRSAProvider[awsRoleARN]) + require.Contains(t, expectedAWSIRSAProvider[sessionName], "radius-terraform-") + require.Equal(t, expectedAWSIRSAProvider[tokenFile], AWSIRSAProvider[tokenFile]) + } }) } } diff --git a/pkg/ucp/api/v20231001preview/testdata/credentialresourcedatamodel-aws-irsa.json b/pkg/ucp/api/v20231001preview/testdata/credentialresourcedatamodel-aws-irsa.json index efb56f8e8d..4df034df1a 100644 --- a/pkg/ucp/api/v20231001preview/testdata/credentialresourcedatamodel-aws-irsa.json +++ b/pkg/ucp/api/v20231001preview/testdata/credentialresourcedatamodel-aws-irsa.json @@ -20,7 +20,7 @@ "awsCredential": { "kind": "IRSA", "irsa": { - "roleARN": "arn:aws:iam::000000000000:role/role-name" + "roleARN": "arn:aws:iam::000000000000:role/role-name" } }, "storage": { diff --git a/pkg/ucp/aws/ucpcredentialprovider.go b/pkg/ucp/aws/ucpcredentialprovider.go index 447e4e57ae..97060fd243 100644 --- a/pkg/ucp/aws/ucpcredentialprovider.go +++ b/pkg/ucp/aws/ucpcredentialprovider.go @@ -23,6 +23,10 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/google/uuid" sdk_cred "github.com/radius-project/radius/pkg/ucp/credentials" "github.com/radius-project/radius/pkg/ucp/ucplog" @@ -33,6 +37,19 @@ var _ aws.CredentialsProvider = (*UCPCredentialProvider)(nil) const ( // DefaultExpireDuration is the default access key expiry duration. DefaultExpireDuration = time.Minute * time.Duration(15) + + // CredentialKind is IRSA + CredentialKindIRSA = "IRSA" + // CredentialKind is AccessKey + CredentialKindAccessKey = "AccessKey" + // Token file path for IRSA + tokenFilePath = "/var/run/secrets/eks.amazonaws.com/serviceaccount/token" + // AWS STS Signing region + awsSTSGlobalEndPointSigningRegion = "us-east-1" + // AWS IRSA session name prefix + sessionPrefix = "radius-ucp-" + // Credential source + credentialSource = "radiusucp" ) // UCPCredentialProvider is the implementation of aws.CredentialsProvider @@ -73,19 +90,65 @@ func (c *UCPCredentialProvider) Retrieve(ctx context.Context) (aws.Credentials, return aws.Credentials{}, err } - if s.AccessKeyCredential == nil || s.AccessKeyCredential.AccessKeyID == "" || s.AccessKeyCredential.SecretAccessKey == "" { - return aws.Credentials{}, errors.New("invalid access key info") - } - - logger.Info(fmt.Sprintf("Retrieved AWS Credential - AccessKeyID: %s", s.AccessKeyCredential.AccessKeyID)) - - value := aws.Credentials{ - AccessKeyID: s.AccessKeyCredential.AccessKeyID, - SecretAccessKey: s.AccessKeyCredential.SecretAccessKey, - Source: "radiusucp", - CanExpire: true, - // Enables AWS SDK to fetch (rotate) access keys by calling Retrieve() after Expires. - Expires: time.Now().UTC().Add(c.options.Duration), + var value aws.Credentials + switch s.Kind { + case CredentialKindAccessKey: + if s.AccessKeyCredential == nil || s.AccessKeyCredential.AccessKeyID == "" || s.AccessKeyCredential.SecretAccessKey == "" { + return aws.Credentials{}, errors.New("invalid access key info") + } + logger.Info(fmt.Sprintf("Retrieved AWS Credential - AccessKeyID: %s", s.AccessKeyCredential.AccessKeyID)) + + value = aws.Credentials{ + AccessKeyID: s.AccessKeyCredential.AccessKeyID, + SecretAccessKey: s.AccessKeyCredential.SecretAccessKey, + Source: credentialSource, + CanExpire: true, + Expires: time.Now().UTC().Add(c.options.Duration), + } + + case CredentialKindIRSA: + if s.IRSACredential == nil || s.IRSACredential.RoleARN == "" { + return aws.Credentials{}, errors.New("invalid IRSA info. RoleARN is required") + } + logger.Info(fmt.Sprintf("Retrieved AWS Credential - RoleARN: %s", s.IRSACredential.RoleARN)) + + // Radius requests will first be routed to STS endpoint, + // where it will be validated and then the request to the specific service (such as S3) will be made using + // the bearer token from the STS response. + // Based on the https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html, + // STS endpoint should be region based, and in the same region as + // Radius instance to minimize latency associated eith STS call and thereby improve performance. + // We should provide the user with ability to configure the STS endpoint region. + // For now, we are using the global STS endpoint, which is the default. + // Ref. https://github.com/radius-project/radius/issues/7747 + awscfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithRegion(awsSTSGlobalEndPointSigningRegion)) + + if err != nil { + return aws.Credentials{}, err + } + + client := sts.NewFromConfig(awscfg) + + credsCache := aws.NewCredentialsCache(stscreds.NewWebIdentityRoleProvider( + client, + s.IRSACredential.RoleARN, + stscreds.IdentityTokenFile(tokenFilePath), + func(o *stscreds.WebIdentityRoleOptions) { + o.RoleSessionName = sessionPrefix + uuid.New().String() + }, + )) + + value, err = credsCache.Retrieve(ctx) + if err != nil { + logger.Info(fmt.Sprintf("Failed to retrieve AWS Credential IRSA - %s", err.Error())) + return aws.Credentials{}, err + } + value.Source = credentialSource + value.CanExpire = true + value.Expires = time.Now().UTC().Add(c.options.Duration) + default: + return aws.Credentials{}, errors.New("invalid credential kind") } return value, nil diff --git a/pkg/ucp/aws/ucpcredentialprovider_test.go b/pkg/ucp/aws/ucpcredentialprovider_test.go index ff1a4ad7b2..1caf10679e 100644 --- a/pkg/ucp/aws/ucpcredentialprovider_test.go +++ b/pkg/ucp/aws/ucpcredentialprovider_test.go @@ -41,7 +41,7 @@ func (p *mockProvider) Fetch(ctx context.Context, planeName, name string) (*sdk_ return p.fakeCredential, nil } -func newMockProvider() *mockProvider { +func newMockProviderAccessKey() *mockProvider { return &mockProvider{ fakeCredential: &sdk_cred.AWSCredential{ Kind: ucp_datamodel.AWSAccessKeyCredentialKind, @@ -53,23 +53,44 @@ func newMockProvider() *mockProvider { } } +func newMockProviderIRSA() *mockProvider { + return &mockProvider{ + fakeCredential: &sdk_cred.AWSCredential{ + Kind: ucp_datamodel.AWSIRSACredentialKind, + IRSACredential: &ucp_datamodel.AWSIRSACredentialProperties{ + RoleARN: "fakearn", + }, + }, + } +} + func TestNewUCPCredentialProvider(t *testing.T) { - p := NewUCPCredentialProvider(newMockProvider(), 0) + p := NewUCPCredentialProvider(newMockProviderAccessKey(), 0) + require.Equal(t, DefaultExpireDuration, p.options.Duration) + + p = NewUCPCredentialProvider(newMockProviderIRSA(), 0) require.Equal(t, DefaultExpireDuration, p.options.Duration) } func TestRetrieve(t *testing.T) { t.Run("invalid credential", func(t *testing.T) { - p := newMockProvider() + p := newMockProviderAccessKey() cp := NewUCPCredentialProvider(p, DefaultExpireDuration) p.fakeCredential.AccessKeyCredential.AccessKeyID = "" _, err := cp.Retrieve(context.TODO()) require.Error(t, err) + + p = newMockProviderIRSA() + cp = NewUCPCredentialProvider(p, DefaultExpireDuration) + p.fakeCredential.IRSACredential.RoleARN = "" + + _, err = cp.Retrieve(context.TODO()) + require.Error(t, err) }) t.Run("valid credential", func(t *testing.T) { - p := newMockProvider() + p := newMockProviderAccessKey() cp := NewUCPCredentialProvider(p, DefaultExpireDuration) expectedExpiry := time.Now().UTC().Add(DefaultExpireDuration) diff --git a/pkg/ucp/credentials/aws.go b/pkg/ucp/credentials/aws.go index 399aae5589..3750062608 100644 --- a/pkg/ucp/credentials/aws.go +++ b/pkg/ucp/credentials/aws.go @@ -65,16 +65,17 @@ func (p *AWSCredentialProvider) Fetch(ctx context.Context, planeName, name strin switch p := cred.Properties.(type) { case *ucpapi.AwsAccessKeyCredentialProperties: - switch c := p.Storage.(type) { - case *ucpapi.InternalCredentialStorageProperties: - storage = c - default: - return nil, errors.New("invalid AWSAccessKeyCredentialProperties") - } + storage, err = getStorageProperties(p.Storage) + case *ucpapi.AwsIRSACredentialProperties: + storage, err = getStorageProperties(p.Storage) default: return nil, errors.New("invalid InternalCredentialStorageProperties") } + if err != nil { + return nil, err + } + secretName := to.String(storage.SecretName) if secretName == "" { return nil, errors.New("unspecified SecretName for internal storage") diff --git a/pkg/ucp/credentials/azure.go b/pkg/ucp/credentials/azure.go index de6e93b225..2712c00fa6 100644 --- a/pkg/ucp/credentials/azure.go +++ b/pkg/ucp/credentials/azure.go @@ -65,23 +65,17 @@ func (p *AzureCredentialProvider) Fetch(ctx context.Context, planeName, name str switch p := cred.Properties.(type) { case *ucpapi.AzureServicePrincipalProperties: - switch c := p.Storage.(type) { - case *ucpapi.InternalCredentialStorageProperties: - storage = c - default: - return nil, errors.New("Azure Credential is invalid - field 'properties.storage' is not InternalCredentialStorageProperties") - } + storage, err = getStorageProperties(p.Storage) case *ucpapi.AzureWorkloadIdentityProperties: - switch c := p.Storage.(type) { - case *ucpapi.InternalCredentialStorageProperties: - storage = c - default: - return nil, errors.New("Azure Credential is invalid - field 'properties.storage' is not InternalCredentialStorageProperties") - } + storage, err = getStorageProperties(p.Storage) default: return nil, errors.New("Azure Credential is invalid - field 'properties' is not AzureServicePrincipalProperties or AzureWorkloadIdentityProperties") } + if err != nil { + return nil, err + } + secretName := to.String(storage.SecretName) if secretName == "" { return nil, errors.New("unspecified SecretName for internal storage") diff --git a/pkg/ucp/credentials/types.go b/pkg/ucp/credentials/types.go index d6ccb9f952..637b39e600 100644 --- a/pkg/ucp/credentials/types.go +++ b/pkg/ucp/credentials/types.go @@ -18,7 +18,9 @@ package credentials import ( "context" + "errors" + ucpapi "github.com/radius-project/radius/pkg/ucp/api/v20231001preview" ucp_dm "github.com/radius-project/radius/pkg/ucp/datamodel" ) @@ -34,6 +36,12 @@ const ( // AzureWorkloadIdentityCredentialKind represents the kind of Azure workload identity credential. AzureWorkloadIdentityCredentialKind = ucp_dm.AzureWorkloadIdentityCredentialKind + + // AWSAccessKeyCredentialKind represents the kind of AWS access key credential. + AWSAccessKeyCredentialKind = ucp_dm.AWSAccessKeyCredentialKind + + // AWSIRSACredentialKind represents the kind of AWS IRSA credential. + AWSIRSACredentialKind = ucp_dm.AWSIRSACredentialKind ) type ( @@ -56,3 +64,12 @@ type CredentialProvider[T any] interface { // Fetch gets the credentials from secret storage. Fetch(ctx context.Context, planeName, name string) (*T, error) } + +func getStorageProperties(p any) (*ucpapi.InternalCredentialStorageProperties, error) { + switch c := p.(type) { + case *ucpapi.InternalCredentialStorageProperties: + return c, nil + default: + return nil, errors.New("invalid credential storage properties - field 'properties.storage' is not InternalCredentialStorageProperties") + } +} diff --git a/pkg/ucp/credentials/types_test.go b/pkg/ucp/credentials/types_test.go new file mode 100644 index 0000000000..5ab363009b --- /dev/null +++ b/pkg/ucp/credentials/types_test.go @@ -0,0 +1,44 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package credentials + +import ( + "errors" + "testing" + + ucpapi "github.com/radius-project/radius/pkg/ucp/api/v20231001preview" + + "github.com/stretchr/testify/require" +) + +func TestGetStorageProperties(t *testing.T) { + t.Run("Success", func(t *testing.T) { + input := &ucpapi.InternalCredentialStorageProperties{} + result, err := getStorageProperties(input) + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, input, result) + }) + + t.Run("InvalidType", func(t *testing.T) { + input := "invalid type" + result, err := getStorageProperties(input) + require.Error(t, err) + require.Nil(t, result) + require.Equal(t, errors.New("invalid credential storage properties - field 'properties.storage' is not InternalCredentialStorageProperties"), err) + }) +}