github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/sign.go (about) 1 package main 2 3 import ( 4 "io/ioutil" 5 "net/url" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 11 "github.com/containers/image/v5/signature" 12 "github.com/containers/image/v5/transports" 13 "github.com/containers/image/v5/transports/alltransports" 14 "github.com/containers/libpod/cmd/podman/cliconfig" 15 "github.com/containers/libpod/cmd/podman/libpodruntime" 16 "github.com/containers/libpod/libpod/image" 17 "github.com/containers/libpod/pkg/rootless" 18 "github.com/containers/libpod/pkg/trust" 19 "github.com/containers/libpod/pkg/util" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus" 22 "github.com/spf13/cobra" 23 ) 24 25 var ( 26 signCommand cliconfig.SignValues 27 signDescription = "Create a signature file that can be used later to verify the image." 28 _signCommand = &cobra.Command{ 29 Use: "sign [flags] IMAGE [IMAGE...]", 30 Short: "Sign an image", 31 Long: signDescription, 32 RunE: func(cmd *cobra.Command, args []string) error { 33 signCommand.InputArgs = args 34 signCommand.GlobalFlags = MainGlobalOpts 35 signCommand.Remote = remoteclient 36 return signCmd(&signCommand) 37 }, 38 Example: `podman image sign --sign-by mykey imageID 39 podman image sign --sign-by mykey --directory ./mykeydir imageID`, 40 } 41 ) 42 43 func init() { 44 signCommand.Command = _signCommand 45 signCommand.SetHelpTemplate(HelpTemplate()) 46 signCommand.SetUsageTemplate(UsageTemplate()) 47 flags := signCommand.Flags() 48 flags.StringVarP(&signCommand.Directory, "directory", "d", "", "Define an alternate directory to store signatures") 49 flags.StringVar(&signCommand.SignBy, "sign-by", "", "Name of the signing key") 50 flags.StringVar(&signCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") 51 } 52 53 // SignatureStoreDir defines default directory to store signatures 54 const SignatureStoreDir = "/var/lib/containers/sigstore" 55 56 func signCmd(c *cliconfig.SignValues) error { 57 args := c.InputArgs 58 if len(args) < 1 { 59 return errors.Errorf("at least one image name must be specified") 60 } 61 runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) 62 if err != nil { 63 return errors.Wrapf(err, "could not create runtime") 64 } 65 defer runtime.DeferredShutdown(false) 66 67 signby := c.SignBy 68 if signby == "" { 69 return errors.Errorf("please provide an identity") 70 } 71 72 var sigStoreDir string 73 if c.Flag("directory").Changed { 74 sigStoreDir = c.Directory 75 if _, err := os.Stat(sigStoreDir); err != nil { 76 return errors.Wrapf(err, "invalid directory %s", sigStoreDir) 77 } 78 } 79 80 sc := runtime.SystemContext() 81 sc.DockerCertPath = c.CertDir 82 83 dockerRegistryOptions := image.DockerRegistryOptions{ 84 DockerCertPath: c.CertDir, 85 } 86 87 mech, err := signature.NewGPGSigningMechanism() 88 if err != nil { 89 return errors.Wrap(err, "error initializing GPG") 90 } 91 defer mech.Close() 92 if err := mech.SupportsSigning(); err != nil { 93 return errors.Wrap(err, "signing is not supported") 94 } 95 96 systemRegistriesDirPath := trust.RegistriesDirPath(sc) 97 registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) 98 if err != nil { 99 return errors.Wrapf(err, "error reading registry configuration") 100 } 101 102 for _, signimage := range args { 103 srcRef, err := alltransports.ParseImageName(signimage) 104 if err != nil { 105 return errors.Wrapf(err, "error parsing image name") 106 } 107 rawSource, err := srcRef.NewImageSource(getContext(), sc) 108 if err != nil { 109 return errors.Wrapf(err, "error getting image source") 110 } 111 err = rawSource.Close() 112 if err != nil { 113 logrus.Errorf("unable to close new image source %q", err) 114 } 115 manifest, _, err := rawSource.GetManifest(getContext(), nil) 116 if err != nil { 117 return errors.Wrapf(err, "error getting manifest") 118 } 119 dockerReference := rawSource.Reference().DockerReference() 120 if dockerReference == nil { 121 return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) 122 } 123 124 // create the signstore file 125 rtc, err := runtime.GetConfig() 126 if err != nil { 127 return err 128 } 129 newImage, err := runtime.ImageRuntime().New(getContext(), signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: signby}, nil, util.PullImageMissing) 130 if err != nil { 131 return errors.Wrapf(err, "error pulling image %s", signimage) 132 } 133 134 if rootless.IsRootless() { 135 if sigStoreDir == "" { 136 sigStoreDir = filepath.Join(filepath.Dir(runtime.StorageConfig().GraphRoot), "sigstore") 137 } 138 } else { 139 registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) 140 if registryInfo != nil { 141 if sigStoreDir == "" { 142 sigStoreDir = registryInfo.SigStoreStaging 143 if sigStoreDir == "" { 144 sigStoreDir = registryInfo.SigStore 145 } 146 } 147 sigStoreDir, err = isValidSigStoreDir(sigStoreDir) 148 if err != nil { 149 return errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) 150 } 151 } 152 if sigStoreDir == "" { 153 sigStoreDir = SignatureStoreDir 154 } 155 } 156 157 repos, err := newImage.RepoDigests() 158 if err != nil { 159 return errors.Wrapf(err, "error calculating repo digests for %s", signimage) 160 } 161 if len(repos) == 0 { 162 logrus.Errorf("no repodigests associated with the image %s", signimage) 163 continue 164 } 165 166 // create signature 167 newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signby) 168 if err != nil { 169 return errors.Wrapf(err, "error creating new signature") 170 } 171 172 trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) 173 sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) 174 if err := os.MkdirAll(sigStoreDir, 0751); err != nil { 175 // The directory is allowed to exist 176 if !os.IsExist(err) { 177 logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) 178 continue 179 } 180 } 181 sigFilename, err := getSigFilename(sigStoreDir) 182 if err != nil { 183 logrus.Errorf("error creating sigstore file: %v", err) 184 continue 185 } 186 err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) 187 if err != nil { 188 logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) 189 continue 190 } 191 } 192 return nil 193 } 194 195 func getSigFilename(sigStoreDirPath string) (string, error) { 196 sigFileSuffix := 1 197 sigFiles, err := ioutil.ReadDir(sigStoreDirPath) 198 if err != nil { 199 return "", err 200 } 201 sigFilenames := make(map[string]bool) 202 for _, file := range sigFiles { 203 sigFilenames[file.Name()] = true 204 } 205 for { 206 sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) 207 if _, exists := sigFilenames[sigFilename]; !exists { 208 return sigFilename, nil 209 } 210 sigFileSuffix++ 211 } 212 } 213 214 func isValidSigStoreDir(sigStoreDir string) (string, error) { 215 writeURIs := map[string]bool{"file": true} 216 url, err := url.Parse(sigStoreDir) 217 if err != nil { 218 return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) 219 } 220 _, exists := writeURIs[url.Scheme] 221 if !exists { 222 return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) 223 } 224 sigStoreDir = url.Path 225 return sigStoreDir, nil 226 }