├── .gitignore ├── .github └── FUNDING.yml ├── Makefile ├── lambda ├── authorizer.js └── api.js ├── api.openapi.yml ├── docs ├── launch-stack.svg └── aws-ecr-public.svg ├── README.md ├── serverless.template.yml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | dist 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: monken 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEMPLATE ?= dist/aws-ecr-public.template.json 2 | STACK_NAME ?= aws-ecr-public 3 | VALIDATION_DOMAIN ?= "" 4 | DOMAIN_NAME ?= "" 5 | VALIDATION_METHOD ?= "EMAIL" 6 | 7 | VERSION = 1.2.0 8 | 9 | build: serverless.template.yml api.openapi.yml lambda/* 10 | mkdir -p dist 11 | cfn-include -t -m serverless.template.yml > $(TEMPLATE) 12 | 13 | test: build 14 | aws cloudformation deploy \ 15 | --template-file $(TEMPLATE) \ 16 | --stack-name $(STACK_NAME) \ 17 | --capabilities CAPABILITY_IAM \ 18 | --parameter-overrides \ 19 | ValidationDomain=$(VALIDATION_DOMAIN) \ 20 | DomainName=$(DOMAIN_NAME) \ 21 | ValidationMethod=$(VALIDATION_METHOD) \ 22 | AuthBasicUsername=foo \ 23 | AuthBasicPassword=bar \ 24 | Authorizer=BASIC 25 | 26 | publish: build 27 | aws s3 cp --acl public-read dist/aws-ecr-public.template.json s3://monken/aws-ecr-public/v$(VERSION)/template.json 28 | 29 | clean: 30 | rm -rf dist 31 | aws cloudformation delete-stack --stack-name $(STACK_NAME) 32 | -------------------------------------------------------------------------------- /lambda/authorizer.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const assert = require('assert').strict; 3 | 4 | const { ADO_ORG, METHOD, BASIC_USER, BASIC_PASSWORD } = process.env; 5 | 6 | const UNAUTHORIZED = new Error('Unauthorized'); 7 | 8 | function basicAuth(user, password) { 9 | assert(user === BASIC_USER && password === BASIC_PASSWORD, UNAUTHORIZED); 10 | } 11 | 12 | function azureDevOps(user, password) { 13 | if (user !== 'ADO' || !ADO_ORG) throw UNAUTHORIZED; 14 | 15 | return new Promise((resolve, reject) => https.get(`https://dev.azure.com/${ADO_ORG}/_apis/projects`, { 16 | headers: { 17 | Authorization: `Bearer ${password}`, 18 | }, 19 | }, async (res) => { 20 | // empty buffer 21 | for await (const chunk of res) {} 22 | if (res.statusCode === 200) resolve(); 23 | else reject(UNAUTHORIZED); 24 | })); 25 | } 26 | 27 | exports.handler = async ({ authorizationToken }) => { 28 | const [type, encoded] = authorizationToken.split(/\s+/, 2); 29 | const [user, password] = Buffer.from(encoded, 'base64').toString().split(':', 2); 30 | 31 | if (METHOD === 'AZURE_DEVOPS') { 32 | await azureDevOps(user, password); 33 | } else if (METHOD === 'BASIC') { 34 | await basicAuth(user, password); 35 | } else throw UNAUTHORIZED; 36 | 37 | return { 38 | policyDocument: { 39 | Version: '2012-10-17', 40 | Statement: [{ 41 | Effect: 'Allow', 42 | Resource: '*', 43 | Action: 'execute-api:Invoke', 44 | }] 45 | }, 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /api.openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: '3.0.1' 2 | info: 3 | version: '1' 4 | title: !Ref AWS::StackName 5 | 6 | x-default-request: &request 7 | security: 8 | Fn::If: 9 | - HasAuthorizer 10 | - [basic: []] 11 | - [] 12 | responses: 13 | 200: 14 | description: "200 response" 15 | content: {} 16 | x-amazon-apigateway-integration: 17 | responses: 18 | default: 19 | statusCode: 200 20 | uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiFunction.Arn}/invocations 21 | passthroughBehavior: when_no_match 22 | httpMethod: POST 23 | type: aws_proxy 24 | 25 | x-amazon-apigateway-gateway-responses: 26 | UNAUTHORIZED: 27 | statusCode: 401 28 | responseParameters: 29 | gatewayresponse.header.WWW-Authenticate: "'Basic'" 30 | responseTemplates: 31 | application/json: 32 | Fn::Stringify: 33 | errors: 34 | - code: UNAUTHORIZED 35 | 36 | paths: 37 | /: 38 | get: 39 | <<: *request 40 | x-amazon-apigateway-integration: 41 | responses: 42 | default: 43 | statusCode: 200 44 | passthroughBehavior: when_no_match 45 | requestTemplates: 46 | application/json: "{\"statusCode\": 200}" 47 | type: mock 48 | 49 | /{proxy+}: 50 | get: 51 | <<: *request 52 | head: 53 | <<: *request 54 | 55 | components: 56 | securitySchemes: 57 | Fn::If: 58 | - HasAuthorizer 59 | - basic: 60 | type: apiKey 61 | name: Authorization 62 | in: header 63 | x-amazon-apigateway-authtype: custom 64 | x-amazon-apigateway-authorizer: 65 | authorizerUri: 66 | Fn::Sub: 67 | - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizerLambdaArn}/invocations 68 | - AuthorizerLambdaArn: !If [ HasCustomAuthorizer, !Ref AuthCustomLambdaArn, !GetAtt AuthorizerFunction.Arn ] 69 | authorizerResultTtlInSeconds: 300 70 | type: token 71 | - !Ref AWS::NoValue 72 | -------------------------------------------------------------------------------- /lambda/api.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | const ecr = new AWS.ECR(); 3 | 4 | const handledErrors = { 5 | AccessDeniedException: { 6 | statusCode: 403, 7 | error: { 8 | code: "DENIED", 9 | message: "requested access to the resource is denied" 10 | } 11 | }, 12 | RepositoryNotFoundException: { 13 | statusCode: 404, 14 | error: { 15 | code: "NAME_UNKNOWN", 16 | message: "repository name not known to registry" 17 | } 18 | } 19 | }; 20 | 21 | const actions = { 22 | manifests: { 23 | head: async (name, ref) => { 24 | const res = await actions.manifests.get(name, ref); 25 | return { 26 | ...res, 27 | body: "" 28 | }; 29 | }, 30 | get: async (name, ref) => { 31 | // matching is done according to https://docs.docker.com/registry/spec/api/#content-digests 32 | // however the algorithm regex seems broken in their docs 33 | // matching to a word should be enough to catch it 34 | if (ref.match(/^\w+:[A-Fa-f0-9]+$/)) { 35 | selectorObj = { imageDigest: ref }; 36 | } else { 37 | selectorObj = { imageTag: ref }; 38 | } 39 | 40 | const { images } = await ecr 41 | .batchGetImage({ 42 | imageIds: [selectorObj], 43 | repositoryName: name 44 | }) 45 | .promise(); 46 | 47 | const image = images[0]; 48 | if (!image) return { statusCode: 404, body: "{}" }; 49 | const manifest = JSON.parse(image.imageManifest); 50 | 51 | return { 52 | statusCode: 200, 53 | body: image.imageManifest, 54 | multiValueHeaders: { 55 | "Content-Type": [manifest.mediaType] 56 | } 57 | }; 58 | } 59 | }, 60 | blobs: { 61 | get: async (name, digest) => { 62 | const url = await ecr 63 | .getDownloadUrlForLayer({ 64 | repositoryName: name, 65 | layerDigest: digest 66 | }) 67 | .promise(); 68 | 69 | return { 70 | statusCode: 307, 71 | headers: { 72 | Location: url.downloadUrl, 73 | "Docker-Content-Digest": url.layerDigest 74 | } 75 | }; 76 | } 77 | } 78 | }; 79 | 80 | exports.handler = async event => { 81 | console.log(JSON.stringify(event)); 82 | const context = event.requestContext; 83 | const segments = context.path.split(/\//); 84 | const [action, reference] = segments.slice(segments.length - 2); 85 | const name = segments.slice(2, segments.length - 2).join("/"); 86 | 87 | let fn; 88 | 89 | try { 90 | fn = actions[action][context.httpMethod.toLowerCase()]; 91 | } catch (e) { 92 | return { statusCode: 404 }; 93 | } 94 | 95 | return fn(name, reference).catch(e => { 96 | if (Object.keys(handledErrors).includes(e.name)) { 97 | const errResponse = handledErrors[e.name]; 98 | return { 99 | statusCode: errResponse.statusCode, 100 | body: JSON.stringify({ 101 | errors: [errResponse.error] 102 | }) 103 | }; 104 | } else { 105 | console.log(e.toString ? e.toString() : e); 106 | throw e; 107 | } 108 | }); 109 | }; 110 | -------------------------------------------------------------------------------- /docs/launch-stack.svg: -------------------------------------------------------------------------------- 1 | Launch Stack -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Public AWS Elastic Container Registry 2 | 3 | Host any Elastic Container Registry (ECR) publicly on a custom domain using this serverless proxy. 4 | 5 | Give it a spin: 6 | 7 | ```bash 8 | # pull a container from a registry named nginx with no authentication 9 | docker pull v3iomfy255.execute-api.us-east-2.amazonaws.com/nginx:alpine 10 | ``` 11 | 12 | ## Solution Overview 13 | 14 | ECR doesn't support [public registries](https://aws.amazon.com/ecr/faqs/). Instead, the docker client needs to authenticate with ECR using AWS IAM credentials which requires the AWS CLI or an SDK that can generate those credentials. 15 | 16 | If you would like to make your registries publicly available then this solution can help. It deploys an API Gateway and a Lambda function that act as a proxy for AWS ECR. [Custom authentication](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html) can easily be added in the API Gateway. Roll your own JWT-based authentication or whatever you desire. Additionally, you can configure the [API Gateway to be private](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-apis.html) and thus limit access to docker clients within your VPC. 17 | 18 | ![diagram](docs/aws-ecr-public.svg) 19 | 20 | ## Deploy 21 | 22 | [![launch](docs/launch-stack.svg)](https://console.aws.amazon.com/cloudformation/home?#/stacks/create/review?filter=active&templateURL=https%3A%2F%2Fs3.us-east-2.amazonaws.com%2Fmonken%2Faws-ecr-public%2Fv1.2.0%2Ftemplate.json&stackName=ecr-public) 23 | 24 | [Download Template](https://s3.us-east-2.amazonaws.com/monken/aws-ecr-public/v1.2.0/template.json) 25 | 26 | 27 | ### Template Parameters 28 | 29 | | Parameter | Required | Description | 30 | | -- | -- | -- | 31 | | DomainName | No | If provided an ACM Certificate and API Domain Name will be created 32 | | ValidationDomain | No | Overwrite default Validation Domain for ACM Certificate 33 | | ValidationMethod | Yes, default to `EMAIL` | Allow you to use `DNS` instead of `EMAIL` for Certificate validation 34 | | Authorizer | No, defaults to `NONE` | Valid values are `NONE`, `BASIC`, `AZURE_DEVOPS` or `CUSTOM` 35 | | AuthBasicUsername | If Authorizer is `BASIC` | Username for Basic authentication 36 | | AuthBasicPassword | If Authorizer is `BASIC` | Password for Basic authentication 37 | | AuthAzureDevOpsOrg | If Authorizer is `AZURE_DEVOPS` | Organization name in Azure Devops 38 | | AuthCustomLambdaArn | If Authorizer is `CUSTOM` | ARN of your custom Lambda authorizer 39 | 40 | ## Authorizers 41 | 42 | This template ships with support for Basic authentication, Azure Devops (using system or access token) and custom Lambda. 43 | 44 | Azure DevOps Pipeline example: 45 | 46 | ```yaml 47 | # username must be ADO 48 | steps: 49 | - script: | 50 | echo $TOKEN | docker login --username ADO --password-stdin example.execute-api.us-east-2.amazonaws.com 51 | docker pull example.execute-api.us-east-2.amazonaws.com/nginx:latest 52 | env: 53 | TOKEN: $(System.AccessToken) 54 | ``` 55 | 56 | ## FAQ 57 | 58 | ### How can I host this proxy on a custom domain? 59 | 60 | Simply provide the `DomainName` parameter when you create the stack. This will create an ACM certificate and API Domain Name resource. The Regional Domain Name and Hosted Zone ID can be found in the outputs tab of the stack. You will need those to create the DNS record in Route 53 (or similar DNS service). 61 | 62 | For Route 53, open your hosted zone, create a **New Record Set**, enter the domain name, set **Alias** to **Yes** and paste the `RegionalDomainName` in the **Alias Target** field. 63 | 64 | ### How can I restrict access to certain registries? 65 | 66 | By default all registries in the account and region will be made publicly available. To limit the number of publicly available repositores, attach a custom policy to the Lambda execution role (look for `${AWS::StackName}-LambdaRole-*`). The following policy will restrict public access to the `myapp` repository (make sure you replace the variables with your region and account id). 67 | 68 | ```json 69 | { 70 | "Version": "2012-10-17", 71 | "Statement": [ 72 | { 73 | "Action": [ 74 | "ecr:GetDownloadUrlForLayer", 75 | "ecr:BatchGetImage" 76 | ], 77 | "NotResource": [ 78 | "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/myapp" 79 | ], 80 | "Effect": "Deny" 81 | } 82 | ] 83 | } 84 | ``` 85 | 86 | ## Develop 87 | 88 | ``` 89 | npm install --global cfn-include 90 | make build 91 | make test # create/update CloudFormation stack 92 | make clean # delete CloudFormation stack 93 | ``` 94 | 95 | ## In the works 96 | 97 | * Cross-account and cross-region access to registries 98 | * Tag-based permissions 99 | * Implement additional endpoints for listing images and tags 100 | -------------------------------------------------------------------------------- /serverless.template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Description: https://github.com/monken/aws-ecr-public 5 | 6 | Metadata: 7 | AWS::CloudFormation::Interface: 8 | ParameterGroups: 9 | - Label: { default: Configuration } 10 | Parameters: 11 | - ValidationDomain 12 | - DomainName 13 | - ValidationMethod 14 | - Authorizer 15 | - Label: { default: Basic Authentication } 16 | Parameters: 17 | - AuthBasicUsername 18 | - AuthBasicPassword 19 | - Label: { default: Azure DevOps Authentication } 20 | Parameters: 21 | - AuthAzureDevOpsOrg 22 | - Label: { default: Custom Lambda Authentication } 23 | Parameters: 24 | - AuthCustomLambdaArn 25 | ParameterLabels: 26 | ValidationDomain: { default: Validation Domain } 27 | DomainName: { default: Domain Name } 28 | ValidationMethod: { default: Validation Method } 29 | AuthBasicUsername: { default: Username } 30 | AuthBasicPassword: { default: Password } 31 | AuthAzureDevOpsOrg: { default: DevOps Organization Name } 32 | AuthCustomLambdaArn: { default: Lambda ARN } 33 | 34 | 35 | Parameters: 36 | ValidationDomain: 37 | Type: String 38 | Default: '' 39 | Description: Overwrite default validation domain for ACM certificate. 40 | DomainName: 41 | Type: String 42 | Default: '' 43 | Description: If provided, an ACM Certificate and API Domain Name will be created. 44 | ValidationMethod: 45 | Type: String 46 | Default: EMAIL 47 | AllowedValues: [EMAIL, DNS] 48 | Description: Choose a validation method for the ACM certificate, only relevant if a Domain Name was provided. 49 | 50 | Authorizer: 51 | Type: String 52 | Default: NONE 53 | AllowedValues: [NONE, BASIC, AZURE_DEVOPS, CUSTOM] 54 | Description: Add an authorizer to your registry. Make sure to provide details below. 55 | 56 | AuthBasicUsername: 57 | Type: String 58 | Default: '' 59 | 60 | AuthBasicPassword: 61 | Type: String 62 | Default: '' 63 | NoEcho: true 64 | 65 | AuthAzureDevOpsOrg: 66 | Type: String 67 | Default: '' 68 | Description: Provide the name of the Azure DevOps organization that is allowed to authenticate. The username is ADO, the password is the System.AccessToken or user token. 69 | 70 | AuthCustomLambdaArn: 71 | Type: String 72 | Default: '' 73 | Description: Provide the full ARN to a custom authorizer function. Permissions will be added automatically. 74 | 75 | Conditions: 76 | HasValidationDomain: !Not [ !Equals [ !Ref ValidationDomain, '' ] ] 77 | HasDomainName: !Not [ !Equals [ !Ref DomainName, '' ] ] 78 | HasAuthorizer: !Not [ !Equals [ !Ref Authorizer, NONE ] ] 79 | HasCustomAuthorizer: !Equals [ !Ref Authorizer, CUSTOM ] 80 | 81 | Resources: 82 | CloudWatchRole: 83 | Type: AWS::IAM::Role 84 | Properties: 85 | AssumeRolePolicyDocument: 86 | Version: '2012-10-17' 87 | Statement: 88 | - Effect: Allow 89 | Principal: 90 | Service: apigateway.amazonaws.com 91 | Action: sts:AssumeRole 92 | ManagedPolicyArns: 93 | - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs 94 | 95 | ApiGatewayRole: 96 | Type: AWS::ApiGateway::Account 97 | DependsOn: Api 98 | Properties: 99 | CloudWatchRoleArn: !GetAtt CloudWatchRole.Arn 100 | 101 | ApiAccessLogGroup: 102 | Type: AWS::Logs::LogGroup 103 | Properties: 104 | LogGroupName: !Sub /${AWS::StackName}/accesslog 105 | RetentionInDays: 7 106 | 107 | Api: 108 | Type: AWS::Serverless::Api 109 | Properties: 110 | StageName: v2 111 | DefinitionBody: !Include api.openapi.yml 112 | EndpointConfiguration: REGIONAL 113 | MinimumCompressionSize: 0 114 | TracingEnabled: true 115 | OpenApiVersion: '3.0.1' 116 | # trigger a redeployment of the Authorizer changes 117 | BinaryMediaTypes: [!Ref Authorizer] 118 | AccessLogSetting: 119 | DestinationArn: !GetAtt ApiAccessLogGroup.Arn 120 | Format: 121 | # https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference 122 | Fn::Stringify: 123 | requestId: $context.requestId 124 | ip: $context.identity.sourceIp 125 | caller: $context.identity.caller 126 | userArn: $context.identity.userArn 127 | requestTimeEpoch: $context.requestTimeEpoch 128 | httpMethod: $context.httpMethod 129 | resourcePath: $context.resourcePath 130 | path: $context.path 131 | status: $context.status 132 | protocol: $context.protocol 133 | responseLength: $context.responseLength 134 | responseLatency: $context.responseLatency 135 | integrationLatency: $context.integrationLatency 136 | error: $context.error 137 | 138 | Certificate: 139 | Type: AWS::CertificateManager::Certificate 140 | Condition: HasDomainName 141 | Properties: 142 | DomainName: !Ref DomainName 143 | ValidationMethod: !Ref ValidationMethod 144 | DomainValidationOptions: 145 | Fn::If: 146 | - HasValidationDomain 147 | - - DomainName: !Ref DomainName 148 | ValidationDomain: !Ref ValidationDomain 149 | - !Ref AWS::NoValue 150 | 151 | ApiDomainName: 152 | Type: AWS::ApiGateway::DomainName 153 | Condition: HasDomainName 154 | Properties: 155 | RegionalCertificateArn: !Ref Certificate 156 | DomainName: !Ref DomainName 157 | EndpointConfiguration: 158 | Types: [REGIONAL] 159 | 160 | BasePathMapping: 161 | Type: AWS::ApiGateway::BasePathMapping 162 | Condition: HasDomainName 163 | DependsOn: Apiv2Stage 164 | Properties: 165 | BasePath: v2 166 | DomainName: !Ref ApiDomainName 167 | RestApiId: !Ref Api 168 | Stage: v2 169 | 170 | ApiFunction: 171 | Type: AWS::Serverless::Function 172 | Properties: 173 | Handler: index.handler 174 | Runtime: nodejs12.x 175 | InlineCode: !Include { type: string, location: lambda/api.js } 176 | Timeout: 30 177 | Events: 178 | Api: 179 | Type: Api 180 | Properties: 181 | Path: /{proxy+} 182 | Method: ANY 183 | RestApiId: !Ref Api 184 | Policies: 185 | - Version: '2012-10-17' 186 | Statement: 187 | - Effect: Allow 188 | Action: 189 | - ecr:GetDownloadUrlForLayer 190 | - ecr:BatchGetImage 191 | Resource: '*' 192 | 193 | AuthorizerFunction: 194 | Type: AWS::Serverless::Function 195 | Condition: HasAuthorizer 196 | Properties: 197 | Handler: index.handler 198 | Runtime: nodejs12.x 199 | InlineCode: !Include { type: string, location: lambda/authorizer.js } 200 | Timeout: 30 201 | Environment: 202 | Variables: 203 | ADO_ORG: !Ref AuthAzureDevOpsOrg 204 | BASIC_USER: !Ref AuthBasicUsername 205 | BASIC_PASSWORD: !Ref AuthBasicPassword 206 | METHOD: !Ref Authorizer 207 | 208 | AuthorizerPermission: 209 | Type: AWS::Lambda::Permission 210 | Condition: HasAuthorizer 211 | Properties: 212 | Action: lambda:InvokeFunction 213 | FunctionName: !If [ HasCustomAuthorizer, !Ref AuthCustomLambdaArn, !GetAtt AuthorizerFunction.Arn ] 214 | Principal: apigateway.amazonaws.com 215 | SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${Api}/authorizers/* 216 | 217 | 218 | Outputs: 219 | ApiDomainName: 220 | Value: !Sub ${Api}.execute-api.${AWS::Region}.amazonaws.com 221 | Export: 222 | Name: !Sub ${AWS::StackName}:ApiDomainName 223 | RegionalDomainName: 224 | Value: !If [ HasDomainName, !GetAtt ApiDomainName.RegionalDomainName, 'null'] 225 | Export: 226 | Name: !Sub ${AWS::StackName}:RegionalDomainName 227 | RegionalHostedZoneId: 228 | Value: !If [ HasDomainName, !GetAtt ApiDomainName.RegionalHostedZoneId, 'null'] 229 | Export: 230 | Name: !Sub ${AWS::StackName}:RegionalHostedZoneId 231 | LambdaExecutionRoleName: 232 | Value: !Ref ApiFunctionRole 233 | Export: 234 | Name: !Sub ${AWS::StackName}:LambdaExecutionRoleName 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/aws-ecr-public.svg: -------------------------------------------------------------------------------- 1 | 2 |
CloudFormation Stack
CloudFormation Stack
AWS Cloud
AWS Cloud
Elastic Container Registry
Elastic Container Registry
Download layer from S3
Download layer from S3
AWS API
AWS API<br>
Generate
presigned
URL
[Not supported by viewer]
Registry
Registry
docker pull myregistry.com/myapp:latest
1. GET  /v2/
2. GET /v2/{name}/manifests/{reference}
3. GET /v2/{name}/blobs/{digest}
[Not supported by viewer]
ECR Service Bucket
ECR Service Bucket
IAM Role
IAM Role
API Gateway
API Gateway
Logs
Logs
--------------------------------------------------------------------------------