-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ecs): proposed design for ECS private registry auth
- Loading branch information
1 parent
ebb8aa1
commit 5bca5b8
Showing
1 changed file
with
146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# AWS ECS - Support for Private Registry Authentication | ||
|
||
To address issue [#1698](https://github.com/awslabs/aws-cdk/issues/1698), the ECS construct library should provide a way for customers to specify [`repositoryCredentials`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html#ECS-Type-ContainerDefinition-repositoryCredentials) on their container. | ||
|
||
Minimally, this would mean adding a new string field on `ContainerDefinition`, however this doesn't provide any added value in terms of logical grouping or resource creation. We can instead modify the existing ECS CDK construct [`ContainerImage`](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs/lib/container-image.ts) so that repository credentials are specified along with the image they're meant to access. | ||
|
||
## General approach | ||
|
||
The [`ecs.ContainerImage`](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs/lib/container-image.ts) class already includes constructs for 3 types of images: | ||
|
||
* DockerHubImage | ||
* EcrImage | ||
* AssetImage | ||
|
||
DockerHub images are assumed public, however DockerHub also provides private repositories and there's currently no way to specify credentials for them in the CDK. | ||
|
||
There's also no way to specify images hosted outside of DockerHub, AWS, or your local machine. Customers hosting their own registries or using another registry host, like Quay.io or JFrog Artifactory, would need to be able to specify both the image URI and the registry credentials in order to pull their images down for ECS tasks. | ||
|
||
Therefore, to support the use of private registries we would need to add credentials to the `DockerHubImage` type (as optional), and add a new `PrivateImage` type which could house credentials (required) and the private registry URI. | ||
|
||
We should also have a unified way to define repository credentials which can be passed to the constructors of both image types. | ||
|
||
## Code changes | ||
|
||
Given the above, we should make the following additions to support private registry authentication: | ||
|
||
1. Define `RepositoryCredentials` interface & class, add to `IContainerImage` | ||
2. Update `DockerHubImage` construct to optionally accept and set `RepositoryCreds` | ||
3. Add new `PrivateImage` class which implements `IContainerImage` and requires `RepositoryCreds` | ||
|
||
|
||
# Part 1: How to define registry credentials | ||
|
||
For extensibility, we can define a new `IRepositoryCreds` interface with a "credentialsParamater" string and a new `RepositoryCreds` class which satisfies it using specific constructs (e.g., "fromSecret"). | ||
|
||
```ts | ||
export interface IRepositoryCreds { | ||
readonly credentialsParameter: string | ||
} | ||
|
||
export class RepositoryCreds { | ||
public readonly credentialsParameter: string; | ||
private readonly secret: secretsManager.Secret; | ||
|
||
public static fromSecret(secret: secretsManager.Secret) { | ||
// sets credentialsParameter from secret.secretArn | ||
} | ||
|
||
public static withCredentialsParameter(param: string) { | ||
// sets credentialsParameter as provided string | ||
} | ||
|
||
public bind(containerDefinition: ContainerDefinition): void { | ||
if (this.secret !== null) { | ||
this.secret.grantRead(containerDefinition.taskDefinition.obtainExecutionRole()); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
The `IContainerImage` interface will be updated to include an optional `repositoryCredentials` property: | ||
```ts | ||
export interface IContainerImage { | ||
// ... | ||
readonly imageName: string; | ||
|
||
/** | ||
* NEW: The credentials required to access the image | ||
*/ | ||
readonly repositoryCredentials?: IRepositoryCreds; | ||
|
||
// ... | ||
bind(containerDefinition: ContainerDefinition): void; | ||
} | ||
``` | ||
|
||
|
||
## Part 2: Extend `DockerHubImage` class to include optional creds | ||
|
||
The `DockerHubImage` construct, which currently takes a string for image name, can be extended to take in an (optional) "repositoryCredentials" field of type `IRepositoryCreds`: | ||
```ts | ||
export class DockerHubImage implements IContainerImage { | ||
// add repositoryCredentials to constructor | ||
constructor(public readonly imageName: string, public readonly repositoryCredentials: RepositoryCreds) { | ||
} | ||
|
||
public bind(_containerDefinition: ContainerDefinition): void { | ||
// Nothing to do | ||
} | ||
} | ||
``` | ||
|
||
Example use: | ||
```ts | ||
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); | ||
|
||
const secret = secretsManager.Secret.import(stack, 'myRepoSecret', { | ||
secretArn: 'arn:aws:secretsmanager:.....' | ||
}) | ||
|
||
taskDefinition.AddContainer('myPrivateContainer', { | ||
image: ecs.ContainerImage.fromDockerHub('userx/test', ecs.RepositoryCreds.fromSecret(secret)); | ||
}); | ||
|
||
``` | ||
|
||
|
||
## Part 3: Add `fromPrivateRepository` method on `ContainerImage`, return new `PrivateImage` subclass | ||
|
||
For non-DockerHub privately hosted images, we can add a new subclass: | ||
```ts | ||
export class PrivateImage implements IContainerImage { | ||
constructor(public readonly image: string, public readonly repositoryCredentials: RepositoryCreds) { | ||
} | ||
|
||
public bind(_containerDefinition: ContainerDefinition): void { | ||
// Nothing to do | ||
} | ||
} | ||
``` | ||
|
||
This will be returned by a new API on `ContainerImage`: | ||
```ts | ||
export class ContainerImage { | ||
//... | ||
public static fromPrivateRepository(imageUri: string, creds: RepositoryCreds) { | ||
return new PrivateImage(imageUri, creds); | ||
} | ||
// ... | ||
|
||
} | ||
``` | ||
|
||
Example use: | ||
```ts | ||
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); | ||
|
||
const secret = secretsManager.Secret.import(stack, 'myRepoSecret', { | ||
secretArn: 'arn:aws:secretsmanager:.....' | ||
}) | ||
|
||
taskDefinition.AddContainer('myPrivateContainer', { | ||
image: ecs.ContainerImage.fromPrivateRepository('myrepo.net/test:latest', ecs.RepositoryCreds.fromSecret(secret)); | ||
}); | ||
|
||
``` |