Skip to content

Commit

Permalink
Change aws policy template rendering (#89)
Browse files Browse the repository at this point in the history
* Change aws-policy-template-rendering

* Add extra bucket names to role
  • Loading branch information
QuentinBisson authored Feb 15, 2024
1 parent 0d0e6fe commit 73f76ac
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Change rendering of bucket policies to use template/text instead of a string to be able to add extra bucket access (needed for the mimir ruler)

## [0.4.3] - 2024-01-11

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/bucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type BucketAccessRole struct {
// Name of the role to create
RoleName string `json:"roleName"`

// ExtraBucketNames is a list of bucket names to add to the role policy in case the role needs to be able to access multiple buckets.
ExtraBucketNames []string `json:"extraBucketNames,omitempty"`

// Name of the service account
ServiceAccountName string `json:"serviceAccountName"`

Expand Down
7 changes: 6 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions config/crd/objectstorage.giantswarm.io_buckets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ spec:
accessRole:
description: Access role that can be assumed to access the bucket
properties:
extraBucketNames:
description: ExtraBucketNames is a list of bucket names to add
to the role policy in case the role needs to be able to access
multiple buckets.
items:
type: string
type: array
roleName:
description: Name of the role to create
type: string
Expand Down
76 changes: 50 additions & 26 deletions internal/pkg/service/objectstorage/cloud/aws/iam.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package aws

import (
"bytes"
"context"
"fmt"
"reflect"
"strings"
"text/template"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
Expand All @@ -17,18 +18,30 @@ import (
)

type IAMAccessRoleServiceAdapter struct {
iamClient *iam.Client
logger logr.Logger
accountId string
cluster AWSCluster
iamClient *iam.Client
logger logr.Logger
accountId string
cluster AWSCluster
trustIdentityPolicy *template.Template
rolePolicy *template.Template
}

func NewIamService(iamClient *iam.Client, logger logr.Logger, accountId string, cluster AWSCluster) IAMAccessRoleServiceAdapter {
trustIdentityPolicy, err := template.New("trustIdentityPolicy").Parse(trustIdentityPolicy)
if err != nil {
panic(err)
}
rolePolicy, err := template.New("rolePolicy").Parse(rolePolicy)
if err != nil {
panic(err)
}
return IAMAccessRoleServiceAdapter{
iamClient: iamClient,
logger: logger,
accountId: accountId,
cluster: cluster,
iamClient: iamClient,
logger: logger,
accountId: accountId,
cluster: cluster,
trustIdentityPolicy: trustIdentityPolicy,
rolePolicy: rolePolicy,
}
}

Expand Down Expand Up @@ -78,12 +91,22 @@ func (s IAMAccessRoleServiceAdapter) ConfigureRole(ctx context.Context, bucket *
}
}

trustPolicy := s.templateTrustPolicy(bucket)
var trustPolicy bytes.Buffer
err = s.trustIdentityPolicy.Execute(&trustPolicy, TrustIdentityPolicyData{
AccountId: s.accountId,
CloudDomain: s.cluster.GetBaseDomain(),
Installation: s.cluster.GetName(),
ServiceAccountName: bucket.Spec.AccessRole.ServiceAccountName,
ServiceAccountNamespace: bucket.Spec.AccessRole.ServiceAccountNamespace,
})
if err != nil {
return err
}

if role == nil {
_, err := s.iamClient.CreateRole(ctx, &iam.CreateRoleInput{
RoleName: aws.String(roleName),
AssumeRolePolicyDocument: aws.String(trustPolicy),
AssumeRolePolicyDocument: aws.String(trustPolicy.String()),
Description: aws.String("Role for Giant Swarm managed Loki"),
Tags: tags,
})
Expand All @@ -94,7 +117,7 @@ func (s IAMAccessRoleServiceAdapter) ConfigureRole(ctx context.Context, bucket *
} else {
_, err = s.iamClient.UpdateAssumeRolePolicy(ctx, &iam.UpdateAssumeRolePolicyInput{
RoleName: aws.String(roleName),
PolicyDocument: aws.String(trustPolicy),
PolicyDocument: aws.String(trustPolicy.String()),
})
if err != nil {
return errors.WithStack(err)
Expand Down Expand Up @@ -123,10 +146,24 @@ func (s IAMAccessRoleServiceAdapter) ConfigureRole(ctx context.Context, bucket *
}
}

var rolePolicy bytes.Buffer
var data = RolePolicyData{
BucketName: bucket.Spec.Name,
}

if len(bucket.Spec.AccessRole.ExtraBucketNames) > 0 {
data.ExtraBucketNames = bucket.Spec.AccessRole.ExtraBucketNames
}

err = s.rolePolicy.Execute(&rolePolicy, data)
if err != nil {
return err
}

_, err = s.iamClient.PutRolePolicy(ctx, &iam.PutRolePolicyInput{
RoleName: aws.String(roleName),
PolicyName: aws.String(roleName),
PolicyDocument: aws.String(templateRolePolicy(bucket)),
PolicyDocument: aws.String(rolePolicy.String()),
})
if err != nil {
return errors.WithStack(err)
Expand Down Expand Up @@ -247,16 +284,3 @@ func (s *IAMAccessRoleServiceAdapter) cleanAttachedPolicies(ctx context.Context,
s.logger.Info("cleaned attached and inline policies from IAM Role")
return nil
}

func (s IAMAccessRoleServiceAdapter) templateTrustPolicy(bucket *v1alpha1.Bucket) string {
policy := strings.ReplaceAll(trustIdentityPolicy, "@CLOUD_DOMAIN@", s.cluster.GetBaseDomain())
policy = strings.ReplaceAll(policy, "@INSTALLATION@", s.cluster.GetName())
policy = strings.ReplaceAll(policy, "@ACCOUNT_ID@", s.accountId)
policy = strings.ReplaceAll(policy, "@SERVICE_ACCOUNT_NAMESPACE@", bucket.Spec.AccessRole.ServiceAccountNamespace)
policy = strings.ReplaceAll(policy, "@SERVICE_ACCOUNT_NAME@", bucket.Spec.AccessRole.ServiceAccountName)
return policy
}

func templateRolePolicy(bucket *v1alpha1.Bucket) string {
return strings.ReplaceAll(rolePolicy, "@BUCKET_NAME@", bucket.Spec.Name)
}
31 changes: 22 additions & 9 deletions internal/pkg/service/objectstorage/cloud/aws/s3.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package aws

import (
"bytes"
"context"
"errors"
"strings"
"text/template"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
Expand All @@ -15,16 +16,23 @@ import (
)

type S3ObjectStorageAdapter struct {
s3Client *s3.Client
logger logr.Logger
cluster AWSCluster
s3Client *s3.Client
logger logr.Logger
cluster AWSCluster
bucketPolicyTemplate *template.Template
}

func NewS3Service(s3Client *s3.Client, logger logr.Logger, cluster AWSCluster) S3ObjectStorageAdapter {
bucketPolicyTemplate, err := template.New("bucketPolicy").Parse(bucketPolicy)
if err != nil {
panic(err)
}

return S3ObjectStorageAdapter{
s3Client: s3Client,
logger: logger,
cluster: cluster,
s3Client: s3Client,
logger: logger,
cluster: cluster,
bucketPolicyTemplate: bucketPolicyTemplate,
}
}
func (s S3ObjectStorageAdapter) ExistsBucket(ctx context.Context, bucket *v1alpha1.Bucket) (bool, error) {
Expand Down Expand Up @@ -111,9 +119,14 @@ func (s S3ObjectStorageAdapter) setLifecycleRules(ctx context.Context, bucket *v
}

func (s S3ObjectStorageAdapter) setBucketPolicy(ctx context.Context, bucket *v1alpha1.Bucket) error {
_, err := s.s3Client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
var policy bytes.Buffer
err := s.bucketPolicyTemplate.Execute(&policy, BucketPolicyData{bucket.Spec.Name})
if err != nil {
return err
}
_, err = s.s3Client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
Bucket: aws.String(bucket.Spec.Name),
Policy: aws.String(strings.ReplaceAll(bucketPolicy, "@BUCKET_NAME@", bucket.Spec.Name)),
Policy: aws.String(policy.String()),
})
return err
}
Expand Down
33 changes: 27 additions & 6 deletions internal/pkg/service/objectstorage/cloud/aws/templates.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package aws

type RolePolicyData struct {
BucketName string
ExtraBucketNames []string
}

const rolePolicy = `{
"Version": "2012-10-17",
"Statement": [
Expand All @@ -12,8 +17,12 @@ const rolePolicy = `{
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::@BUCKET_NAME@",
"arn:aws:s3:::@BUCKET_NAME@/*"
{{ range .ExtraBucketNames }}
"arn:aws:s3:::{{ . }}",
"arn:aws:s3:::{{ . }}/*",
{{ end }}
"arn:aws:s3:::{{ .BucketName }}",
"arn:aws:s3:::{{ .BucketName }}/*"
]
},
{
Expand All @@ -28,24 +37,36 @@ const rolePolicy = `{
]
}`

type TrustIdentityPolicyData struct {
AccountId string
CloudDomain string
Installation string
ServiceAccountName string
ServiceAccountNamespace string
}

const trustIdentityPolicy = `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::@ACCOUNT_ID@:oidc-provider/irsa.@INSTALLATION@.@CLOUD_DOMAIN@"
"Federated": "arn:aws:iam::{{ .AccountId }}:oidc-provider/irsa.{{ .Installation }}.{{ .CloudDomain }}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"irsa.@INSTALLATION@.@CLOUD_DOMAIN@:sub": "system:serviceaccount:@SERVICE_ACCOUNT_NAMESPACE@:@SERVICE_ACCOUNT_NAME@"
"irsa.{{ .Installation }}.{{ .CloudDomain }}:sub": "system:serviceaccount:{{ .ServiceAccountNamespace }}:{{ .ServiceAccountName }}"
}
}
}
]
}`

type BucketPolicyData struct {
BucketName string
}

const bucketPolicy = `{
"Version": "2012-10-17",
"Statement": [
Expand All @@ -55,8 +76,8 @@ const bucketPolicy = `{
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::@BUCKET_NAME@",
"arn:aws:s3:::@BUCKET_NAME@/*"
"arn:aws:s3:::{{ .BucketName }}",
"arn:aws:s3:::{{ .BucketName }}/*"
],
"Condition": {
"Bool": {
Expand Down

0 comments on commit 73f76ac

Please sign in to comment.