get.porter.sh/porter@v1.3.0/tests/integration/signing_test.go (about)

     1  //go:build integration
     2  
     3  package integration
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"testing"
    11  
    12  	"get.porter.sh/porter/pkg/cnab"
    13  	"get.porter.sh/porter/tests/tester"
    14  	"github.com/distribution/reference"
    15  	"github.com/google/go-containerregistry/pkg/crane"
    16  	"github.com/opencontainers/go-digest"
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/uwu-tools/magex/shx"
    19  )
    20  
    21  func TestCosign(t *testing.T) {
    22  	testr, err := tester.NewTestWithConfig(t, "tests/integration/testdata/signing/config/config-cosign.yaml")
    23  	require.NoError(t, err, "tester.NewTest failed")
    24  	defer testr.Close()
    25  	reg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: true})
    26  	defer reg.Close()
    27  	ref := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", reg.String()))
    28  
    29  	setupCosign(t, testr)
    30  	_, output, err := testr.RunPorterWith(func(pc *shx.PreparedCommand) {
    31  		pc.Args("publish", "--sign-bundle", "--insecure-registry", "-f", "testdata/bundles/signing/porter.yaml", "-r", ref.String())
    32  		pc.Env("COSIGN_PASSWORD='test'")
    33  	})
    34  	require.NoError(t, err, "Publish failed")
    35  
    36  	ref = toRefWithDigest(t, ref)
    37  	bundleImageRef := resolveBundleImageDigest(t, output, "sign")
    38  
    39  	_, output = testr.RequirePorter("install", "--verify-bundle", "--reference", ref.String(), "--insecure-registry", "--force")
    40  	require.Contains(t, output, fmt.Sprintf("bundle signature verified for %s", ref.String()))
    41  	require.Contains(t, output, fmt.Sprintf("bundle image signature verified for %s", bundleImageRef.String()))
    42  }
    43  
    44  func TestCosignFromArchive(t *testing.T) {
    45  	testr, err := tester.NewTestWithConfig(t, "tests/integration/testdata/signing/config/config-cosign.yaml")
    46  	require.NoError(t, err, "tester.NewTest failed")
    47  	defer testr.Close()
    48  	reg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: true})
    49  	defer reg.Close()
    50  	ref := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", reg.String()))
    51  
    52  	setupCosign(t, testr)
    53  	_, output, err := testr.RunPorterWith(func(pc *shx.PreparedCommand) {
    54  		pc.Args("publish", "--insecure-registry", "-f", "testdata/bundles/signing/porter.yaml", "-r", ref.String())
    55  		pc.Env("COSIGN_PASSWORD='test'")
    56  	})
    57  	require.NoError(t, err, "Publish failed")
    58  
    59  	tmpDir, err := os.MkdirTemp("", "signBundle")
    60  	require.NoError(t, err, "Error creating temporary directory")
    61  	defer func() {
    62  		os.RemoveAll(tmpDir)
    63  	}()
    64  	archivePath := filepath.Join(tmpDir, "signBundle.tgz")
    65  	_, output = testr.RequirePorter("archive", archivePath, "--insecure-registry", "--reference", ref.String())
    66  	ref, err = cnab.ParseOCIReference(fmt.Sprintf("%s/sign-from-archive:v1.0.0", reg.String()))
    67  	require.NoError(t, err, "error parsing OCI reference")
    68  
    69  	_, output, err = testr.RunPorterWith(func(pc *shx.PreparedCommand) {
    70  		pc.Args("publish", "--sign-bundle", "--insecure-registry", "--archive", archivePath, "-r", ref.String())
    71  		pc.Env("COSIGN_PASSWORD='test'")
    72  	})
    73  	fmt.Println(output)
    74  	require.NoError(t, err, "Publish archive failed")
    75  
    76  	ref = toRefWithDigest(t, ref)
    77  	bundleImageRef := getBundleImageDigest(t, output, "sign-from-archive")
    78  
    79  	_, output = testr.RequirePorter("install", "--verify-bundle", "--reference", ref.String(), "--insecure-registry", "--force")
    80  	require.Contains(t, output, fmt.Sprintf("bundle signature verified for %s", ref.String()))
    81  	require.Contains(t, output, fmt.Sprintf("bundle image signature verified for %s", bundleImageRef.String()))
    82  }
    83  
    84  func TestCosignCopyBundle(t *testing.T) {
    85  	testr, err := tester.NewTestWithConfig(t, "tests/integration/testdata/signing/config/config-cosign.yaml")
    86  	require.NoError(t, err, "tester.NewTest failed")
    87  	defer testr.Close()
    88  	reg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: true})
    89  	defer reg.Close()
    90  	ref := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", reg.String()))
    91  	secondReg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: true})
    92  	defer secondReg.Close()
    93  	copiedRef := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", secondReg.String()))
    94  
    95  	setupCosign(t, testr)
    96  	_, output, err := testr.RunPorterWith(func(pc *shx.PreparedCommand) {
    97  		pc.Args("publish", "--insecure-registry", "-f", "testdata/bundles/signing/porter.yaml", "-r", ref.String())
    98  		pc.Env("COSIGN_PASSWORD='test'")
    99  	})
   100  	require.NoError(t, err, "Publish failed")
   101  
   102  	_, output, err = testr.RunPorterWith(func(pc *shx.PreparedCommand) {
   103  		pc.Args("copy", "--insecure-registry", "--sign-bundle", "--source", ref.String(), "--destination", copiedRef.String())
   104  		pc.Env("COSIGN_PASSWORD='test'")
   105  	})
   106  	fmt.Println(output)
   107  	require.NoError(t, err, "Copy failed")
   108  
   109  	ref = toRefWithDigest(t, ref)
   110  	bundleImageRef := getBundleImageDigest(t, output, "sign")
   111  
   112  	_, output = testr.RequirePorter("install", "--verify-bundle", "--reference", copiedRef.String(), "--insecure-registry", "--force")
   113  	require.Contains(t, output, fmt.Sprintf("bundle signature verified for %s", copiedRef.String()))
   114  	require.Contains(t, output, fmt.Sprintf("bundle image signature verified for %s", bundleImageRef.String()))
   115  }
   116  
   117  func setupCosign(t *testing.T, testr tester.Tester) {
   118  	cmd := shx.Command("cosign", "generate-key-pair").Env("COSIGN_PASSWORD='test'").In(testr.PorterHomeDir)
   119  	err := cmd.RunE()
   120  	require.NoError(t, err, "Generate cosign key pair failed")
   121  }
   122  
   123  func TestNotation(t *testing.T) {
   124  	testr, err := tester.NewTestWithConfig(t, "tests/integration/testdata/signing/config/config-notation.yaml")
   125  	require.NoError(t, err, "tester.NewTest failed")
   126  	defer testr.Close()
   127  	reg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: false})
   128  	defer reg.Close()
   129  	ref := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", reg.String()))
   130  
   131  	setupNotation(t, testr)
   132  	defer cleanupNotation(t)
   133  	_, output, err := testr.RunPorterWith(func(pc *shx.PreparedCommand) {
   134  		pc.Args("publish", "--sign-bundle", "--insecure-registry", "-f", "testdata/bundles/signing/porter.yaml", "-r", ref.String())
   135  	})
   136  	require.NoError(t, err, "Publish failed")
   137  
   138  	ref = toRefWithDigest(t, ref)
   139  	bundleImageRef := resolveBundleImageDigest(t, output, "sign")
   140  
   141  	_, output = testr.RequirePorter("install", "--verify-bundle", "--reference", ref.String(), "--insecure-registry", "--force")
   142  	fmt.Println(output)
   143  	require.Contains(t, output, fmt.Sprintf("bundle signature verified for %s", ref.String()))
   144  	require.Contains(t, output, fmt.Sprintf("bundle image signature verified for %s", bundleImageRef.String()))
   145  }
   146  
   147  func TestNotationFromArchive(t *testing.T) {
   148  	testr, err := tester.NewTestWithConfig(t, "tests/integration/testdata/signing/config/config-notation.yaml")
   149  	require.NoError(t, err, "tester.NewTest failed")
   150  	defer testr.Close()
   151  	reg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: false})
   152  	defer reg.Close()
   153  	ref := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", reg.String()))
   154  
   155  	setupNotation(t, testr)
   156  	defer cleanupNotation(t)
   157  	_, output, err := testr.RunPorterWith(func(pc *shx.PreparedCommand) {
   158  		pc.Args("publish", "--insecure-registry", "-f", "testdata/bundles/signing/porter.yaml", "-r", ref.String())
   159  	})
   160  	require.NoError(t, err, "Publish failed")
   161  
   162  	tmpDir, err := os.MkdirTemp("", "signBundle")
   163  	require.NoError(t, err, "Error creating temporary directory")
   164  	defer func() {
   165  		os.RemoveAll(tmpDir)
   166  	}()
   167  	archivePath := filepath.Join(tmpDir, "signBundle.tgz")
   168  	_, output = testr.RequirePorter("archive", archivePath, "--insecure-registry", "--reference", ref.String())
   169  	ref, err = cnab.ParseOCIReference(fmt.Sprintf("%s/sign-from-archive:v1.0.0", reg.String()))
   170  	require.NoError(t, err, "error parsing OCI reference")
   171  
   172  	_, output, err = testr.RunPorterWith(func(pc *shx.PreparedCommand) {
   173  		pc.Args("publish", "--sign-bundle", "--insecure-registry", "--archive", archivePath, "-r", ref.String())
   174  	})
   175  	fmt.Println(output)
   176  	require.NoError(t, err, "Publish archive failed")
   177  
   178  	ref = toRefWithDigest(t, ref)
   179  	bundleImageRef := getBundleImageDigest(t, output, "sign-from-archive")
   180  
   181  	_, output = testr.RequirePorter("install", "--verify-bundle", "--reference", ref.String(), "--insecure-registry", "--force")
   182  	require.Contains(t, output, fmt.Sprintf("bundle signature verified for %s", ref.String()))
   183  	require.Contains(t, output, fmt.Sprintf("bundle image signature verified for %s", bundleImageRef.String()))
   184  }
   185  
   186  func TestNotationCopyBundle(t *testing.T) {
   187  	testr, err := tester.NewTestWithConfig(t, "tests/integration/testdata/signing/config/config-notation.yaml")
   188  	require.NoError(t, err, "tester.NewTest failed")
   189  	defer testr.Close()
   190  	reg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: false})
   191  	defer reg.Close()
   192  	ref := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", reg.String()))
   193  	secondReg := testr.StartTestRegistry(tester.TestRegistryOptions{UseTLS: false})
   194  	defer secondReg.Close()
   195  	copiedRef := cnab.MustParseOCIReference(fmt.Sprintf("%s/sign:v1.0.0", secondReg.String()))
   196  
   197  	setupNotation(t, testr)
   198  	defer cleanupNotation(t)
   199  	_, output, err := testr.RunPorterWith(func(pc *shx.PreparedCommand) {
   200  		pc.Args("publish", "--insecure-registry", "-f", "testdata/bundles/signing/porter.yaml", "-r", ref.String())
   201  	})
   202  	require.NoError(t, err, "Publish failed")
   203  
   204  	_, output, err = testr.RunPorterWith(func(pc *shx.PreparedCommand) {
   205  		pc.Args("copy", "--insecure-registry", "--sign-bundle", "--source", ref.String(), "--destination", copiedRef.String(), "--force")
   206  	})
   207  	require.NoError(t, err, "Copy failed")
   208  
   209  	ref = toRefWithDigest(t, ref)
   210  	bundleImageRef := getBundleImageDigest(t, output, "sign")
   211  
   212  	_, output = testr.RequirePorter("install", "--verify-bundle", "--reference", copiedRef.String(), "--insecure-registry", "--force")
   213  	require.Contains(t, output, fmt.Sprintf("bundle signature verified for %s", copiedRef.String()))
   214  	require.Contains(t, output, fmt.Sprintf("bundle image signature verified for %s", bundleImageRef.String()))
   215  }
   216  
   217  func setupNotation(t *testing.T, testr tester.Tester) {
   218  	cmd := shx.Command("notation", "cert", "generate-test", "porter-test.org")
   219  	err := cmd.RunE()
   220  	require.NoError(t, err, "Generate notation certificate failed")
   221  	trustPolicy := `
   222  	{
   223  		"version": "1.0",
   224  		"trustPolicies": [
   225  			{
   226  				"name": "porter-test-images",
   227  				"registryScopes": [ "*" ],
   228  				"signatureVerification": {
   229  					"level" : "strict"
   230  				},
   231  				"trustStores": [ "ca:porter-test.org" ],
   232  				"trustedIdentities": [
   233  					"*"
   234  				]
   235  			}
   236  		]
   237  	}`
   238  	trustPolicyPath := filepath.Join(testr.PorterHomeDir, "trustpolicy.json")
   239  	err = os.WriteFile(trustPolicyPath, []byte(trustPolicy), 0644)
   240  	require.NoError(t, err, "Creation of trust policy failed")
   241  	err = shx.Command("notation", "policy", "import", trustPolicyPath).RunE()
   242  	require.NoError(t, err, "importing trust policy failed")
   243  }
   244  
   245  func cleanupNotation(t *testing.T) {
   246  	output, err := shx.Command("notation", "key", "ls").Output()
   247  	require.NoError(t, err)
   248  	keyRegex := regexp.MustCompile(`(/.+porter-test\.org\.key)`)
   249  	keyMatches := keyRegex.FindAllStringSubmatch(output, -1)
   250  	require.Len(t, keyMatches, 1)
   251  	crtRegex := regexp.MustCompile(`key\s+(/.+porter-test\.org\.crt)`)
   252  	crtMatches := crtRegex.FindAllStringSubmatch(output, -1)
   253  	require.Len(t, crtMatches, 1)
   254  	err = shx.Command("notation", "key", "delete", "porter-test.org").RunV()
   255  	require.NoError(t, err)
   256  	err = shx.Command("notation", "cert", "delete", "--type", "ca", "--store", "porter-test.org", "porter-test.org.crt", "--yes").RunV()
   257  	require.NoError(t, err)
   258  	err = os.Remove(keyMatches[0][1])
   259  	require.NoError(t, err)
   260  	err = os.Remove(crtMatches[0][1])
   261  	require.NoError(t, err)
   262  }
   263  
   264  func toRefWithDigest(t *testing.T, ref cnab.OCIReference) cnab.OCIReference {
   265  	desc, err := crane.Head(ref.String(), crane.Insecure)
   266  	require.NoError(t, err)
   267  	ref.Named = reference.TrimNamed(ref.Named)
   268  	ref, err = ref.WithDigest(digest.Digest(desc.Digest.String()))
   269  	require.NoError(t, err)
   270  	return ref
   271  }
   272  
   273  func resolveBundleImageDigest(t *testing.T, output string, imageName string) cnab.OCIReference {
   274  	r := regexp.MustCompile(fmt.Sprintf(`(?m:^Signing bundle image (localhost:\d+/%s:porter-[0-9a-z]+)\.)`, imageName))
   275  	matches := r.FindAllStringSubmatch(output, -1)
   276  	require.Len(t, matches, 1)
   277  	invocationImageRefString := matches[0][1]
   278  	desc, err := crane.Head(invocationImageRefString, crane.Insecure)
   279  	require.NoError(t, err)
   280  	ref := cnab.MustParseOCIReference(invocationImageRefString)
   281  	ref.Named = reference.TrimNamed(ref.Named)
   282  	ref, err = ref.WithDigest(digest.Digest(desc.Digest.String()))
   283  	require.NoError(t, err)
   284  	return ref
   285  }
   286  
   287  func getBundleImageDigest(t *testing.T, output string, imageName string) cnab.OCIReference {
   288  	r := regexp.MustCompile(fmt.Sprintf(`(?m:^Signing bundle image (localhost:\d+/%s@sha256:[0-9a-z]+)\.)`, imageName))
   289  	matches := r.FindAllStringSubmatch(output, -1)
   290  	require.Len(t, matches, 1)
   291  	invocationImageRefString := matches[0][1]
   292  	return cnab.MustParseOCIReference(invocationImageRefString)
   293  }