-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ var ( | |
saveCommand, | ||
tagCommand, | ||
trustCommand, | ||
signCommand, | ||
} | ||
|
||
imageDescription = "Manage images" | ||
|
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) | ||
} | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a hidden registriesdirpath option to do tests? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 😅 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, I can't find test for sign in atomic There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, let's use |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's use |
||
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 | ||
} |
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**] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you show the long form of the option first please? |
||
[**--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** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably be username
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 🤔