aws ecr get-login-passwordでdocker loginするのをAWS SDKでシェルなしで実行する
対話的なECRへのdocker loginの方法
プライベートECRにdocker pushするためには、 Amazon ECR での AWS CLI の使用 - Amazon ECR に書いてあるように、
aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com
と実行してログインする必要があります。
SDKでシェルなしで実行したい
私はビルド→ログイン→プッシュを行うシェルスクリプトをリポジトリのトップに置いていましたが、シェルスクリプトは構造的にデータを扱うのが苦手ですし、込み入ったことを書くのはエラーチェックが貧弱で辛いです。
ここはAWS CDKとアセットを共有するためにもTypeScript(JavaScript)で書き直したいですね。既にAWS CDKで書かれたインフラはあるので、設定を取り込むのはTypeScriptで書けば容易です。
aws cliをTypeScriptで呼び出すのは嫌ですね。手元で動かすだけの外部入力が入らないであろう部分ですが、私はシェルに詳しくないのでシェルに纏わるセキュリティ問題を考えるのは辛いです。パイプでデータ注入とか何が起きてもおかしくありません。ならばシェルをそもそも使わなければ問題解決です。
dockerコマンドは、
dockerode
にdocker login
に値するコマンドが無かったので使っていますが、コマンド直接呼び出しのspawn
のシェル無効モードならリスクは多少軽減されていることでしょう。
SDKにget-login-passwordないじゃん
@aws-sdk/client-ecr
を眺めましたが、
SDKのAPIにget-login
とかget-login-password
はありませんでした。
じゃあaws cliはどうしてるんですか
https://github.com/aws/aws-cli/blob/5aa599949f60b6af554fd5714d7161aa272716f7/awscli/customizations/ecr.py#L89
がget-login-password
の処理本体らしいです。
やっぱりトークン取って自前で処理しているんですね。
なんか適当に真似してもうまく行きませんでした
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Error response from daemon: login attempt to https://foo.dkr.ecr.ap-northeast-1.amazonaws.com/v2/ failed with status: 400 Bad Request
みたいなエラーが出ます。
もう一回aws cliのソースコードをよく見ます
よく見たらbase64をdecodeしてuserとpasswordのpasswordだけ取得してる処理を見逃していました。
ログインする関数完成
const ecrClient = new ECRClient({ region: props.env.region });
/** ECRにdocker loginします */
async function loginToEcr(): Promise<void> {
const authorizationResult = await ecrClient.send(
new GetAuthorizationTokenCommand({})
);
const token = authorizationResult.authorizationData?.[0].authorizationToken;
if (token == null) {
throw new Error("docker login用のトークンが取得できませんでした");
}
// base64をデコードしてパスワードだけを取り出します
const decodeToken = Buffer.from(token, "base64").toString("binary");
const [, password] = decodeToken.split(":");
const loginReturns = spawnSync(
"docker",
[
"login",
"--username",
"AWS",
"--password-stdin",
`${props.env.account}.dkr.ecr.${props.env.region}.amazonaws.com`,
],
{
input: password,
}
);
// eslint-disable-next-line no-console
console.log(loginReturns.stdout.toString());
// eslint-disable-next-line no-console
console.error(loginReturns.stderr.toString());
if (loginReturns.status !== 0) {
throw new Error("ログインに失敗しました");
}
}
stdoutとstderrの処理は他は、
stdio: [process.stdin, process.stdout, process.stderr]
を指定することでストリームさせることが出来たのですが、
input
との共存が出来なかったのでconsole
使ってしまいました。多分他に良い方法があるのだと思いますが、よくわからないし別にここがストリームされてもあんまりうれしくないので妥協しました。