Skip to content

Commit

Permalink
implement irsa server side support (radius-project#7738)
Browse files Browse the repository at this point in the history
# Description

Add server side support for AWS IRSA.
UCP handles AWS resource deployment and needs irsa suuport.
Terraform provider communicates with AWS directly and needs IRSA support
too.

## Type of change
- This pull request adds or changes features of Radius and has an
approved issue (issue link required).

Partially Fixes: radius-project#7618

---------

Signed-off-by: nithyatsu <nithyasu@microsoft.com>
Signed-off-by: Nithya Subramanian <98416062+nithyatsu@users.noreply.github.com>
Co-authored-by: Karishma Chawla <kachawla@microsoft.com>
Signed-off-by: Reshma Abdul Rahim <reshmarahim.abdul@microsoft.com>
  • Loading branch information
2 people authored and Reshrahim committed Aug 27, 2024
1 parent c68b960 commit abd3345
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 69 deletions.
39 changes: 34 additions & 5 deletions pkg/recipes/terraform/config/providers/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
146 changes: 118 additions & 28 deletions pkg/recipes/terraform/config/providers/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
}
Expand All @@ -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
Expand Down Expand Up @@ -185,17 +210,17 @@ 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
expectedCreds *ucp_credentials.AWSCredential
expectedErr bool
}{
{
desc: "valid credentials",
credentialsProvider: newMockAWSCredentialsProvider(),
expectedCreds: &testAWSCredentials,
desc: "valid accesskey credentials",
credentialsProvider: newMockAWSAccessKeyCredentialsProvider(),
expectedCreds: &testAWSAccessKeyCredentials,
expectedErr: false,
},
{
Expand All @@ -207,27 +232,60 @@ 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,
},
},
},
expectedCreds: nil,
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: "",
},
},
},
Expand Down Expand Up @@ -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,
},
},
{
Expand All @@ -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{},
Expand All @@ -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])
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading

0 comments on commit abd3345

Please sign in to comment.