• 作成:

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コマンドは、 dockerodedocker 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#L89get-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使ってしまいました。多分他に良い方法があるのだと思いますが、よくわからないし別にここがストリームされてもあんまりうれしくないので妥協しました。