Skip to content
Open
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
2 changes: 1 addition & 1 deletion examples/kubernetes/cross_account_mount/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Lets say you have an EKS cluster in aws account `A` & you wish to mount your fil
1. Perform [vpc-peering](https://docs.aws.amazon.com/vpc/latest/peering/working-with-vpc-peering.html) between EKS cluster `vpc` in aws account `A` and EFS `vpc` in another aws account `B`.
2. Create an IAM role, say `EFSCrossAccountAccessRole` in Account `B` which has a [trust relationship](./iam-policy-examples/trust-relationship-example.json) with Account `A` and add an inline EFS policy with [permissions](./iam-policy-examples/describe-mount-target-example.json) to call `DescribeMountTargets`. This role will be used by CSI-Driver's Controller service running on EKS cluster in account `A` to determine the mount targets for your file system in account `B`.
3. In aws account `A`, attach an inline policy to IAM role of efs-csi-driver's controller service account with necessary [permissions](./iam-policy-examples/cross-account-assume-policy-example.json) to perform `sts assume role` on the IAM role created in step 2.
4. Create a kubernetes secret with `awsRoleArn` as the key and the role from step 2 as the value. For example, `kubectl create secret generic x-account --namespace=default --from-literal=awsRoleArn='arn:aws:iam::123456789012:role/EFSCrossAccountAccessRole'`. If you would like to ensure that your EFS Mount Target is in the same availability zone as your EKS Node, then ensure you have completed the [prerequisites for cross-account DNS resolution](https://github.com/aws/efs-utils?tab=readme-ov-file#crossaccount-option-prerequisites) and include the `crossaccount` key with value `true`. For example, `kubectl create secret generic x-account --namespace=kube-system --from-literal=awsRoleArn='arn:aws:iam::123456789012:role/EFSCrossAccountAccessRole' --from-literal=crossaccount='true'` instead.
4. Create a kubernetes secret with `awsRoleArn` as the key and the role from step 2 as the value. For example, `kubectl create secret generic x-account --namespace=default --from-literal=awsRoleArn='arn:aws:iam::123456789012:role/EFSCrossAccountAccessRole'`.If your IAM role ARN requires externalId as validation, include `externalId` key with the value. For example, `kubectl create secret generic x-account --namespace=kube-system --from-literal=awsRoleArn='arn:aws:iam::123456789012:role/EFSCrossAccountAccessRole --from-literal=externalId="external-id"`. If you would like to ensure that your EFS Mount Target is in the same availability zone as your EKS Node, then ensure you have completed the [prerequisites for cross-account DNS resolution](https://github.com/aws/efs-utils?tab=readme-ov-file#crossaccount-option-prerequisites) and include the `crossaccount` key with value `true`. For example, `kubectl create secret generic x-account --namespace=kube-system --from-literal=awsRoleArn='arn:aws:iam::123456789012:role/EFSCrossAccountAccessRole' --from-literal=crossaccount='true'` instead.
5. Create an IAM role for service accounts for EKS cluster in account `A` with required [permissions](./iam-policy-examples/node-deamonset-iam-policy-example.json) for EFS client mount. Alternatively, you can find this policy under AWS managed policy as `AmazonElasticFileSystemClientFullAccess`.
6. Attach the service account from step 5 to node daemonset.
7. Create a [file system policy](https://docs.aws.amazon.com/efs/latest/ug/iam-access-control-nfs-efs.html#file-sys-policy-examples) for file system in account `B` which allows account `A` to perform mount on it.
Expand Down
24 changes: 16 additions & 8 deletions pkg/cloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import (
"errors"
"fmt"

"github.com/aws/smithy-go"
"math/rand"
"os"
"time"

"github.com/aws/smithy-go"

"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"
Expand Down Expand Up @@ -118,16 +119,16 @@ type cloud struct {
// NewCloud returns a new instance of AWS cloud
// It panics if session is invalid
func NewCloud(adaptiveRetryMode bool) (Cloud, error) {
return createCloud("", adaptiveRetryMode)
return createCloud("", "", adaptiveRetryMode)
}

// NewCloudWithRole returns a new instance of AWS cloud after assuming an aws role
// It panics if driver does not have permissions to assume role.
func NewCloudWithRole(awsRoleArn string, adaptiveRetryMode bool) (Cloud, error) {
return createCloud(awsRoleArn, adaptiveRetryMode)
func NewCloudWithRole(awsRoleArn string, externalId string, adaptiveRetryMode bool) (Cloud, error) {
return createCloud(awsRoleArn, externalId, adaptiveRetryMode)
}

func createCloud(awsRoleArn string, adaptiveRetryMode bool) (Cloud, error) {
func createCloud(awsRoleArn string, externalId string, adaptiveRetryMode bool) (Cloud, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
klog.Warningf("Could not load config: %v", err)
Expand All @@ -152,7 +153,7 @@ func createCloud(awsRoleArn string, adaptiveRetryMode bool) (Cloud, error) {

rm := newRetryManager(adaptiveRetryMode)

efs_client := createEfsClient(awsRoleArn, metadata)
efs_client := createEfsClient(awsRoleArn, externalId, metadata)
klog.V(5).Infof("EFS Client created using the following endpoint: %+v", cfg.BaseEndpoint)

return &cloud{
Expand All @@ -162,11 +163,18 @@ func createCloud(awsRoleArn string, adaptiveRetryMode bool) (Cloud, error) {
}, nil
}

func createEfsClient(awsRoleArn string, metadata MetadataService) Efs {
func createEfsClient(awsRoleArn string, externalId string, metadata MetadataService) Efs {
cfg, _ := config.LoadDefaultConfig(context.TODO(), config.WithRegion(metadata.GetRegion()))
if awsRoleArn != "" {
stsClient := sts.NewFromConfig(cfg)
roleProvider := stscreds.NewAssumeRoleProvider(stsClient, awsRoleArn)
var roleProvider aws.CredentialsProvider
if externalId != "" {
roleProvider = stscreds.NewAssumeRoleProvider(stsClient, awsRoleArn, func(o *stscreds.AssumeRoleOptions) {
o.ExternalID = &externalId
})
} else {
roleProvider = stscreds.NewAssumeRoleProvider(stsClient, awsRoleArn)
}
cfg.Credentials = aws.NewCredentialsCache(roleProvider)
}
return efs.NewFromConfig(cfg)
Expand Down
19 changes: 16 additions & 3 deletions pkg/driver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const (
DefaultTagValue = "true"
DirectoryPerms = "directoryPerms"
EnsureUniqueDirectory = "ensureUniqueDirectory"
ExternalId = "externalId"
FsId = "fileSystemId"
Gid = "gid"
GidMin = "gidRangeStart"
Expand Down Expand Up @@ -644,6 +645,7 @@ func getCloud(secrets map[string]string, driver *Driver) (cloud.Cloud, string, b

var localCloud cloud.Cloud
var roleArn string
var externalId string
var crossAccountDNSEnabled bool
var err error

Expand All @@ -652,6 +654,10 @@ func getCloud(secrets map[string]string, driver *Driver) (cloud.Cloud, string, b
if value, ok := secrets[RoleArn]; ok {
roleArn = value
}
if value, ok := secrets[ExternalId]; ok {
externalId = value
}

if value, ok := secrets[CrossAccount]; ok {
crossAccountDNSEnabled, err = strconv.ParseBool(value)
if err != nil {
Expand All @@ -662,9 +668,16 @@ func getCloud(secrets map[string]string, driver *Driver) (cloud.Cloud, string, b
}

if roleArn != "" {
localCloud, err = cloud.NewCloudWithRole(roleArn, driver.adaptiveRetryMode)
if err != nil {
return nil, "", false, status.Errorf(codes.Unauthenticated, "Unable to initialize aws cloud: %v. Please verify role has the correct AWS permissions for cross account mount", err)
if externalId != "" {
localCloud, err = cloud.NewCloudWithRole(roleArn, externalId, driver.adaptiveRetryMode)
if err != nil {
return nil, "", false, status.Errorf(codes.Unauthenticated, "Unable to initialize aws cloud: %v. Please verify role has the correct AWS permissions for cross account mount", err)
}
} else {
localCloud, err = cloud.NewCloudWithRole(roleArn, "", driver.adaptiveRetryMode)
if err != nil {
return nil, "", false, status.Errorf(codes.Unauthenticated, "Unable to initialize aws cloud: %v. Please verify role has the correct AWS permissions for cross account mount", err)
}
}
} else {
localCloud = driver.cloud
Expand Down
2 changes: 2 additions & 0 deletions pkg/driver/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2888,6 +2888,7 @@ func TestCreateVolume(t *testing.T) {

secrets := map[string]string{}
secrets["awsRoleArn"] = "arn:aws:iam::1234567890:role/EFSCrossAccountRole"
secrets["externalId"] = "external-id"
secrets["crossaccount"] = "true"

req := &csi.CreateVolumeRequest{
Expand Down Expand Up @@ -4091,6 +4092,7 @@ func TestDeleteVolume(t *testing.T) {

secrets := map[string]string{}
secrets["awsRoleArn"] = "arn:aws:iam::1234567890:role/EFSCrossAccountRole"
secrets["externalId"] = "external-id"

req := &csi.DeleteVolumeRequest{
VolumeId: volumeId,
Expand Down