eliminate manifest-push-script, jq dependency, odd logic

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher 2020-03-29 11:14:30 +03:00
parent b32eb2afd9
commit 8a81fd0ffc
3 changed files with 116 additions and 109 deletions

View File

@ -5,16 +5,32 @@ package pkglib
//go:generate ./gen
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"os"
"os/exec"
"path"
"strings"
"github.com/docker/cli/cli/config"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
)
const dctEnableEnv = "DOCKER_CONTENT_TRUST=1"
const (
dctEnableEnv = "DOCKER_CONTENT_TRUST=1"
registry = "https://index.docker.io/v1/"
notaryServer = "https://notary.docker.io"
notaryDelegationPassphraseEnvVar = "NOTARY_DELEGATION_PASSPHRASE"
notaryAuthEnvVar = "NOTARY_AUTH"
dctEnvVar = "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"
)
var platforms = []string{
"linux/amd64", "linux/arm64", "linux/s390x",
}
type dockerRunner struct {
dct bool
@ -133,18 +149,13 @@ func (dr dockerRunner) pushWithManifest(img, suffix string) error {
return err
}
var dctArg string
var trust bool
if dr.dct {
dctArg = "1"
trust = true
}
fmt.Printf("Pushing %s to manifest %s\n", img+suffix, img)
cmd := exec.Command("/bin/sh", "-c", manifestPushScript, "manifest-push-script", img, dctArg)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Debugf("Executing: %v", cmd.Args)
return cmd.Run()
return manifestPush(img, trust)
}
func (dr dockerRunner) tag(ref, tag string) error {
@ -166,3 +177,99 @@ func (dr dockerRunner) save(tgt string, refs ...string) error {
args := append([]string{"image", "save", "-o", tgt}, refs...)
return dr.command(args...)
}
func manifestPush(img string, trust bool) error {
imgParts := strings.Split(img, ":")
if len(imgParts) < 2 {
return fmt.Errorf("image not composed of <repo>:<tag> '%s'", img)
}
repo := imgParts[0]
tag := imgParts[1]
cfgFile := config.LoadDefaultConfigFile(os.Stderr)
auth, err := cfgFile.GetAuthConfig(registry)
if err != nil {
return fmt.Errorf("unable to get auth for %s: %v", registry, err)
}
args := []string{
"push",
"from-args",
"--ignore-missing",
"--platforms",
strings.Join(platforms, ","),
"--template",
fmt.Sprintf("%s-ARCH", img),
"--target",
img,
}
manTool := "manifest-tool"
// we do this separately to avoid printing username and password to debug output
log.Debugf("Executing (will add username/password): %v", append([]string{manTool}, args...))
args = append([]string{
"--username",
auth.Username,
"--password",
auth.Password,
}, args...)
cmd := exec.Command(manTool, args...)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to execute manifest-tool: %v", err)
}
if !trust {
fmt.Printf("trust disabled, not signing %s\n", img)
return nil
}
// get the image hash and the length from the manifest tool output
manToolOut := string(stdout.Bytes())
manToolOutParts := strings.Fields(manToolOut)
if len(manToolOutParts) < 3 {
return fmt.Errorf("manifest-tool output was less then required 3 parts '%s'", manToolOut)
}
hashParts := strings.Split(manToolOutParts[1], ":")
if len(hashParts) < 2 {
return fmt.Errorf("manifest-tool output hash was not in format <repo>:<hash> '%s'", manToolOutParts[1])
}
hash := hashParts[1]
length := manToolOutParts[2]
notaryAuth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", auth.Username, auth.Password)))
// run the notary command to sign
args = []string{
"-s",
notaryServer,
"-d",
path.Join(os.Getenv("HOME"), ".docker/trust"),
"addhash",
"-p",
fmt.Sprintf("docker.io/%s", repo),
tag,
length,
"--sha256",
hash,
"-r",
"targets/releases",
}
cmd = exec.Command("notary", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", notaryDelegationPassphraseEnvVar, os.Getenv(dctEnvVar)), fmt.Sprintf("%s=%s", notaryAuthEnvVar, notaryAuth))
log.Debugf("Executing: %v", cmd.Args)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to execute notary-tool: %v", err)
}
// report output
fmt.Printf("New signed multi-arch image: %s:%s\n", repo, tag)
return nil
}

View File

@ -1,11 +0,0 @@
#!/bin/sh
set -e
(
echo package pkglib
echo
echo const manifestPushScript = \`
# TODO(ijc) once everything is ported this script can move to this source directory
cat ../../../../scripts/push-manifest.sh
echo \`
) > manifest_push_script.go.new
mv manifest_push_script.go.new manifest_push_script.go

View File

@ -1,89 +0,0 @@
package pkglib
const manifestPushScript = `
#! /bin/sh
set -e
# This script pushes a multiarch manifest for packages and signs it.
#
# The TARGET must be of the form <org>/<image>:<tag> and this is what
# the manifest is pushed to. It assumes that there is are images of
# the form <org>/<image>:<tag>-<arch> 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)
# Prior to 2018-03-27 D4M used a .bin suffix on the keychain utility binary name. Support the old name for a while
if [ -f /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain.bin ]; then
CREDHELPER="/Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain.bin"
else
CREDHELPER="/Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain"
fi
;;
Linux)
CREDSTORE=$(cat ~/.docker/config.json | jq -r '.credsStore // empty')
if [ -n "$CREDSTORE" ] ; then
CREDHELPER="docker-credential-$CREDSTORE"
else
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=
fi
;;
*)
echo "Unsupported platform"
exit 1
;;
esac
if [ -n "$CREDHELPER" ] ; then
CRED=$(echo "https://index.docker.io/v1/" | "$CREDHELPER" get)
USER=$(echo "$CRED" | jq -r '.Username')
PASS=$(echo "$CRED" | jq -r '.Secret')
MT_ARGS="--username $USER --password $PASS"
fi
# Push manifest list
OUT=$(manifest-tool $MT_ARGS push from-args \
--ignore-missing \
--platforms linux/amd64,linux/arm64,linux/s390x \
--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 0.6.0 accepts authentication as base64-encoded "username:password"
export NOTARY_AUTH=$(echo "$USER:$PASS" | base64)
export NOTARY_DELEGATION_PASSPHRASE="$DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"
notary -s https://notary.docker.io -d $HOME/.docker/trust addhash \
-p docker.io/$REPO $TAG $LEN --sha256 $SHA256 \
-r targets/releases
echo
echo "New signed multi-arch image: $REPO:$TAG"
echo
`