From 6d4162343a51d1be7d875efd6a5b11deebab8fa6 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Wed, 2 Aug 2017 14:06:37 +0100 Subject: [PATCH] scripts: Add a script to push and sign manifests Also adjust the 'linuxkit/alpine' script to follow the same pattern. The new version of the script extract username/password from the credential helper (or docker) and build and 'expect' script to feed the info to 'notary'. They can be invoked by: DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE="phrase" ./push-manifest.sh ... Signed-off-by: Rolf Neugebauer --- scripts/push-manifest.sh | 108 ++++++++++++++++++++++++++++++++++ tools/alpine/push-manifest.sh | 78 +++++++++++++++++++----- 2 files changed, 171 insertions(+), 15 deletions(-) create mode 100755 scripts/push-manifest.sh diff --git a/scripts/push-manifest.sh b/scripts/push-manifest.sh new file mode 100755 index 000000000..56889ecb5 --- /dev/null +++ b/scripts/push-manifest.sh @@ -0,0 +1,108 @@ +#! /bin/sh + +set -e + +# This script pushes a multiarch manifest for packages and signs it. +# +# The TARGET must be of the form /: and this is what +# the manifest is pushed to. It assumes that there is are images of +# the form /:- already on hub. +# +# If TRUST is not set, the manifest will not be signed. +# +# For signing, DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE must be set. + +# This should all be replaced with 'docker manifest' once it lands. + +TARGET=$1 +TRUST=$2 + +REPO=$(echo "$TARGET" | cut -d':' -f1) +TAG=$(echo "$TARGET" | cut -d':' -f2) + +# Work out credentials. On macOS they are needed for manifest-tool and +# we need them for notary on all platforms. +case $(uname -s) in + Darwin) + CRED=$(echo "https://index.docker.io/v1/" | /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain.bin get) + USER=$(echo "$CRED" | jq -r '.Username') + PASS=$(echo "$CRED" | jq -r '.Secret') + MT_ARGS="--username $USER --password $PASS" + ;; + Linux) + CRED=$(cat ~/.docker/config.json | jq -r '.auths."https://index.docker.io/v1/".auth' | base64 -d -) + USER=$(echo $CRED | cut -d ':' -f 1) + PASS=$(echo $CRED | cut -d ':' -f 2-) + # manifest-tool can use docker credentials directly + MT_ARGS= + ;; + *) + echo "Unsupported platform" + exit 1 + ;; +esac + +# Push manifest list +OUT=$(manifest-tool $MT_ARGS push from-args \ + --ignore-missing \ + --platforms linux/amd64,linux/arm64 \ + --template "$TARGET"-ARCH \ + --target "$TARGET") + +echo "$OUT" +if [ -z "$TRUST" ]; then + echo "Not signing $TARGET" + exit 0 +fi + +# Extract sha256 and length from the manifest-tool output +SHA256=$(echo "$OUT" | cut -d' ' -f2 | cut -d':' -f2) +LEN=$(echo "$OUT" | cut -d' ' -f3) + +# Notary requires a PTY for username/password so use expect for that. +export NOTARY_DELEGATION_PASSPHRASE="$DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE" +NOTARY_CMD="notary -s https://notary.docker.io -d $HOME/.docker/trust addhash \ + -p docker.io/$REPO $TAG $LEN --sha256 $SHA256 \ + -r targets/releases" + +echo ' +spawn '"$NOTARY_CMD"' +set pid [exp_pid] +set timeout 60 +expect { + timeout { + puts "Expected username prompt" + exec kill -9 $pid + exit 1 + } + "username: " { + send "'"$USER"'\n" + } +} +expect { + timeout { + puts "Expected password prompt" + exec kill -9 $pid + exit 1 + } + "password: " { + send "'"$PASS"'\n" + } +} +expect { + timeout { + puts "Expected password prompt" + exec kill -9 $pid + exit 1 + } + eof { + } +} +set waitval [wait -i $spawn_id] +set exval [lindex $waitval 3] +exit $exval +' | expect -f - + +echo +echo "New signed multi-arch image: $REPO:$TAG" +echo diff --git a/tools/alpine/push-manifest.sh b/tools/alpine/push-manifest.sh index 665cf9c07..e7ae1903a 100755 --- a/tools/alpine/push-manifest.sh +++ b/tools/alpine/push-manifest.sh @@ -1,4 +1,5 @@ #! /bin/sh +set -e # This script creates a multiarch manifest for the 'linuxkit/alpine' # image, pushes and signs it. The manifest is pushed with the tag of @@ -9,6 +10,8 @@ # This script is specific to 'linuxkit/alpine'. For normal packages we # use a different scheme. # +# For signing, DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE must be set. +# # This should all be replaced with 'docker manifest' once it lands. ORG=$1 @@ -16,12 +19,13 @@ IMAGE=$2 IMG_X86_64=$(head -1 versions.x86_64 | sed 's,[#| ]*,,') IMG_ARM64=$(head -1 versions.aarch64 | sed 's,[#| ]*,,') -IMG_MANIFEST=$(echo "$IMG_X86_64" | sed 's,\-.*$,,') -IMG_TAG=$(echo "$IMG_MANIFEST" | sed 's,.*:,,') +# Extract the TAG from the x86_64 name and build the manifest target name +TAG=$(echo "$IMG_X86_64" | sed 's,\-.*$,,' | cut -d':' -f2) +TARGET="$ORG/$IMAGE:$TAG" YAML=$(mktemp) cat < "$YAML" -image: $IMG_MANIFEST +image: $TARGET manifests: - image: $IMG_ARM64 platform: @@ -33,20 +37,21 @@ manifests: os: linux EOF -# work out additional arguments. Specifically, on Darwin the hub -# credentials are stored on the keychain and we need to extract them -# from there +# Work out credentials. On macOS they are needed for manifest-tool and +# we need them for notary on all platforms. case $(uname -s) in Darwin) CRED=$(echo "https://index.docker.io/v1/" | /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain.bin get) USER=$(echo "$CRED" | jq -r '.Username') PASS=$(echo "$CRED" | jq -r '.Secret') - USERPASS="$USER\n$PASS" MT_ARGS="--username $USER --password $PASS" ;; Linux) + CRED=$(cat ~/.docker/config.json | jq -r '.auths."https://index.docker.io/v1/".auth' | base64 -d -) + USER=$(echo $CRED | cut -d ':' -f 1) + PASS=$(echo $CRED | cut -d ':' -f 2-) + # manifest-tool can use docker credentials directly MT_ARGS= - USERPASS=$(cat ~/.docker/config.json | jq -r '.auths."https://index.docker.io/v1/".auth' | base64 -d - | sed 's,:,\\n,') ;; *) echo "Unsupported platform" @@ -58,14 +63,57 @@ esac OUT=$(manifest-tool $MT_ARGS push from-spec "$YAML") rm "$YAML" echo "$OUT" + +# Extract sha256 and length from the manifest-tool output SHA256=$(echo "$OUT" | cut -d' ' -f2 | cut -d':' -f2) LEN=$(echo "$OUT" | cut -d' ' -f3) -# Sign manifest (TODO: Use $USERPASS and pass them into notary) -notary -s https://notary.docker.io \ - -d ~/.docker/trust addhash \ - -p docker.io/"$ORG"/"$IMAGE" \ - "$IMG_TAG" "$LEN" --sha256 "$SHA256" \ - -r targets/releases +NOTARY_DELEGATION_PASSPHRASE="$DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE" -echo "New multi-arch image: $ORG/$IMAGE:$IMG_TAG" +# Notary requires a PTY for username/password so use expect for that. +export NOTARY_DELEGATION_PASSPHRASE="$DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE" +NOTARY_CMD="notary -s https://notary.docker.io -d $HOME/.docker/trust addhash \ + -p docker.io/"$ORG"/"$IMAGE" $TAG $LEN --sha256 $SHA256 \ + -r targets/releases" + +echo ' +spawn '"$NOTARY_CMD"' +set pid [exp_pid] +set timeout 60 +expect { + timeout { + puts "Expected username prompt" + exec kill -9 $pid + exit 1 + } + "username: " { + send "'"$USER"'\n" + } +} +expect { + timeout { + puts "Expected password prompt" + exec kill -9 $pid + exit 1 + } + "password: " { + send "'"$PASS"'\n" + } +} +expect { + timeout { + puts "Expected password prompt" + exec kill -9 $pid + exit 1 + } + eof { + } +} +set waitval [wait -i $spawn_id] +set exval [lindex $waitval 3] +exit $exval +' | expect -f - + +echo +echo "New signed multi-arch image: $REPO:$TAG" +echo