mirror of
https://github.com/containers/skopeo.git
synced 2025-06-26 14:52:36 +00:00
Add standalone-sign and standalone-verify commands
This commit is contained in:
parent
69d5a131c9
commit
03f6cb89e6
@ -3,6 +3,7 @@ FROM fedora
|
|||||||
RUN dnf -y update && dnf install -y make git golang golang-github-cpuguy83-go-md2man \
|
RUN dnf -y update && dnf install -y make git golang golang-github-cpuguy83-go-md2man \
|
||||||
# gpgme bindings deps
|
# gpgme bindings deps
|
||||||
libassuan-devel gpgme-devel \
|
libassuan-devel gpgme-devel \
|
||||||
|
gnupg \
|
||||||
# registry v1 deps
|
# registry v1 deps
|
||||||
xz-devel \
|
xz-devel \
|
||||||
python-devel \
|
python-devel \
|
||||||
|
3
Makefile
3
Makefile
@ -53,8 +53,9 @@ shell: build-container
|
|||||||
|
|
||||||
check: validate test-unit test-integration
|
check: validate test-unit test-integration
|
||||||
|
|
||||||
|
# The tests can run out of entropy and block in containers, so replace /dev/random.
|
||||||
test-integration: build-container
|
test-integration: build-container
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh test-integration
|
$(DOCKER_RUN_DOCKER) bash -c 'rm -f /dev/random; ln -sf /dev/urandom /dev/random; hack/make.sh test-integration'
|
||||||
|
|
||||||
test-unit: build-container
|
test-unit: build-container
|
||||||
# Just call (make test unit-local) here instead of worrying about environment differences, e.g. GO15VENDOREXPERIMENT.
|
# Just call (make test unit-local) here instead of worrying about environment differences, e.g. GO15VENDOREXPERIMENT.
|
||||||
|
@ -54,6 +54,8 @@ func main() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
inspectCmd,
|
inspectCmd,
|
||||||
layersCmd,
|
layersCmd,
|
||||||
|
standaloneSignCmd,
|
||||||
|
standaloneVerifyCmd,
|
||||||
}
|
}
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
|
88
cmd/skopeo/signing.go
Normal file
88
cmd/skopeo/signing.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/projectatomic/skopeo/signature"
|
||||||
|
)
|
||||||
|
|
||||||
|
func standaloneSign(context *cli.Context) {
|
||||||
|
outputFile := context.String("output")
|
||||||
|
if len(context.Args()) != 3 || outputFile == "" {
|
||||||
|
logrus.Fatal("Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature")
|
||||||
|
}
|
||||||
|
manifestPath := context.Args()[0]
|
||||||
|
dockerReference := context.Args()[1]
|
||||||
|
fingerprint := context.Args()[2]
|
||||||
|
|
||||||
|
manifest, err := ioutil.ReadFile(manifestPath)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error reading %s: %s", manifestPath, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
mech, err := signature.NewGPGSigningMechanism()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error initializing GPG: %s", err.Error())
|
||||||
|
}
|
||||||
|
signature, err := signature.SignDockerManifest(manifest, dockerReference, mech, fingerprint)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error creating signature: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(outputFile, signature, 0644); err != nil {
|
||||||
|
logrus.Fatalf("Error writing signature to %s: %s", outputFile, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Document in the man page
|
||||||
|
var standaloneSignCmd = cli.Command{
|
||||||
|
Name: "standalone-sign",
|
||||||
|
Usage: "Create a signature using local files",
|
||||||
|
Action: standaloneSign,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "output, o",
|
||||||
|
Usage: "output signature file name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func standaloneVerify(context *cli.Context) {
|
||||||
|
if len(context.Args()) != 4 {
|
||||||
|
logrus.Fatal("Usage: skopeo standalone-verify manifest docker-reference key-fingerprint signature")
|
||||||
|
}
|
||||||
|
manifestPath := context.Args()[0]
|
||||||
|
expectedDockerReference := context.Args()[1]
|
||||||
|
expectedFingerprint := context.Args()[2]
|
||||||
|
signaturePath := context.Args()[3]
|
||||||
|
|
||||||
|
unverifiedManifest, err := ioutil.ReadFile(manifestPath)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error reading manifest from %s: %s", signaturePath, err.Error())
|
||||||
|
}
|
||||||
|
unverifiedSignature, err := ioutil.ReadFile(signaturePath)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error reading signature from %s: %s", signaturePath, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
mech, err := signature.NewGPGSigningMechanism()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error initializing GPG: %s", err.Error())
|
||||||
|
}
|
||||||
|
sig, err := signature.VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest, expectedDockerReference, mech, expectedFingerprint)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error verifying signature: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Signature verified, digest %s\n", sig.DockerManifestDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Document in the man page
|
||||||
|
var standaloneVerifyCmd = cli.Command{
|
||||||
|
Name: "standalone-verify",
|
||||||
|
Usage: "Verify a signature using local files",
|
||||||
|
Action: standaloneVerify,
|
||||||
|
}
|
117
integration/signing_test.go
Normal file
117
integration/signing_test.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
"github.com/projectatomic/skopeo/signature/fixtures"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gpgBinary = "gpg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
check.Suite(&SigningSuite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type SigningSuite struct {
|
||||||
|
gpgHome string
|
||||||
|
fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFingerprint(lineBytes []byte) (string, error) {
|
||||||
|
lines := string(lineBytes)
|
||||||
|
for _, line := range strings.Split(lines, "\n") {
|
||||||
|
fields := strings.Split(line, ":")
|
||||||
|
if len(fields) >= 10 && fields[0] == "fpr" {
|
||||||
|
return fields[9], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("No fingerprint found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeAndLogOutput takes (f, err) from an exec.*Pipe(), and causes all output to it to be logged to c.
|
||||||
|
func ConsumeAndLogOutput(c *check.C, id string, f io.ReadCloser, err error) {
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
f.Close()
|
||||||
|
c.Logf("Output %s: Closed", id)
|
||||||
|
}()
|
||||||
|
buf := make([]byte, 0, 1024)
|
||||||
|
for {
|
||||||
|
c.Logf("Output %s: waiting", id)
|
||||||
|
n, err := f.Read(buf)
|
||||||
|
c.Logf("Output %s: got %d,%#v: %#v", id, n, err, buf[:n])
|
||||||
|
if n <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigningSuite) SetUpTest(c *check.C) {
|
||||||
|
_, err := exec.LookPath(skopeoBinary)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
_, err = exec.LookPath(skopeoBinary)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
s.gpgHome, err = ioutil.TempDir("", "skopeo-gpg")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
os.Setenv("GNUPGHOME", s.gpgHome)
|
||||||
|
|
||||||
|
cmd := exec.Command(gpgBinary, "--homedir", s.gpgHome, "--batch", "--gen-key")
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
ConsumeAndLogOutput(c, "gen-key stdout", stdout, err)
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
ConsumeAndLogOutput(c, "gen-key stderr", stderr, err)
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
_, err = stdin.Write([]byte("Key-Type: RSA\nName-Real: Testing user\n%commit\n"))
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
err = stdin.Close()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
err = cmd.Wait()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
lines, err := exec.Command(gpgBinary, "--homedir", s.gpgHome, "--with-colons", "--no-permission-warning", "--fingerprint").Output()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
s.fingerprint, err = findFingerprint(lines)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigningSuite) TearDownTest(c *check.C) {
|
||||||
|
if s.gpgHome != "" {
|
||||||
|
err := os.RemoveAll(s.gpgHome)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
}
|
||||||
|
s.gpgHome = ""
|
||||||
|
|
||||||
|
os.Unsetenv("GNUPGHOME")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigningSuite) TestSignVerifySmoke(c *check.C) {
|
||||||
|
manifestPath := "../signature/fixtures/image.manifest.json"
|
||||||
|
dockerReference := "testing/smoketest"
|
||||||
|
|
||||||
|
sigOutput, err := ioutil.TempFile("", "sig")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer os.Remove(sigOutput.Name())
|
||||||
|
out, err := exec.Command(skopeoBinary, "standalone-sign", "-o", sigOutput.Name(),
|
||||||
|
manifestPath, dockerReference, s.fingerprint).CombinedOutput()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(string(out), check.Equals, "")
|
||||||
|
|
||||||
|
out, err = exec.Command(skopeoBinary, "standalone-verify", manifestPath,
|
||||||
|
dockerReference, s.fingerprint, sigOutput.Name()).CombinedOutput()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(string(out), check.Equals, "Signature verified, digest "+fixtures.TestImageManifestDigest+"\n")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user