-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
docker-credentials.ts
93 lines (78 loc) · 3.86 KB
/
docker-credentials.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { Logger } from './shell';
import { IAws } from '../aws';
export interface DockerCredentials {
readonly Username: string;
readonly Secret: string;
}
export interface DockerCredentialsConfig {
readonly version: string;
readonly domainCredentials: Record<string, DockerDomainCredentialSource>;
}
export interface DockerDomainCredentialSource {
readonly secretsManagerSecretId?: string;
readonly secretsUsernameField?: string;
readonly secretsPasswordField?: string;
readonly ecrRepository?: boolean;
readonly assumeRoleArn?: string;
}
/** Returns the presumed location of the CDK Docker credentials config file */
export function cdkCredentialsConfigFile(): string {
return process.env.CDK_DOCKER_CREDS_FILE ?? path.join((os.userInfo().homedir ?? os.homedir()).trim() || '/', '.cdk', 'cdk-docker-creds.json');
}
let _cdkCredentials: DockerCredentialsConfig | undefined;
/** Loads and parses the CDK Docker credentials configuration, if it exists. */
export function cdkCredentialsConfig(): DockerCredentialsConfig | undefined {
if (!_cdkCredentials) {
try {
_cdkCredentials = JSON.parse(fs.readFileSync(cdkCredentialsConfigFile(), { encoding: 'utf-8' })) as DockerCredentialsConfig;
} catch { }
}
return _cdkCredentials;
}
/** Fetches login credentials from the configured source (e.g., SecretsManager, ECR) */
export async function fetchDockerLoginCredentials(aws: IAws, config: DockerCredentialsConfig, endpoint: string) {
// Paranoid handling to ensure new URL() doesn't throw if the schema is missing
// For official docker registry, docker will pass https://index.docker.io/v1/
endpoint = endpoint.includes('://') ? endpoint : `https://${endpoint}`;
const domain = new URL(endpoint).hostname;
if (!Object.keys(config.domainCredentials).includes(domain) && !Object.keys(config.domainCredentials).includes(endpoint)) {
throw new Error(`unknown domain ${domain}`);
}
let domainConfig = config.domainCredentials[domain] ?? config.domainCredentials[endpoint];
if (domainConfig.secretsManagerSecretId) {
const sm = await aws.secretsManagerClient({ assumeRoleArn: domainConfig.assumeRoleArn });
const secretValue = await sm.getSecretValue({ SecretId: domainConfig.secretsManagerSecretId }).promise();
if (!secretValue.SecretString) { throw new Error(`unable to fetch SecretString from secret: ${domainConfig.secretsManagerSecretId}`); };
const secret = JSON.parse(secretValue.SecretString);
const usernameField = domainConfig.secretsUsernameField ?? 'username';
const secretField = domainConfig.secretsPasswordField ?? 'secret';
if (!secret[usernameField] || !secret[secretField]) {
throw new Error(`malformed secret string ("${usernameField}" or "${secretField}" field missing)`);
}
return { Username: secret[usernameField], Secret: secret[secretField] };
} else if (domainConfig.ecrRepository) {
const ecr = await aws.ecrClient({ assumeRoleArn: domainConfig.assumeRoleArn });
const ecrAuthData = await obtainEcrCredentials(ecr);
return { Username: ecrAuthData.username, Secret: ecrAuthData.password };
} else {
throw new Error('unknown credential type: no secret ID or ECR repo');
}
}
export async function obtainEcrCredentials(ecr: AWS.ECR, logger?: Logger) {
if (logger) { logger('Fetching ECR authorization token'); }
const authData = (await ecr.getAuthorizationToken({ }).promise()).authorizationData || [];
if (authData.length === 0) {
throw new Error('No authorization data received from ECR');
}
const token = Buffer.from(authData[0].authorizationToken!, 'base64').toString('ascii');
const [username, password] = token.split(':');
if (!username || !password) { throw new Error('unexpected ECR authData format'); }
return {
username,
password,
endpoint: authData[0].proxyEndpoint!,
};
}