From 9c47b79032f07e201d1daaeb4da73b252b246c9b Mon Sep 17 00:00:00 2001 From: bhoff Date: Tue, 29 Oct 2024 20:15:09 -0700 Subject: [PATCH 1/2] PLFM-8554: Configure IAM User to access S3 --- cdk.json | 2 + docker_fargate/docker_fargate_stack.py | 56 +++++++++++++++++++++----- resources/dev/config.yml | 4 +- resources/prod/config.yml | 2 +- startup.sh | 5 +++ 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/cdk.json b/cdk.json index 429deb6..a617412 100644 --- a/cdk.json +++ b/cdk.json @@ -40,6 +40,7 @@ "OwnerEmail": "synapseeng@sagebase.org" }, "STACK_NAME_PREFIX": "registry-dev", + "BUCKET_NAME": "dev-docker-registry.sagebase.org", "VPC_CIDR": "172.29.0.0/24", "ACM_CERT_ARN": "arn:aws:acm:us-east-1:449435941126:certificate/bbd59a26-ad30-4b74-ad2d-194241801b22" }, @@ -53,6 +54,7 @@ "OwnerEmail": "synapseeng@sagebase.org" }, "STACK_NAME_PREFIX": "registry-prod", + "BUCKET_NAME": "prod.docker.registry.sagebase.org", "VPC_CIDR": "172.29.1.0/24", "ACM_CERT_ARN": "arn:aws:acm:us-east-1:325565585839:certificate/7c42c355-3d69-4537-a5e6-428212db646f" } diff --git a/docker_fargate/docker_fargate_stack.py b/docker_fargate/docker_fargate_stack.py index f095377..9e19582 100644 --- a/docker_fargate/docker_fargate_stack.py +++ b/docker_fargate/docker_fargate_stack.py @@ -1,9 +1,11 @@ from aws_cdk import (Stack, aws_ec2 as ec2, + aws_s3 as s3, aws_ecs as ecs, aws_ecs_patterns as ecs_patterns, aws_elasticloadbalancingv2 as elbv2, aws_route53 as r53, + aws_iam as iam, CfnOutput, Duration, Tags) @@ -14,6 +16,8 @@ from constructs import Construct from docker_fargate.generate_ssl_cert import cert_gen +from aws_cdk.aws_ecr_assets import Platform + ACM_CERT_ARN_CONTEXT = "ACM_CERT_ARN" IMAGE_PATH_AND_TAG_CONTEXT = "IMAGE_PATH_AND_TAG" PORT_NUMBER_CONTEXT = "PORT" @@ -25,15 +29,22 @@ PRIVATE_KEY_FILE_NAME = "privatekey.pem" CERTIFICATE_FILE_NAME = "certificate.pem" -def get_secret(scope: Construct, id: str, name: str) -> str: +BUCKET_NAME = "BUCKET_NAME" + +SECRET_JSON_KEY="notification_auth" + +def get_secret(scope: Construct, id: str, name: str, secret_json_key) -> str: isecret = sm.Secret.from_secret_name_v2(scope, id, name) - return ecs.Secret.from_secrets_manager(isecret) + return ecs.Secret.from_secrets_manager(isecret, secret_json_key) # see also: https://docs.aws.amazon.com/cdk/api/v1/python/aws_cdk.aws_ecs/Secret.html # see also: ecs.Secret.from_ssm_parameter(ssm.IParameter(parameter_name=name)) def get_container_env(env: dict) -> dict: return env.get(CONTAINER_ENV_NAME, {}) +def get_bucket_name(env: dict) -> dict: + return env.get(BUCKET_NAME) + def get_certificate_arn(env: dict) -> str: return env.get(ACM_CERT_ARN_CONTEXT) @@ -43,26 +54,52 @@ def get_docker_image_name(env: dict): def get_port(env: dict) -> int: return int(env.get(PORT_NUMBER_CONTEXT)) - class DockerFargateStack(Stack): def __init__(self, scope: Construct, context: str, env: dict, vpc: ec2.Vpc, **kwargs) -> None: stack_prefix = f'{env.get(config.STACK_NAME_PREFIX_CONTEXT)}' stack_id = f'{stack_prefix}-DockerFargateStack' super().__init__(scope, stack_id, **kwargs) - + + # set up the bucket + bucket_name=get_bucket_name(env) + bucket_arn=f"arn:aws:s3:::{bucket_name}" + bucket=s3.Bucket.from_bucket_attributes(self, id=bucket_name, bucket_arn=bucket_arn) + + # + # Docker Registry cannot access the task role provided by + # ECS. The work-around is to define an IAM user, give the + # user bucket access, and pass its key pair to the container + # as environment variables. + # + + # create a user + user = iam.User(self, "DockerRegistryUser") + # create a key pair, storing the secret in Secret Manager + access_key = iam.AccessKey(self, "AccessKey", user=user) + secret_stored_name = f'{env.get(config.STACK_NAME_PREFIX_CONTEXT)}-DockerFargateStack/{context}/access_key' + secret_stored_access_key = sm.Secret(self, secret_stored_name, + secret_string_value=access_key.secret_access_key + ) + + # give the user S3 access + bucket.grant_read_write(user) + cluster = ecs.Cluster( self, f'{stack_id}-Cluster', vpc=vpc, container_insights=True) - + secret_name = f'{env.get(config.STACK_NAME_PREFIX_CONTEXT)}-DockerFargateStack/{context}/ecs' secrets = { - SECRETS_MANAGER_ENV_NAME: get_secret(self, secret_name, secret_name) + SECRET_JSON_KEY: get_secret(self, secret_name, secret_name, SECRET_JSON_KEY), + "AWS_SECRET_ACCESS_KEY": ecs.Secret.from_secrets_manager(secret_stored_access_key) } env_vars = get_container_env(env) + env_vars[BUCKET_NAME]=bucket_name + env_vars["AWS_ACCESS_KEY_ID"]=access_key.access_key_id # Build the container image for the registry # Need self-signed certificates to add to the image @@ -75,6 +112,7 @@ def __init__(self, scope: Construct, context: str, env: dict, vpc: ec2.Vpc, **kw # Now build the image, using the self-signed cert and key image = ecs.ContainerImage.from_asset( directory=".", + platform=Platform.LINUX_AMD64, # important to include when building locally, for testing build_args={"stack":context} # 'dev' or 'prod' ) @@ -90,10 +128,6 @@ def __init__(self, scope: Construct, context: str, env: dict, vpc: ec2.Vpc, **kw get_certificate_arn(env), ) - # - # for options to pass to ApplicationLoadBalancedTaskImageOptions see: - # https://docs.aws.amazon.com/cdk/api/v1/python/aws_cdk.aws_ecs_patterns/ApplicationLoadBalancedTaskImageOptions.html#aws_cdk.aws_ecs_patterns.ApplicationLoadBalancedTaskImageOptions - # load_balanced_fargate_service = ecs_patterns.ApplicationLoadBalancedFargateService( self, f'{stack_prefix}-Service', @@ -109,7 +143,7 @@ def __init__(self, scope: Construct, context: str, env: dict, vpc: ec2.Vpc, **kw target_protocol=elbv2.ApplicationProtocol.HTTPS, certificate=cert, protocol=elbv2.ApplicationProtocol.HTTPS, - ssl_policy=elbv2.SslPolicy.FORWARD_SECRECY_TLS12_RES, # Strong forward secrecy ciphers and TLS1.2 only. + ssl_policy=elbv2.SslPolicy.FORWARD_SECRECY_TLS12_RES # Strong forward secrecy ciphers and TLS1.2 only. ) scalable_target = load_balanced_fargate_service.service.auto_scale_task_count( diff --git a/resources/dev/config.yml b/resources/dev/config.yml index f5955b1..43ca5ea 100644 --- a/resources/dev/config.yml +++ b/resources/dev/config.yml @@ -21,7 +21,7 @@ storage: layerinfo: inmemory s3: region: us-east-1 - bucket: dev-docker-registry.sagebase.org + bucket: bucket_name encrypt: true secure: true v4auth: true @@ -31,7 +31,7 @@ storage: auth: token: realm: https://repo-dev.dev.sagebase.org/docker/v1/bearerToken - service: dev-docker-registry.dev.sagebase.org + service: docker-staging.dev.sagebase.org issuer: www.synapse.org rootcertbundle: /etc/docker/registry/token_signing_key_public_cert.pem diff --git a/resources/prod/config.yml b/resources/prod/config.yml index 66cbe47..e06f24d 100644 --- a/resources/prod/config.yml +++ b/resources/prod/config.yml @@ -21,7 +21,7 @@ storage: layerinfo: inmemory s3: region: us-east-1 - bucket: prod.docker.registry.sagebase.org + bucket: bucket_name encrypt: true secure: true v4auth: true diff --git a/startup.sh b/startup.sh index 38c429b..dd1fd23 100755 --- a/startup.sh +++ b/startup.sh @@ -5,6 +5,11 @@ # during ECS deployment comes from the AWS Secrets Manager. sed -i "s/notification_auth/$notification_auth/g" /etc/docker/registry/config.yml +# Inject bucket name into config.yml +# The value is taken from the environment variable, `BUCKET_NAME` which, +# during ECS deployment comes from the cdk.json file. +sed -i "s/bucket_name/$BUCKET_NAME/g" /etc/docker/registry/config.yml + # this assumed a particular start-up for the container registry # if the command changes in future versions, this will have to be updated too /entrypoint.sh /etc/docker/registry/config.yml From 286d36888a2287a809a9fc07ecd43e5d19790249 Mon Sep 17 00:00:00 2001 From: bhoff Date: Tue, 29 Oct 2024 21:09:45 -0700 Subject: [PATCH 2/2] PLFM-8554: Configure IAM User to access S3 --- docker_fargate/docker_fargate_stack.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker_fargate/docker_fargate_stack.py b/docker_fargate/docker_fargate_stack.py index 9e19582..1aee63f 100644 --- a/docker_fargate/docker_fargate_stack.py +++ b/docker_fargate/docker_fargate_stack.py @@ -60,37 +60,37 @@ def __init__(self, scope: Construct, context: str, env: dict, vpc: ec2.Vpc, **kw stack_prefix = f'{env.get(config.STACK_NAME_PREFIX_CONTEXT)}' stack_id = f'{stack_prefix}-DockerFargateStack' super().__init__(scope, stack_id, **kwargs) - + # set up the bucket bucket_name=get_bucket_name(env) bucket_arn=f"arn:aws:s3:::{bucket_name}" bucket=s3.Bucket.from_bucket_attributes(self, id=bucket_name, bucket_arn=bucket_arn) - + # # Docker Registry cannot access the task role provided by # ECS. The work-around is to define an IAM user, give the # user bucket access, and pass its key pair to the container # as environment variables. # - + # create a user user = iam.User(self, "DockerRegistryUser") # create a key pair, storing the secret in Secret Manager access_key = iam.AccessKey(self, "AccessKey", user=user) secret_stored_name = f'{env.get(config.STACK_NAME_PREFIX_CONTEXT)}-DockerFargateStack/{context}/access_key' secret_stored_access_key = sm.Secret(self, secret_stored_name, - secret_string_value=access_key.secret_access_key + secret_string_value=access_key.secret_access_key ) - + # give the user S3 access bucket.grant_read_write(user) - + cluster = ecs.Cluster( self, f'{stack_id}-Cluster', vpc=vpc, container_insights=True) - + secret_name = f'{env.get(config.STACK_NAME_PREFIX_CONTEXT)}-DockerFargateStack/{context}/ecs' secrets = { SECRET_JSON_KEY: get_secret(self, secret_name, secret_name, SECRET_JSON_KEY),