Skip to content

Commit

Permalink
feat: [CI-14214] : Added PLUGIN_USER_ROLE_EXTERNAL_ID to pass externa…
Browse files Browse the repository at this point in the history
…l 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]>
  • Loading branch information
DevanshMathur19 and Ompragash authored Oct 30, 2024
1 parent 6d61119 commit 665db05
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 58 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ vendor/

coverage.out
drone-s3

update_script.sh
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,25 @@ docker run --rm \
-w $(pwd) \
plugins/s3 --dry-run
```

## Configuration Variables for Secondary Role Assumption with External ID

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.

### Variables

#### `PLUGIN_USER_ROLE_ARN`

- **Type**: String
- **Required**: No
- **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.

#### `PLUGIN_USER_ROLE_EXTERNAL_ID`

- **Type**: String
- **Required**: No
- **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.

### Usage Notes

- If the role secondary role (`PLUGIN_USER_ROLE_ARN`) requires an External ID then pass it through `PLUGIN_USER_ROLE_EXTERNAL_ID`.
2 changes: 1 addition & 1 deletion docker/manifest.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ manifests:
platform:
architecture: amd64
os: windows
version: ltsc2022
version: ltsc2022
12 changes: 10 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"

"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)

Expand Down Expand Up @@ -52,6 +52,11 @@ func main() {
Usage: "AWS user role",
EnvVar: "PLUGIN_USER_ROLE_ARN,AWS_USER_ROLE_ARN",
},
cli.StringFlag{
Name: "user-role-external-id",
Usage: "external ID to use when assuming secondary role",
EnvVar: "PLUGIN_USER_ROLE_EXTERNAL_ID",
},
cli.StringFlag{
Name: "bucket",
Usage: "aws bucket",
Expand Down Expand Up @@ -149,7 +154,7 @@ func main() {
}

if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
log.Fatal(err)
}
}

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


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

return plugin.Exec()
}

125 changes: 70 additions & 55 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Plugin struct {
AssumeRoleSessionName string
Bucket string
UserRoleArn string
UserRoleExternalID string

// if not "", enable server-side encryption
// valid values are:
Expand Down Expand Up @@ -99,7 +100,7 @@ type Plugin struct {
// set externalID for assume role
ExternalID string

// set OIDC ID Token to retrieve temporary credentials
// set OIDC ID Token to retrieve temporary credentials
IdToken string
}

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

func assumeRole(roleArn, roleSessionName, externalID string) *credentials.Credentials {

sess, _ := session.NewSession()
client := sts.New(sess)
duration := time.Hour * 1
Expand All @@ -295,7 +297,9 @@ func assumeRole(roleArn, roleSessionName, externalID string) *credentials.Creden
stsProvider.ExternalID = &externalID
}

return credentials.NewCredentials(stsProvider)
creds := credentials.NewCredentials(stsProvider)

return creds
}

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

// createS3Client creates and returns an S3 client based on the plugin configuration
func (p *Plugin) createS3Client() *s3.S3 {
conf := &aws.Config{
Region: aws.String(p.Region),
Endpoint: &p.Endpoint,
DisableSSL: aws.Bool(strings.HasPrefix(p.Endpoint, "http://")),
S3ForcePathStyle: aws.Bool(p.PathStyle),
}

sess, err := session.NewSession(conf)
if err != nil {
log.Fatalf("failed to create AWS session: %v", err)
}

if p.Key != "" && p.Secret != "" {
conf.Credentials = credentials.NewStaticCredentials(p.Key, p.Secret, "")
} else if p.IdToken != "" && p.AssumeRole != "" {
creds, err := assumeRoleWithWebIdentity(sess, p.AssumeRole, p.AssumeRoleSessionName, p.IdToken)
if err != nil {
log.Fatalf("failed to assume role with web identity: %v", err)
}
conf.Credentials = creds
} else if p.AssumeRole != "" {
conf.Credentials = assumeRole(p.AssumeRole, p.AssumeRoleSessionName, p.ExternalID)
} else {
log.Warn("AWS Key and/or Secret not provided (falling back to ec2 instance profile)")
}

sess, err = session.NewSession(conf)
if err != nil {
log.Fatalf("failed to create AWS session: %v", err)
}

client := s3.New(sess, conf)

if len(p.UserRoleArn) > 0 {
confRoleArn := aws.Config{
Region: aws.String(p.Region),
Credentials: stscreds.NewCredentials(sess, p.UserRoleArn),
}
client = s3.New(sess, &confRoleArn)
}

return client

conf := &aws.Config{
Region: aws.String(p.Region),
Endpoint: &p.Endpoint,
DisableSSL: aws.Bool(strings.HasPrefix(p.Endpoint, "http://")),
S3ForcePathStyle: aws.Bool(p.PathStyle),
}

sess, err := session.NewSession(conf)
if err != nil {
log.Fatalf("failed to create AWS session: %v", err)
}

if p.Key != "" && p.Secret != "" {
conf.Credentials = credentials.NewStaticCredentials(p.Key, p.Secret, "")
} else if p.IdToken != "" && p.AssumeRole != "" {
creds, err := assumeRoleWithWebIdentity(sess, p.AssumeRole, p.AssumeRoleSessionName, p.IdToken)
if err != nil {
log.Fatalf("failed to assume role with web identity: %v", err)
}
conf.Credentials = creds
} else if p.AssumeRole != "" {
conf.Credentials = assumeRole(p.AssumeRole, p.AssumeRoleSessionName, p.ExternalID)
} else {
log.Warn("AWS Key and/or Secret not provided (falling back to ec2 instance profile)")
}

client := s3.New(sess, conf)

if len(p.UserRoleArn) > 0 {
log.WithField("UserRoleArn", p.UserRoleArn).Info("Using user role ARN")
// Create new credentials by assuming the UserRoleArn (with ExternalID when provided)
creds := stscreds.NewCredentials(sess, p.UserRoleArn, func(provider *stscreds.AssumeRoleProvider) {
if p.UserRoleExternalID != "" {
provider.ExternalID = aws.String(p.UserRoleExternalID)
}
})

// Create a new session with the new credentials
confWithUserRole := &aws.Config{
Region: aws.String(p.Region),
Credentials: creds,
}

sessWithUserRole, err := session.NewSession(confWithUserRole)
if err != nil {
log.Fatalf("failed to create AWS session with user role: %v", err)
}

client = s3.New(sessWithUserRole)
}

return client
}

func assumeRoleWithWebIdentity(sess *session.Session, roleArn, roleSessionName, idToken string) (*credentials.Credentials, error) {
svc := sts.New(sess)
input := &sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String(roleArn),
RoleSessionName: aws.String(roleSessionName),
WebIdentityToken: aws.String(idToken),
}
result, err := svc.AssumeRoleWithWebIdentity(input)
if err != nil {
log.Fatalf("failed to assume role with web identity: %v", err)
}
return credentials.NewStaticCredentials(*result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken), nil
svc := sts.New(sess)
input := &sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String(roleArn),
RoleSessionName: aws.String(roleSessionName),
WebIdentityToken: aws.String(idToken),
}
result, err := svc.AssumeRoleWithWebIdentity(input)
if err != nil {
log.Fatalf("failed to assume role with web identity: %v", err)
}
return credentials.NewStaticCredentials(*result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken), nil
}

0 comments on commit 665db05

Please sign in to comment.