package main import ( "io/ioutil" "net/url" "os" "path/filepath" "strconv" "strings" "github.com/containers/image/signature" "github.com/containers/image/transports" "github.com/containers/image/transports/alltransports" "github.com/containers/libpod/cmd/podman/cliconfig" "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/spf13/cobra" ) var ( signCommand cliconfig.SignValues signDescription = "Create a signature file that can be used later to verify the image" _signCommand = &cobra.Command{ Use: "sign", Short: "Sign an image", Long: signDescription, RunE: func(cmd *cobra.Command, args []string) error { signCommand.InputArgs = args signCommand.GlobalFlags = MainGlobalOpts return signCmd(&signCommand) }, Example: "IMAGE-NAME [IMAGE-NAME ...]", } ) func init() { signCommand.Command = _signCommand flags := signCommand.Flags() flags.StringVarP(&signCommand.Directory, "directory", "d", "", "Define an alternate directory to store signatures") flags.StringVar(&signCommand.SignBy, "sign-by", "", "Name of the signing key") } // SignatureStoreDir defines default directory to store signatures const SignatureStoreDir = "/var/lib/containers/sigstore" func signCmd(c *cliconfig.SignValues) error { args := c.InputArgs if len(args) < 1 { return errors.Errorf("at least one image name must be specified") } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) signby := c.SignBy if signby == "" { return errors.Errorf("please provide an identity") } var sigStoreDir string if c.Flag("directory").Changed { sigStoreDir = c.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, nil) 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, err := newImage.RepoDigests() if err != nil { return errors.Wrapf(err, "error calculating repo digests for %s", signimage) } 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") } trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 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(filepath.Join(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 }