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  }