From cbdbb025a3f6e6e5417cdade032075d679842056 Mon Sep 17 00:00:00 2001
From: Miloslav Trmač <mitr@redhat.com>
Date: Wed, 24 Aug 2022 21:39:14 +0200
Subject: Move most of imageEngine.SetTrust to pkg/trust.AddPolicyEntries
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This will allow us to write unit tests without setting up the complete Podman runtime
(and without the Linux dependency).

Also, actually add a basic smoke test of the core functionality.

Should not change behavior.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
---
 pkg/domain/infra/abi/trust.go | 68 +++------------------------------------
 pkg/trust/policy.go           | 74 +++++++++++++++++++++++++++++++++++++++++++
 pkg/trust/policy_test.go      | 71 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 150 insertions(+), 63 deletions(-)
 create mode 100644 pkg/trust/policy_test.go

(limited to 'pkg')

diff --git a/pkg/domain/infra/abi/trust.go b/pkg/domain/infra/abi/trust.go
index 61bf03727..381ea5deb 100644
--- a/pkg/domain/infra/abi/trust.go
+++ b/pkg/domain/infra/abi/trust.go
@@ -2,11 +2,8 @@ package abi
 
 import (
 	"context"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"io/ioutil"
-	"os"
 	"strings"
 
 	"github.com/containers/podman/v4/pkg/domain/entities"
@@ -51,71 +48,16 @@ func (ir *ImageEngine) SetTrust(ctx context.Context, args []string, options enti
 	}
 	scope := args[0]
 
-	var (
-		policyContentStruct trust.PolicyContent
-		newReposContent     []trust.RepoContent
-	)
-	trustType := options.Type
-	if trustType == "accept" {
-		trustType = "insecureAcceptAnything"
-	}
-
-	pubkeysfile := options.PubKeysFile
-	if len(pubkeysfile) == 0 && trustType == "signedBy" {
-		return errors.New("at least one public key must be defined for type 'signedBy'")
-	}
-
 	policyPath := trust.DefaultPolicyPath(ir.Libpod.SystemContext())
 	if len(options.PolicyPath) > 0 {
 		policyPath = options.PolicyPath
 	}
-	_, err := os.Stat(policyPath)
-	if !os.IsNotExist(err) {
-		policyContent, err := ioutil.ReadFile(policyPath)
-		if err != nil {
-			return err
-		}
-		if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
-			return errors.New("could not read trust policies")
-		}
-	}
-	if len(pubkeysfile) != 0 {
-		for _, filepath := range pubkeysfile {
-			newReposContent = append(newReposContent, trust.RepoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath})
-		}
-	} else {
-		newReposContent = append(newReposContent, trust.RepoContent{Type: trustType})
-	}
-	if scope == "default" {
-		policyContentStruct.Default = newReposContent
-	} else {
-		if len(policyContentStruct.Default) == 0 {
-			return errors.New("default trust policy must be set")
-		}
-		registryExists := false
-		for transport, transportval := range policyContentStruct.Transports {
-			_, registryExists = transportval[scope]
-			if registryExists {
-				policyContentStruct.Transports[transport][scope] = newReposContent
-				break
-			}
-		}
-		if !registryExists {
-			if policyContentStruct.Transports == nil {
-				policyContentStruct.Transports = make(map[string]trust.RepoMap)
-			}
-			if policyContentStruct.Transports["docker"] == nil {
-				policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent)
-			}
-			policyContentStruct.Transports["docker"][scope] = append(policyContentStruct.Transports["docker"][scope], newReposContent...)
-		}
-	}
 
-	data, err := json.MarshalIndent(policyContentStruct, "", "    ")
-	if err != nil {
-		return fmt.Errorf("error setting trust policy: %w", err)
-	}
-	return ioutil.WriteFile(policyPath, data, 0644)
+	return trust.AddPolicyEntries(policyPath, trust.AddPolicyEntriesInput{
+		Scope:       scope,
+		Type:        options.Type,
+		PubKeyFiles: options.PubKeysFile,
+	})
 }
 
 func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]*trust.Policy, error) {
diff --git a/pkg/trust/policy.go b/pkg/trust/policy.go
index 62950131d..352be781c 100644
--- a/pkg/trust/policy.go
+++ b/pkg/trust/policy.go
@@ -5,6 +5,7 @@ import (
 	"bytes"
 	"encoding/base64"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -123,3 +124,76 @@ func GetPolicy(policyPath string) (PolicyContent, error) {
 	}
 	return policyContentStruct, nil
 }
+
+// AddPolicyEntriesInput collects some parameters to AddPolicyEntries,
+// primarily so that the callers use named values instead of just strings in a sequence.
+type AddPolicyEntriesInput struct {
+	Scope       string // "default" or a docker/atomic scope name
+	Type        string
+	PubKeyFiles []string // For signature enforcement types, paths to public keys files (where the image needs to be signed by at least one key from _each_ of the files). File format depends on Type.
+}
+
+// AddPolicyEntries adds one or more policy entries necessary to implement AddPolicyEntriesInput.
+func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error {
+	var (
+		policyContentStruct PolicyContent
+		newReposContent     []RepoContent
+	)
+	trustType := input.Type
+	if trustType == "accept" {
+		trustType = "insecureAcceptAnything"
+	}
+
+	pubkeysfile := input.PubKeyFiles
+	if len(pubkeysfile) == 0 && trustType == "signedBy" {
+		return errors.New("at least one public key must be defined for type 'signedBy'")
+	}
+
+	_, err := os.Stat(policyPath)
+	if !os.IsNotExist(err) {
+		policyContent, err := ioutil.ReadFile(policyPath)
+		if err != nil {
+			return err
+		}
+		if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
+			return errors.New("could not read trust policies")
+		}
+	}
+	if len(pubkeysfile) != 0 {
+		for _, filepath := range pubkeysfile {
+			newReposContent = append(newReposContent, RepoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath})
+		}
+	} else {
+		newReposContent = append(newReposContent, RepoContent{Type: trustType})
+	}
+	if input.Scope == "default" {
+		policyContentStruct.Default = newReposContent
+	} else {
+		if len(policyContentStruct.Default) == 0 {
+			return errors.New("default trust policy must be set")
+		}
+		registryExists := false
+		for transport, transportval := range policyContentStruct.Transports {
+			_, registryExists = transportval[input.Scope]
+			if registryExists {
+				policyContentStruct.Transports[transport][input.Scope] = newReposContent
+				break
+			}
+		}
+		if !registryExists {
+			if policyContentStruct.Transports == nil {
+				policyContentStruct.Transports = make(map[string]RepoMap)
+			}
+			if policyContentStruct.Transports["docker"] == nil {
+				policyContentStruct.Transports["docker"] = make(map[string][]RepoContent)
+			}
+			policyContentStruct.Transports["docker"][input.Scope] = append(policyContentStruct.Transports["docker"][input.Scope], newReposContent...)
+		}
+	}
+
+	data, err := json.MarshalIndent(policyContentStruct, "", "    ")
+	if err != nil {
+		return fmt.Errorf("error setting trust policy: %w", err)
+	}
+	return ioutil.WriteFile(policyPath, data, 0644)
+}
diff --git a/pkg/trust/policy_test.go b/pkg/trust/policy_test.go
new file mode 100644
index 000000000..1f2f585c8
--- /dev/null
+++ b/pkg/trust/policy_test.go
@@ -0,0 +1,71 @@
+package trust
+
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/containers/image/v5/signature"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAddPolicyEntries(t *testing.T) {
+	tempDir := t.TempDir()
+	policyPath := filepath.Join(tempDir, "policy.json")
+
+	minimalPolicy := &signature.Policy{
+		Default: []signature.PolicyRequirement{
+			signature.NewPRInsecureAcceptAnything(),
+		},
+	}
+	minimalPolicyJSON, err := json.Marshal(minimalPolicy)
+	require.NoError(t, err)
+	err = os.WriteFile(policyPath, minimalPolicyJSON, 0600)
+	require.NoError(t, err)
+
+	err = AddPolicyEntries(policyPath, AddPolicyEntriesInput{
+		Scope: "default",
+		Type:  "reject",
+	})
+	assert.NoError(t, err)
+	err = AddPolicyEntries(policyPath, AddPolicyEntriesInput{
+		Scope: "quay.io/accepted",
+		Type:  "accept",
+	})
+	assert.NoError(t, err)
+	err = AddPolicyEntries(policyPath, AddPolicyEntriesInput{
+		Scope:       "quay.io/multi-signed",
+		Type:        "signedBy",
+		PubKeyFiles: []string{"/1.pub", "/2.pub"},
+	})
+	assert.NoError(t, err)
+
+	// Test that the outcome is consumable, and compare it with the expected values.
+	parsedPolicy, err := signature.NewPolicyFromFile(policyPath)
+	require.NoError(t, err)
+	assert.Equal(t, &signature.Policy{
+		Default: signature.PolicyRequirements{
+			signature.NewPRReject(),
+		},
+		Transports: map[string]signature.PolicyTransportScopes{
+			"docker": {
+				"quay.io/accepted": {
+					signature.NewPRInsecureAcceptAnything(),
+				},
+				"quay.io/multi-signed": {
+					xNewPRSignedByKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+					xNewPRSignedByKeyPath(t, "/2.pub", signature.NewPRMMatchRepoDigestOrExact()),
+				},
+			},
+		},
+	}, parsedPolicy)
+}
+
+// xNewPRSignedByKeyPath is a wrapper for NewPRSignedByKeyPath which must not fail.
+func xNewPRSignedByKeyPath(t *testing.T, keyPath string, signedIdentity signature.PolicyReferenceMatch) signature.PolicyRequirement {
+	pr, err := signature.NewPRSignedByKeyPath(signature.SBKeyTypeGPGKeys, keyPath, signedIdentity)
+	require.NoError(t, err)
+	return pr
+}
-- 
cgit v1.2.3-54-g00ecf