Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support podman image sign #2040

Merged
merged 1 commit into from
Jan 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we default this to the current user? Can we figure out the default signature of the current user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atomic set this default in a config file /etc/atomic.conf. Does it need to be added to some config file here?
the value of sign-by flag should be a keyring generated using gpg, rather than $USER, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a default signer in gpg?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably be username

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gpgconf --list-options gpg | grep default-key

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also found the gpg command to generate the key pair without user interaction. But cannot avoid user interaction if I want to execute podman image sign command in the test file 🤔

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)
}
QiWang19 marked this conversation as resolved.
Show resolved Hide resolved
}

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a hidden registriesdirpath option to do tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I need! But still wondering how to write the test for this. What sign-by value should I put there, or I may need to run gpg command to generate some key first. 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think you will need to do this. Was their any atomic test for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I can't find test for sign in atomic

Copy link
Contributor Author

@QiWang19 QiWang19 Jan 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I can't find atomic test for this

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, let's use filepath.Join

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)
QiWang19 marked this conversation as resolved.
Show resolved Hide resolved
continue
}
}
sigFilename, err := getSigFilename(sigStoreDir)
if err != nil {
logrus.Errorf("error creating sigstore file: %v", err)
continue
}
err = ioutil.WriteFile(sigStoreDir+"/"+sigFilename, newSig, 0644)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use filepath.Join here

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++
}
}
QiWang19 marked this conversation as resolved.
Show resolved Hide resolved

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**]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you show the long form of the option first please?
`[--help|-h]

[**--sign-by**]
[ IMAGE... ]

# DESCRIPTION
**podmain image sign** will create a local signature for one or more local images that have
QiWang19 marked this conversation as resolved.
Show resolved Hide resolved
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**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto long form first, short form second.

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 foo@bar.com with a user's keyring and save the signature in /tmp/signatures/.

sudo podman image sign --sign-by foo@bar.com -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/-d/--directory/

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