Move most of imageEngine.SetTrust to pkg/trust.AddPolicyEntries

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>
This commit is contained in:
Miloslav Trmač
2022-08-24 21:39:14 +02:00
parent 4f68075306
commit cbdbb025a3
3 changed files with 150 additions and 63 deletions

View File

@ -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) {

View File

@ -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)
}

71
pkg/trust/policy_test.go Normal file
View File

@ -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
}