mirror of
https://github.com/containers/podman.git
synced 2025-10-19 20:23:08 +08:00
[WIP]Support podman image sign
Generate a signature claim for an image using user keyring (--sign-by). The signature file will be stored in simple json format under the default or the given directory (--directory or yaml file in /etc/containers/registries.d/). Signed-off-by: Qi Wang <qiwan@redhat.com>
This commit is contained in:
@ -20,6 +20,7 @@ var (
|
|||||||
saveCommand,
|
saveCommand,
|
||||||
tagCommand,
|
tagCommand,
|
||||||
trustCommand,
|
trustCommand,
|
||||||
|
signCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
imageDescription = "Manage images"
|
imageDescription = "Manage images"
|
||||||
|
194
cmd/podman/sign.go
Normal file
194
cmd/podman/sign.go
Normal file
@ -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
docs/podman-image-sign.1.md
Normal file
52
docs/podman-image-sign.1.md
Normal file
@ -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 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
|
||||||
|
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)
|
@ -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. |
|
| 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. |
|
| 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. |
|
| 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
|
## SEE ALSO
|
||||||
podman
|
podman
|
||||||
|
Reference in New Issue
Block a user