Skip to content

Commit

Permalink
sts: Add STS support for S3 (PROJQUAY-6362) (quay#2632)
Browse files Browse the repository at this point in the history
- Add STS authentication to s3
- Add STS validation to config tool
- PR automatically refreshes token on expiry
  • Loading branch information
jonathankingfc authored Feb 22, 2024
1 parent 4cb0a57 commit 233c128
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,27 @@ func NewDistributedStorageArgs(storageArgs map[string]interface{}) (*shared.Dist
}
}

if value, ok := storageArgs["sts_role_arn"]; ok {
newDistributedStorageArgs.STSRoleArn, ok = value.(string)
if !ok {
return newDistributedStorageArgs, errors.New("sts_role_arn must be a string")
}
}

if value, ok := storageArgs["sts_user_access_key"]; ok {
newDistributedStorageArgs.STSUserAccessKey, ok = value.(string)
if !ok {
return newDistributedStorageArgs, errors.New("sts_user_access_key must be a string")
}
}

if value, ok := storageArgs["sts_user_secret_key"]; ok {
newDistributedStorageArgs.STSUserSecretKey, ok = value.(string)
if !ok {
return newDistributedStorageArgs, errors.New("sts_user_secret_key must be a string")
}
}

return newDistributedStorageArgs, nil
}

Expand Down
53 changes: 53 additions & 0 deletions config-tool/pkg/lib/shared/storage_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
awscredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/ncw/swift"
Expand Down Expand Up @@ -143,6 +144,58 @@ func ValidateStorage(opts Options, storageName string, storageType string, args
errors = append(errors, err)
}

case "STSS3Storage":

// Check bucket name
if ok, err := ValidateRequiredString(args.S3Bucket, "DISTRIBUTED_STORAGE_CONFIG."+storageName+".s3_bucket", fgName); !ok {
errors = append(errors, err)
}

roleArn := args.STSRoleArn
sess := session.Must(session.NewSession(&aws.Config{
Credentials: awscredentials.NewStaticCredentials(args.STSUserAccessKey, args.STSUserSecretKey, ""),
}))
svc := sts.New(sess)
roleToAssumeArn := roleArn
durationSeconds := int64(3600)
assumeRoleInput := &sts.AssumeRoleInput{
RoleArn: aws.String(roleToAssumeArn),
RoleSessionName: aws.String("quay"),
DurationSeconds: aws.Int64(durationSeconds),
}
assumeRoleOutput, err := svc.AssumeRole(assumeRoleInput)
if err != nil {
errors = append(errors, ValidationError{
Tags: []string{"DISTRIBUTED_STORAGE_CONFIG"},
FieldGroup: fgName,
Message: "Could not fetch credentials from STS. Error: " + err.Error(),
})
break
}

accessKey := *assumeRoleOutput.Credentials.AccessKeyId
secretKey := *assumeRoleOutput.Credentials.SecretAccessKey
token = *assumeRoleOutput.Credentials.SessionToken
bucketName = args.S3Bucket
isSecure = true

if len(args.Host) == 0 {
endpoint = "s3.amazonaws.com"
} else {
endpoint = args.Host
}
if args.Port != 0 {
endpoint = endpoint + ":" + strconv.Itoa(args.Port)
}

if len(errors) > 0 {
return false, errors
}

if ok, err := validateMinioGateway(opts, storageName, endpoint, accessKey, secretKey, bucketName, token, isSecure, fgName); !ok {
errors = append(errors, err)
}

case "GoogleCloudStorage":

// Check access key
Expand Down
4 changes: 4 additions & 0 deletions config-tool/pkg/lib/shared/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,8 @@ type DistributedStorageArgs struct {
DefaultProvider string `default:"" validate:"" json:"default_provider,omitempty" yaml:"default_provider,omitempty"`
Providers map[string]interface{} `default:"" validate:"" json:"providers,omitempty" yaml:"providers,omitempty"`
StorageConfig map[string]interface{} `default:"" validate:"" json:"storage_config,omitempty" yaml:"storage_config,omitempty"`
// Args for STSS3Storage
STSUserAccessKey string `default:"" validate:"" json:"sts_user_access_key,omitempty" yaml:"sts_user_access_key,omitempty"`
STSUserSecretKey string `default:"" validate:"" json:"sts_user_secret_key,omitempty" yaml:"sts_user_secret_key,omitempty"`
STSRoleArn string `default:"" validate:"" json:"sts_role_arn,omitempty" yaml:"sts_role_arn,omitempty"`
}
2 changes: 2 additions & 0 deletions storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
RadosGWStorage,
RHOCSStorage,
S3Storage,
STSS3Storage,
)
from storage.cloudflarestorage import CloudFlareS3Storage
from storage.distributedstorage import DistributedStorage
Expand All @@ -31,6 +32,7 @@
"CloudFlareStorage": CloudFlareS3Storage,
"MultiCDNStorage": MultiCDNStorage,
"IBMCloudStorage": IBMCloudStorage,
"STSS3Storage": STSS3Storage,
}


Expand Down
54 changes: 53 additions & 1 deletion storage/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import boto3.session
import botocore.config
import botocore.exceptions
from botocore.credentials import create_assume_role_refresher
from botocore.credentials import DeferredRefreshableCredentials
from botocore.client import Config
from botocore.signers import CloudFrontSigner
from cachetools.func import lru_cache
Expand Down Expand Up @@ -113,6 +115,7 @@ def __init__(
bucket_name,
access_key=None,
secret_key=None,
deferred_refreshable_credentials=None,
):
super(_CloudStorage, self).__init__()

Expand All @@ -135,8 +138,12 @@ def __init__(
self._session = self._connection_class(
aws_access_key_id=self._access_key,
aws_secret_access_key=self._secret_key,
aws_session_token=self._connect_kwargs.get("aws_session_token"),
)

if deferred_refreshable_credentials:
self._session._session._credentials = deferred_refreshable_credentials

def _initialize_cloud_conn(self):
if not self._initialized:
# Low-level client. Needed to generate presigned urls
Expand Down Expand Up @@ -741,9 +748,15 @@ def __init__(
# Boto3 options (Full url including scheme anbd optionally port)
endpoint_url=None,
maximum_chunk_size_gb=None,
# STS Options
aws_session_token=None,
deferred_refreshable_credentials=None,
):
upload_params = {"ServerSideEncryption": "AES256"}
connect_kwargs = {"config": Config(signature_version="s3v4")}
connect_kwargs = {
"config": Config(signature_version="s3v4"),
"aws_session_token": aws_session_token,
}
if s3_region is not None:
connect_kwargs["region_name"] = s3_region
connect_kwargs["endpoint_url"] = "https://s3.{region}.amazonaws.com".format(
Expand All @@ -766,6 +779,7 @@ def __init__(
s3_bucket,
access_key=s3_access_key or None,
secret_key=s3_secret_key or None,
deferred_refreshable_credentials=deferred_refreshable_credentials or None,
)
chunk_size = (
maximum_chunk_size_gb if maximum_chunk_size_gb is not None else 5
Expand Down Expand Up @@ -1169,3 +1183,41 @@ def _load_private_key(self, cloudfront_privatekey_filename):
return serialization.load_pem_private_key(
key_file.read(), password=None, backend=default_backend()
)


class STSS3Storage(S3Storage):
def __init__(
self,
context,
storage_path,
s3_bucket,
sts_role_arn=None,
sts_user_access_key=None,
sts_user_secret_key=None,
s3_region=None,
endpoint_url=None,
maximum_chunk_size_gb=None,
):
sts_client = boto3.client(
"sts", aws_access_key_id=sts_user_access_key, aws_secret_access_key=sts_user_secret_key
)
assumed_role = sts_client.assume_role(RoleArn=sts_role_arn, RoleSessionName="quay")
credentials = assumed_role["Credentials"]
deferred_refreshable_credentials = DeferredRefreshableCredentials(
refresh_using=create_assume_role_refresher(
sts_client, {"RoleArn": sts_role_arn, "RoleSessionName": "quay"}
),
method="sts-assume-role",
)

connect_kwargs = {
"s3_access_key": credentials["AccessKeyId"],
"s3_secret_key": credentials["SecretAccessKey"],
"aws_session_token": credentials["SessionToken"],
"s3_region": s3_region,
"endpoint_url": endpoint_url,
"maximum_chunk_size_gb": maximum_chunk_size_gb,
"deferred_refreshable_credentials": deferred_refreshable_credentials,
}

super().__init__(context, storage_path, s3_bucket, **connect_kwargs)

0 comments on commit 233c128

Please sign in to comment.