github.com/containerd/nerdctl@v1.7.7/pkg/signutil/cosignutil.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package signutil 18 19 import ( 20 "bufio" 21 "context" 22 "errors" 23 "os" 24 "os/exec" 25 "strings" 26 27 "github.com/containerd/log" 28 "github.com/containerd/nerdctl/pkg/imgutil" 29 ) 30 31 // SignCosign signs an image(`rawRef`) using a cosign private key (`keyRef`) 32 func SignCosign(rawRef string, keyRef string) error { 33 cosignExecutable, err := exec.LookPath("cosign") 34 if err != nil { 35 log.L.WithError(err).Error("cosign executable not found in path $PATH") 36 log.L.Info("you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation") 37 return err 38 } 39 40 cosignCmd := exec.Command(cosignExecutable, []string{"sign"}...) 41 cosignCmd.Env = os.Environ() 42 43 // if key is empty, use keyless mode(experimental) 44 if keyRef != "" { 45 cosignCmd.Args = append(cosignCmd.Args, "--key", keyRef) 46 } else { 47 cosignCmd.Env = append(cosignCmd.Env, "COSIGN_EXPERIMENTAL=true") 48 } 49 50 cosignCmd.Args = append(cosignCmd.Args, "--yes") 51 cosignCmd.Args = append(cosignCmd.Args, rawRef) 52 53 log.L.Debugf("running %s %v", cosignExecutable, cosignCmd.Args) 54 55 err = processCosignIO(cosignCmd) 56 if err != nil { 57 return err 58 } 59 60 return cosignCmd.Wait() 61 } 62 63 // VerifyCosign verifies an image(`rawRef`) with a cosign public key(`keyRef`) 64 // `hostsDirs` are used to resolve image `rawRef` 65 // Either --cosign-certificate-identity or --cosign-certificate-identity-regexp and either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows. 66 func VerifyCosign(ctx context.Context, rawRef string, keyRef string, hostsDirs []string, 67 certIdentity string, certIdentityRegexp string, certOidcIssuer string, certOidcIssuerRegexp string) (string, error) { 68 digest, err := imgutil.ResolveDigest(ctx, rawRef, false, hostsDirs) 69 if err != nil { 70 log.G(ctx).WithError(err).Errorf("unable to resolve digest for an image %s: %v", rawRef, err) 71 return rawRef, err 72 } 73 ref := rawRef 74 if !strings.Contains(ref, "@") { 75 ref += "@" + digest 76 } 77 78 log.G(ctx).Debugf("verifying image: %s", ref) 79 80 cosignExecutable, err := exec.LookPath("cosign") 81 if err != nil { 82 log.G(ctx).WithError(err).Error("cosign executable not found in path $PATH") 83 log.G(ctx).Info("you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation") 84 return ref, err 85 } 86 87 cosignCmd := exec.Command(cosignExecutable, []string{"verify"}...) 88 cosignCmd.Env = os.Environ() 89 90 // if key is empty, use keyless mode(experimental) 91 if keyRef != "" { 92 cosignCmd.Args = append(cosignCmd.Args, "--key", keyRef) 93 } else { 94 if certIdentity == "" && certIdentityRegexp == "" { 95 return ref, errors.New("--cosign-certificate-identity or --cosign-certificate-identity-regexp is required for Cosign verification in keyless mode") 96 } 97 if certIdentity != "" { 98 cosignCmd.Args = append(cosignCmd.Args, "--certificate-identity", certIdentity) 99 } 100 if certIdentityRegexp != "" { 101 cosignCmd.Args = append(cosignCmd.Args, "--certificate-identity-regexp", certIdentityRegexp) 102 } 103 if certOidcIssuer == "" && certOidcIssuerRegexp == "" { 104 return ref, errors.New("--cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp is required for Cosign verification in keyless mode") 105 } 106 if certOidcIssuer != "" { 107 cosignCmd.Args = append(cosignCmd.Args, "--certificate-oidc-issuer", certOidcIssuer) 108 } 109 if certOidcIssuerRegexp != "" { 110 cosignCmd.Args = append(cosignCmd.Args, "--certificate-oidc-issuer-regexp", certOidcIssuerRegexp) 111 } 112 cosignCmd.Env = append(cosignCmd.Env, "COSIGN_EXPERIMENTAL=true") 113 } 114 115 cosignCmd.Args = append(cosignCmd.Args, ref) 116 117 log.G(ctx).Debugf("running %s %v", cosignExecutable, cosignCmd.Args) 118 119 err = processCosignIO(cosignCmd) 120 if err != nil { 121 return ref, err 122 } 123 if err := cosignCmd.Wait(); err != nil { 124 return ref, err 125 } 126 127 return ref, nil 128 } 129 130 func processCosignIO(cosignCmd *exec.Cmd) error { 131 stdout, err := cosignCmd.StdoutPipe() 132 if err != nil { 133 log.L.Warn("cosign: " + err.Error()) 134 } 135 stderr, err := cosignCmd.StderrPipe() 136 if err != nil { 137 log.L.Warn("cosign: " + err.Error()) 138 } 139 if err := cosignCmd.Start(); err != nil { 140 // only return err if it's critical (cosign start failed.) 141 return err 142 } 143 144 scanner := bufio.NewScanner(stdout) 145 for scanner.Scan() { 146 log.L.Info("cosign: " + scanner.Text()) 147 } 148 if err := scanner.Err(); err != nil { 149 log.L.Warn("cosign: " + err.Error()) 150 } 151 152 errScanner := bufio.NewScanner(stderr) 153 for errScanner.Scan() { 154 log.L.Info("cosign: " + errScanner.Text()) 155 } 156 if err := errScanner.Err(); err != nil { 157 log.L.Warn("cosign: " + err.Error()) 158 } 159 160 return nil 161 }