sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/test/integration/sign_test.go (about)

     1  //go:build integration
     2  // +build integration
     3  
     4  /*
     5  Copyright 2022 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package integration
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/sigstore/cosign/v2/pkg/cosign"
    31  	"github.com/stretchr/testify/require"
    32  
    33  	"sigs.k8s.io/release-sdk/sign"
    34  )
    35  
    36  const (
    37  	testFile = "hello kubefolx!"
    38  )
    39  
    40  type cleanupFn func() error
    41  
    42  func generateCosignKeyPair(t *testing.T) (privateKeyPath, publicKeyPath string, fn cleanupFn) {
    43  	tempDir, err := os.MkdirTemp("", "k8s-cosign-keys-")
    44  	require.Nil(t, err)
    45  
    46  	keys, err := cosign.GenerateKeyPair(nil)
    47  	require.Nil(t, err)
    48  	require.NotNil(t, keys)
    49  
    50  	privateKeyPath = filepath.Join(tempDir, "cosign.key")
    51  	err = os.WriteFile(privateKeyPath, keys.PrivateBytes, 0o600)
    52  	require.Nil(t, err)
    53  
    54  	publicKeyPath = filepath.Join(tempDir, "cosign.pub")
    55  	err = os.WriteFile(publicKeyPath, keys.PublicBytes, 0o644)
    56  	require.Nil(t, err)
    57  	cleanupFn := func() error {
    58  		return os.RemoveAll(tempDir)
    59  	}
    60  	return privateKeyPath, publicKeyPath, cleanupFn
    61  }
    62  
    63  func TestSuccessSignImage(t *testing.T) {
    64  	imageName := fmt.Sprintf("localhost:5000/honk:%d", time.Now().Unix())
    65  	reg := runDockerRegistryWithDummyImage(t, imageName)
    66  	defer deleteRegistryContainer(t)
    67  
    68  	privateKeyPath, publicKeyPath, cleanup := generateCosignKeyPair(t)
    69  	defer func() {
    70  		require.Nil(t, cleanup())
    71  	}()
    72  
    73  	opts := sign.Default()
    74  	opts.PrivateKeyPath = privateKeyPath
    75  	opts.PublicKeyPath = publicKeyPath
    76  	opts.IgnoreSCT = true
    77  	opts.IgnoreTlog = true
    78  
    79  	signer := sign.New(opts)
    80  
    81  	signedObject, err := signer.SignImage(reg.ImageName)
    82  	require.Nil(t, err)
    83  	require.NotNil(t, signedObject)
    84  	verifiedObject, err := signer.VerifyImage(reg.ImageName)
    85  	require.Nil(t, err)
    86  	require.NotNil(t, verifiedObject)
    87  }
    88  
    89  func TestSuccessSignFile(t *testing.T) {
    90  	// Setup the temp dir
    91  	tempDir, err := os.MkdirTemp("", "k8s-test-file-")
    92  	require.Nil(t, err)
    93  	defer func() {
    94  		require.Nil(t, os.RemoveAll(tempDir))
    95  	}()
    96  
    97  	// Write the test file
    98  	testFilePath := filepath.Join(tempDir, "test")
    99  	testFileCertPath := filepath.Join(tempDir, "test.cert")
   100  	testFileSigPath := filepath.Join(tempDir, "test.sig")
   101  	require.Nil(t, os.WriteFile(testFilePath, []byte(testFile), 0o644))
   102  
   103  	privateKeyPath, publicKeyPath, cleanup := generateCosignKeyPair(t)
   104  	defer func() {
   105  		require.Nil(t, cleanup())
   106  	}()
   107  
   108  	opts := sign.Default()
   109  	opts.PrivateKeyPath = privateKeyPath
   110  	opts.PublicKeyPath = publicKeyPath
   111  	opts.OutputCertificatePath = testFileCertPath
   112  	opts.OutputSignaturePath = testFileSigPath
   113  
   114  	signer := sign.New(opts)
   115  
   116  	signedObject, err := signer.SignFile(testFilePath)
   117  	require.Nil(t, err)
   118  	require.NotNil(t, signedObject.File)
   119  
   120  	verifiedObject, err := signer.VerifyFile(testFilePath, true)
   121  	require.Nil(t, err)
   122  	require.NotNil(t, verifiedObject.File)
   123  }
   124  
   125  func TestIsImageSigned(t *testing.T) {
   126  	signer := sign.New(sign.Default())
   127  	for _, tc := range []struct {
   128  		imageRef  string
   129  		isSigned  bool
   130  		shouldErr bool
   131  	}{
   132  		{
   133  			// cosign ~1.5.2 signed image
   134  			"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2", true, false,
   135  		},
   136  		{
   137  			// k8s/pause ~feb 13 2022. not signed
   138  			"registry.k8s.io/pause@sha256:a78c2d6208eff9b672de43f880093100050983047b7b0afe0217d3656e1b0d5f", false, false,
   139  		},
   140  		{
   141  			// nonexistent image, must fail
   142  			"kornotios/supermegafakeimage", false, true,
   143  		},
   144  	} {
   145  		res, err := signer.IsImageSigned(tc.imageRef)
   146  		require.Equal(t, tc.isSigned, res, fmt.Sprintf("Checking %s for signature", tc.imageRef))
   147  		if tc.shouldErr {
   148  			require.Error(t, err)
   149  		} else {
   150  			require.NoError(t, err)
   151  		}
   152  	}
   153  }
   154  
   155  func TestImagesSigned(t *testing.T) {
   156  	signer := sign.New(sign.Default())
   157  	const repo = "registry.k8s.io/security-profiles-operator/security-profiles-operator"
   158  
   159  	// Running it twice should lead to the same results
   160  	for i := 0; i < 2; i++ {
   161  		for _, tc := range []struct {
   162  			refs      map[string]bool
   163  			shouldErr bool
   164  		}{
   165  			{ // signed single image
   166  				map[string]bool{"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true},
   167  				false,
   168  			},
   169  			{ // nonexistent
   170  				map[string]bool{"kornotios/supermegafakeimage": false},
   171  				true,
   172  			},
   173  			{ // one valid and one nonexistent
   174  				map[string]bool{
   175  					"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true,
   176  					"kornotios/supermegafakeimage":                                            false,
   177  				},
   178  				true,
   179  			},
   180  			{ // list of valid images
   181  				map[string]bool{
   182  					repo + ":v0.2.0": false,
   183  					repo + ":v0.3.0": false,
   184  					repo + ":v0.4.0": false,
   185  					repo + ":v0.4.2": false,
   186  					repo + ":v0.4.3": true,
   187  					repo + ":v0.5.0": true,
   188  					repo + "@sha256:4e61cb64ab34d1b80ebdb900c636a2aff60d85c9a48b0f1d34202d9388856bd7": true,
   189  					repo + "@sha256:9da6d7f148b19154fd6df4cc052e6cd52787962369f34c7b0411f77b843f3d4c": true,
   190  				},
   191  				false,
   192  			},
   193  		} {
   194  			refs := []string{}
   195  			for ref := range tc.refs {
   196  				refs = append(refs, ref)
   197  			}
   198  
   199  			res, err := signer.ImagesSigned(context.Background(), refs...)
   200  			if tc.shouldErr {
   201  				require.Error(t, err)
   202  			} else {
   203  				require.NoError(t, err)
   204  
   205  				for ref, expected := range tc.refs {
   206  					signed, ok := res.Load(ref)
   207  					require.True(t, ok)
   208  					require.Equal(t, expected, signed)
   209  				}
   210  			}
   211  		}
   212  	}
   213  }
   214  
   215  func TestVerifyImages(t *testing.T) {
   216  	const repo = "registry.k8s.io/security-profiles-operator/security-profiles-operator"
   217  
   218  	// Running it twice should lead to the same results
   219  	for i := 0; i < 2; i++ {
   220  		for _, tc := range []struct {
   221  			refs           map[string]bool
   222  			certIdentity   string
   223  			certOidcIssuer string
   224  			shouldErr      bool
   225  		}{
   226  			{ // signed single image
   227  				map[string]bool{"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true},
   228  				"https://github.com/sigstore/cosign/.github/workflows/github-oidc.yaml@refs/heads/main",
   229  				"https://token.actions.githubusercontent.com",
   230  				false,
   231  			},
   232  			{ // nonexistent
   233  				map[string]bool{"kornotios/supermegafakeimage": false},
   234  				"",
   235  				"",
   236  				true,
   237  			},
   238  			{ // one valid and one nonexistent
   239  				map[string]bool{
   240  					"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true,
   241  					"kornotios/supermegafakeimage":                                            false,
   242  				},
   243  				"https://github.com/sigstore/cosign/.github/workflows/github-oidc.yaml@refs/heads/main",
   244  				"https://token.actions.githubusercontent.com",
   245  				true,
   246  			},
   247  			{ // list of valid images
   248  				map[string]bool{
   249  					repo + ":v0.2.0": false,
   250  					repo + ":v0.3.0": false,
   251  					repo + ":v0.4.0": false,
   252  					repo + ":v0.4.2": false,
   253  					repo + ":v0.4.3": true,
   254  					repo + ":v0.5.0": true,
   255  					repo + "@sha256:4e61cb64ab34d1b80ebdb900c636a2aff60d85c9a48b0f1d34202d9388856bd7": true,
   256  					repo + "@sha256:9da6d7f148b19154fd6df4cc052e6cd52787962369f34c7b0411f77b843f3d4c": true,
   257  				},
   258  				"krel-trust@k8s-releng-prod.iam.gserviceaccount.com",
   259  				"https://accounts.google.com",
   260  				false,
   261  			},
   262  		} {
   263  			refs := []string{}
   264  			for ref := range tc.refs {
   265  				refs = append(refs, ref)
   266  			}
   267  
   268  			opts := sign.Default()
   269  			opts.CertIdentity = tc.certIdentity
   270  			opts.CertOidcIssuer = tc.certOidcIssuer
   271  			opts.CertIdentityRegexp = ""
   272  			opts.IgnoreSCT = true
   273  
   274  			signer := sign.New(opts)
   275  
   276  			res, err := signer.VerifyImages(refs...)
   277  			if tc.shouldErr {
   278  				require.Error(t, err)
   279  			} else {
   280  				require.NoError(t, err)
   281  
   282  				for ref, expected := range tc.refs {
   283  					_, ok := res.Load(ref)
   284  					require.Equal(t, expected, ok)
   285  				}
   286  			}
   287  		}
   288  	}
   289  }