Skip to content

Commit 665db05

Browse files
feat: [CI-14214] : Added PLUGIN_USER_ROLE_EXTERNAL_ID to pass external ID for the secondary role when required. (#174)
* feat : [CI-14214]: PLUGIN_USER_ROLE_EXTERNAL_ID added. * Updated manifest.tmpl * Updated manifest.tmpl * Update main.go Removing testing code. Co-authored-by: OP (oppenheimer) <[email protected]> * Update main.go Removing loggers. Co-authored-by: OP (oppenheimer) <[email protected]> * Update plugin.go Removing loggers. Co-authored-by: OP (oppenheimer) <[email protected]> * Update plugin.go Removing loggers. Co-authored-by: OP (oppenheimer) <[email protected]> * Updating README * Remove excessive logging. * Update README.md * Update README.md * Fixing CI-14887 and CI-14134 * Fixing CI-14887 and CI-14134 * Testing with AWS KEYS withour second session. * Update plugin.go * Update README.md * Update README.md test change to trigger CI job --------- Co-authored-by: OP (oppenheimer) <[email protected]>
1 parent 6d61119 commit 665db05

File tree

5 files changed

+105
-58
lines changed

5 files changed

+105
-58
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ vendor/
2828

2929
coverage.out
3030
drone-s3
31+
32+
update_script.sh

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,25 @@ docker run --rm \
7474
-w $(pwd) \
7575
plugins/s3 --dry-run
7676
```
77+
78+
## Configuration Variables for Secondary Role Assumption with External ID
79+
80+
The following environment variables enable the plugin to assume a secondary IAM role using IRSA, with an External ID if required by the role’s trust policy.
81+
82+
### Variables
83+
84+
#### `PLUGIN_USER_ROLE_ARN`
85+
86+
- **Type**: String
87+
- **Required**: No
88+
- **Description**: Specifies the secondary IAM role to be assumed by the plugin, allowing it to inherit permissions associated with this role and access specific AWS resources.
89+
90+
#### `PLUGIN_USER_ROLE_EXTERNAL_ID`
91+
92+
- **Type**: String
93+
- **Required**: No
94+
- **Description**: Provide the External ID necessary for the role assumption process if the secondary role’s trust policy mandates it. This is often required for added security, ensuring that only authorized entities assume the role.
95+
96+
### Usage Notes
97+
98+
- If the role secondary role (`PLUGIN_USER_ROLE_ARN`) requires an External ID then pass it through `PLUGIN_USER_ROLE_EXTERNAL_ID`.

docker/manifest.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ manifests:
2828
platform:
2929
architecture: amd64
3030
os: windows
31-
version: ltsc2022
31+
version: ltsc2022

main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"os"
66

77
"github.com/joho/godotenv"
8-
"github.com/sirupsen/logrus"
8+
log "github.com/sirupsen/logrus"
99
"github.com/urfave/cli"
1010
)
1111

@@ -52,6 +52,11 @@ func main() {
5252
Usage: "AWS user role",
5353
EnvVar: "PLUGIN_USER_ROLE_ARN,AWS_USER_ROLE_ARN",
5454
},
55+
cli.StringFlag{
56+
Name: "user-role-external-id",
57+
Usage: "external ID to use when assuming secondary role",
58+
EnvVar: "PLUGIN_USER_ROLE_EXTERNAL_ID",
59+
},
5560
cli.StringFlag{
5661
Name: "bucket",
5762
Usage: "aws bucket",
@@ -149,7 +154,7 @@ func main() {
149154
}
150155

151156
if err := app.Run(os.Args); err != nil {
152-
logrus.Fatal(err)
157+
log.Fatal(err)
153158
}
154159
}
155160

@@ -158,6 +163,7 @@ func run(c *cli.Context) error {
158163
_ = godotenv.Load(c.String("env-file"))
159164
}
160165

166+
161167
plugin := Plugin{
162168
Endpoint: c.String("endpoint"),
163169
Key: c.String("access-key"),
@@ -166,6 +172,7 @@ func run(c *cli.Context) error {
166172
AssumeRoleSessionName: c.String("assume-role-session-name"),
167173
Bucket: c.String("bucket"),
168174
UserRoleArn: c.String("user-role-arn"),
175+
UserRoleExternalID: c.String("user-role-external-id"),
169176
Region: c.String("region"),
170177
Access: c.String("acl"),
171178
Source: c.String("source"),
@@ -186,3 +193,4 @@ func run(c *cli.Context) error {
186193

187194
return plugin.Exec()
188195
}
196+

plugin.go

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Plugin struct {
2929
AssumeRoleSessionName string
3030
Bucket string
3131
UserRoleArn string
32+
UserRoleExternalID string
3233

3334
// if not "", enable server-side encryption
3435
// valid values are:
@@ -99,7 +100,7 @@ type Plugin struct {
99100
// set externalID for assume role
100101
ExternalID string
101102

102-
// set OIDC ID Token to retrieve temporary credentials
103+
// set OIDC ID Token to retrieve temporary credentials
103104
IdToken string
104105
}
105106

@@ -281,6 +282,7 @@ func matchExtension(match string, stringMap map[string]string) string {
281282
}
282283

283284
func assumeRole(roleArn, roleSessionName, externalID string) *credentials.Credentials {
285+
284286
sess, _ := session.NewSession()
285287
client := sts.New(sess)
286288
duration := time.Hour * 1
@@ -295,7 +297,9 @@ func assumeRole(roleArn, roleSessionName, externalID string) *credentials.Creden
295297
stsProvider.ExternalID = &externalID
296298
}
297299

298-
return credentials.NewCredentials(stsProvider)
300+
creds := credentials.NewCredentials(stsProvider)
301+
302+
return creds
299303
}
300304

301305
// resolveKey is a helper function that returns s3 object key where file present at srcPath is uploaded to.
@@ -434,60 +438,71 @@ func (p *Plugin) downloadS3Objects(client *s3.S3, sourceDir string) error {
434438

435439
// createS3Client creates and returns an S3 client based on the plugin configuration
436440
func (p *Plugin) createS3Client() *s3.S3 {
437-
conf := &aws.Config{
438-
Region: aws.String(p.Region),
439-
Endpoint: &p.Endpoint,
440-
DisableSSL: aws.Bool(strings.HasPrefix(p.Endpoint, "http://")),
441-
S3ForcePathStyle: aws.Bool(p.PathStyle),
442-
}
443-
444-
sess, err := session.NewSession(conf)
445-
if err != nil {
446-
log.Fatalf("failed to create AWS session: %v", err)
447-
}
448-
449-
if p.Key != "" && p.Secret != "" {
450-
conf.Credentials = credentials.NewStaticCredentials(p.Key, p.Secret, "")
451-
} else if p.IdToken != "" && p.AssumeRole != "" {
452-
creds, err := assumeRoleWithWebIdentity(sess, p.AssumeRole, p.AssumeRoleSessionName, p.IdToken)
453-
if err != nil {
454-
log.Fatalf("failed to assume role with web identity: %v", err)
455-
}
456-
conf.Credentials = creds
457-
} else if p.AssumeRole != "" {
458-
conf.Credentials = assumeRole(p.AssumeRole, p.AssumeRoleSessionName, p.ExternalID)
459-
} else {
460-
log.Warn("AWS Key and/or Secret not provided (falling back to ec2 instance profile)")
461-
}
462-
463-
sess, err = session.NewSession(conf)
464-
if err != nil {
465-
log.Fatalf("failed to create AWS session: %v", err)
466-
}
467-
468-
client := s3.New(sess, conf)
469-
470-
if len(p.UserRoleArn) > 0 {
471-
confRoleArn := aws.Config{
472-
Region: aws.String(p.Region),
473-
Credentials: stscreds.NewCredentials(sess, p.UserRoleArn),
474-
}
475-
client = s3.New(sess, &confRoleArn)
476-
}
477-
478-
return client
441+
442+
conf := &aws.Config{
443+
Region: aws.String(p.Region),
444+
Endpoint: &p.Endpoint,
445+
DisableSSL: aws.Bool(strings.HasPrefix(p.Endpoint, "http://")),
446+
S3ForcePathStyle: aws.Bool(p.PathStyle),
447+
}
448+
449+
sess, err := session.NewSession(conf)
450+
if err != nil {
451+
log.Fatalf("failed to create AWS session: %v", err)
452+
}
453+
454+
if p.Key != "" && p.Secret != "" {
455+
conf.Credentials = credentials.NewStaticCredentials(p.Key, p.Secret, "")
456+
} else if p.IdToken != "" && p.AssumeRole != "" {
457+
creds, err := assumeRoleWithWebIdentity(sess, p.AssumeRole, p.AssumeRoleSessionName, p.IdToken)
458+
if err != nil {
459+
log.Fatalf("failed to assume role with web identity: %v", err)
460+
}
461+
conf.Credentials = creds
462+
} else if p.AssumeRole != "" {
463+
conf.Credentials = assumeRole(p.AssumeRole, p.AssumeRoleSessionName, p.ExternalID)
464+
} else {
465+
log.Warn("AWS Key and/or Secret not provided (falling back to ec2 instance profile)")
466+
}
467+
468+
client := s3.New(sess, conf)
469+
470+
if len(p.UserRoleArn) > 0 {
471+
log.WithField("UserRoleArn", p.UserRoleArn).Info("Using user role ARN")
472+
// Create new credentials by assuming the UserRoleArn (with ExternalID when provided)
473+
creds := stscreds.NewCredentials(sess, p.UserRoleArn, func(provider *stscreds.AssumeRoleProvider) {
474+
if p.UserRoleExternalID != "" {
475+
provider.ExternalID = aws.String(p.UserRoleExternalID)
476+
}
477+
})
478+
479+
// Create a new session with the new credentials
480+
confWithUserRole := &aws.Config{
481+
Region: aws.String(p.Region),
482+
Credentials: creds,
483+
}
484+
485+
sessWithUserRole, err := session.NewSession(confWithUserRole)
486+
if err != nil {
487+
log.Fatalf("failed to create AWS session with user role: %v", err)
488+
}
489+
490+
client = s3.New(sessWithUserRole)
491+
}
492+
493+
return client
479494
}
480495

481496
func assumeRoleWithWebIdentity(sess *session.Session, roleArn, roleSessionName, idToken string) (*credentials.Credentials, error) {
482-
svc := sts.New(sess)
483-
input := &sts.AssumeRoleWithWebIdentityInput{
484-
RoleArn: aws.String(roleArn),
485-
RoleSessionName: aws.String(roleSessionName),
486-
WebIdentityToken: aws.String(idToken),
487-
}
488-
result, err := svc.AssumeRoleWithWebIdentity(input)
489-
if err != nil {
490-
log.Fatalf("failed to assume role with web identity: %v", err)
491-
}
492-
return credentials.NewStaticCredentials(*result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken), nil
497+
svc := sts.New(sess)
498+
input := &sts.AssumeRoleWithWebIdentityInput{
499+
RoleArn: aws.String(roleArn),
500+
RoleSessionName: aws.String(roleSessionName),
501+
WebIdentityToken: aws.String(idToken),
502+
}
503+
result, err := svc.AssumeRoleWithWebIdentity(input)
504+
if err != nil {
505+
log.Fatalf("failed to assume role with web identity: %v", err)
506+
}
507+
return credentials.NewStaticCredentials(*result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken), nil
493508
}

0 commit comments

Comments
 (0)