Skip to content

Commit

Permalink
Merge pull request containers#2040 from QiWang19/signimg
Browse files Browse the repository at this point in the history
Support podman image sign
  • Loading branch information
openshift-merge-robot authored Jan 9, 2019
2 parents 7b9d4f1 + bce22dc commit c37f731
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/podman/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (
saveCommand,
tagCommand,
trustCommand,
signCommand,
}

imageDescription = "Manage images"
Expand Down
194 changes: 194 additions & 0 deletions cmd/podman/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package main

import (
"fmt"
"io/ioutil"
"net/url"
"os"
"strconv"
"strings"

"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/trust"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)

var (
signFlags = []cli.Flag{
cli.StringFlag{
Name: "sign-by",
Usage: "Name of the signing key",
},
cli.StringFlag{
Name: "directory, d",
Usage: "Define an alternate directory to store signatures",
},
}

signDescription = "Create a signature file that can be used later to verify the image"
signCommand = cli.Command{
Name: "sign",
Usage: "Sign an image",
Description: signDescription,
Flags: sortFlags(signFlags),
Action: signCmd,
ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]",
OnUsageError: usageErrorHandler,
}
)

// SignatureStoreDir defines default directory to store signatures
const SignatureStoreDir = "/var/lib/containers/sigstore"

func signCmd(c *cli.Context) error {
args := c.Args()
if len(args) < 1 {
return errors.Errorf("at least one image name must be specified")
}
runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not create runtime")
}
defer runtime.Shutdown(false)

signby := c.String("sign-by")
if signby == "" {
return errors.Errorf("You must provide an identity")
}

var sigStoreDir string
if c.IsSet("directory") {
sigStoreDir = c.String("directory")
if _, err := os.Stat(sigStoreDir); err != nil {
return errors.Wrapf(err, "invalid directory %s", sigStoreDir)
}
}

mech, err := signature.NewGPGSigningMechanism()
if err != nil {
return errors.Wrap(err, "Error initializing GPG")
}
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
return errors.Wrap(err, "Signing is not supported")
}

systemRegistriesDirPath := trust.RegistriesDirPath(runtime.SystemContext())
registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath)
if err != nil {
return errors.Wrapf(err, "error reading registry configuration")
}

for _, signimage := range args {
srcRef, err := alltransports.ParseImageName(signimage)
if err != nil {
return errors.Wrapf(err, "error parsing image name")
}
rawSource, err := srcRef.NewImageSource(getContext(), runtime.SystemContext())
if err != nil {
return errors.Wrapf(err, "error getting image source")
}
manifest, _, err := rawSource.GetManifest(getContext(), nil)
if err != nil {
return errors.Wrapf(err, "error getting manifest")
}
dockerReference := rawSource.Reference().DockerReference()
if dockerReference == nil {
return errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
}

// create the signstore file
newImage, err := runtime.ImageRuntime().New(getContext(), signimage, runtime.GetConfig().SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, false)
if err != nil {
return errors.Wrapf(err, "error pulling image %s", signimage)
}

registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs)
if registryInfo != nil {
if sigStoreDir == "" {
sigStoreDir = registryInfo.SigStoreStaging
if sigStoreDir == "" {
sigStoreDir = registryInfo.SigStore
}
}
sigStoreDir, err = isValidSigStoreDir(sigStoreDir)
if err != nil {
return errors.Wrapf(err, "invalid signature storage %s", sigStoreDir)
}
}
if sigStoreDir == "" {
sigStoreDir = SignatureStoreDir
}

repos := newImage.RepoDigests()
if len(repos) == 0 {
logrus.Errorf("no repodigests associated with the image %s", signimage)
continue
}

// create signature
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signby)
if err != nil {
return errors.Wrapf(err, "error creating new signature")
}

sigStoreDir = fmt.Sprintf("%s/%s", sigStoreDir, strings.Replace(repos[0][strings.Index(repos[0], "/")+1:len(repos[0])], ":", "=", 1))
if err := os.MkdirAll(sigStoreDir, 0751); err != nil {
// The directory is allowed to exist
if !os.IsExist(err) {
logrus.Errorf("error creating directory %s: %s", sigStoreDir, err)
continue
}
}
sigFilename, err := getSigFilename(sigStoreDir)
if err != nil {
logrus.Errorf("error creating sigstore file: %v", err)
continue
}
err = ioutil.WriteFile(sigStoreDir+"/"+sigFilename, newSig, 0644)
if err != nil {
logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
continue
}
}
return nil
}

func getSigFilename(sigStoreDirPath string) (string, error) {
sigFileSuffix := 1
sigFiles, err := ioutil.ReadDir(sigStoreDirPath)
if err != nil {
return "", err
}
sigFilenames := make(map[string]bool)
for _, file := range sigFiles {
sigFilenames[file.Name()] = true
}
for {
sigFilename := "signature-" + strconv.Itoa(sigFileSuffix)
if _, exists := sigFilenames[sigFilename]; !exists {
return sigFilename, nil
}
sigFileSuffix++
}
}

func isValidSigStoreDir(sigStoreDir string) (string, error) {
writeURIs := map[string]bool{"file": true}
url, err := url.Parse(sigStoreDir)
if err != nil {
return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir)
}
_, exists := writeURIs[url.Scheme]
if !exists {
return sigStoreDir, errors.Errorf("Writing to %s is not supported. Use a supported scheme", sigStoreDir)
}
sigStoreDir = url.Path
return sigStoreDir, nil
}
52 changes: 52 additions & 0 deletions docs/podman-image-sign.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
% podman-image-sign(1)

# NAME
podman-image-sign- Create a signature for an image

# SYNOPSIS
**podman image sign**
[**-h**|**--help**]
[**-d**, **--directory**]
[**--sign-by**]
[ IMAGE... ]

# DESCRIPTION
**podmain image sign** will create a local signature for one or more local images that have
been pulled from a registry. The signature will be written to a directory
derived from the registry configuration files in /etc/containers/registries.d. By default, the signature will be written into /var/lib/containers/sigstore directory.

# OPTIONS
**-h** **--help**
Print usage statement.

**-d** **--directory**
Store the signatures in the specified directory. Default: /var/lib/containers/sigstore

**--sign-by**
Override the default identity of the signature.

# EXAMPLES
Sign the busybox image with the identify of [email protected] with a user's keyring and save the signature in /tmp/signatures/.

sudo podman image sign --sign-by [email protected] -d /tmp/signatures transport://privateregistry.example.com/foobar

# RELATED CONFIGURATION

The write (and read) location for signatures is defined in YAML-based
configuration files in /etc/containers/registries.d/. When you sign
an image, podman will use those configuration files to determine
where to write the signature based on the the name of the originating
registry or a default storage value unless overriden with the -d
option. For example, consider the following configuration file.

docker:
privateregistry.example.com:
sigstore: file:///var/lib/containers/sigstore

When signing an image preceeded with the registry name 'privateregistry.example.com',
the signature will be written into subdirectories of
/var/lib/containers/sigstore/privateregistry.example.com. The use of 'sigstore' also means
the signature will be 'read' from that same location on a pull-related function.

# HISTORY
November 2018, Originally compiled by Qi Wang (qiwan at redhat dot com)
3 changes: 2 additions & 1 deletion docs/podman-image.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ The image command allows you to manage images
| rm | [podman-rm(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
| save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. |
| tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container image trust policy.
| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container image trust policy. |
| sign | [podman-image-sign(1)](podman-image-sign.1.md) | Sign an image. |

## SEE ALSO
podman

0 comments on commit c37f731

Please sign in to comment.