16
"github.com/containers/common/pkg/config"
17
"github.com/containers/image/v5/types"
18
"github.com/containers/storage/pkg/fileutils"
19
"github.com/containers/storage/pkg/homedir"
20
"github.com/sirupsen/logrus"
23
// policyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy)
24
type policyContent struct {
25
Default []repoContent `json:"default"`
26
Transports transportsContent `json:"transports,omitempty"`
29
// transportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports)
30
type transportsContent map[string]repoMap
32
// repoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes)
33
type repoMap map[string][]repoContent
35
// repoContent is a single policy requirement (one of possibly several for a scope), representing all of the individual alternatives in a single merged struct
36
// (= c/image/v5/signature.{PolicyRequirement,pr*})
37
type repoContent struct {
38
Type string `json:"type"`
39
KeyType string `json:"keyType,omitempty"`
40
KeyPath string `json:"keyPath,omitempty"`
41
KeyPaths []string `json:"keyPaths,omitempty"`
42
KeyData string `json:"keyData,omitempty"`
43
SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"`
46
// genericPolicyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy), using generic data for individual requirements.
47
type genericPolicyContent struct {
48
Default json.RawMessage `json:"default"`
49
Transports genericTransportsContent `json:"transports,omitempty"`
52
// genericTransportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports), using generic data for individual requirements.
53
type genericTransportsContent map[string]genericRepoMap
55
// genericRepoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes)
56
type genericRepoMap map[string]json.RawMessage
58
// DefaultPolicyPath returns a path to the default policy of the system.
59
func DefaultPolicyPath(sys *types.SystemContext) string {
60
if sys != nil && sys.SignaturePolicyPath != "" {
61
return sys.SignaturePolicyPath
64
userPolicyFilePath := filepath.Join(homedir.Get(), filepath.FromSlash(".config/containers/policy.json"))
65
err := fileutils.Exists(userPolicyFilePath)
67
return userPolicyFilePath
69
if !errors.Is(err, fs.ErrNotExist) {
70
logrus.Warnf("Error trying to read local config file: %s", err.Error())
73
systemDefaultPolicyPath := config.DefaultSignaturePolicyPath
74
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
75
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
77
return systemDefaultPolicyPath
80
// gpgIDReader returns GPG key IDs of keys stored at the provided path.
81
// It exists only for tests, production code should always use getGPGIdFromKeyPath.
82
type gpgIDReader func(string) []string
84
// createTmpFile creates a temp file under dir and writes the content into it
85
func createTmpFile(dir, pattern string, content []byte) (string, error) {
86
tmpfile, err := os.CreateTemp(dir, pattern)
92
if _, err := tmpfile.Write(content); err != nil {
95
return tmpfile.Name(), nil
98
// getGPGIdFromKeyPath returns GPG key IDs of keys stored at the provided path.
99
func getGPGIdFromKeyPath(path string) []string {
100
cmd := exec.Command("gpg2", "--with-colons", path)
101
results, err := cmd.Output()
103
logrus.Errorf("Getting key identity: %s", err)
106
return parseUids(results)
109
// getGPGIdFromKeyData returns GPG key IDs of keys in the provided keyring.
110
func getGPGIdFromKeyData(idReader gpgIDReader, key string) []string {
111
decodeKey, err := base64.StdEncoding.DecodeString(key)
113
logrus.Errorf("%s, error decoding key data", err)
116
tmpfileName, err := createTmpFile("", "", decodeKey)
118
logrus.Errorf("Creating key date temp file %s", err)
120
defer os.Remove(tmpfileName)
121
return idReader(tmpfileName)
124
func parseUids(colonDelimitKeys []byte) []string {
125
var parseduids []string
126
scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys))
128
line := scanner.Text()
129
if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
130
uid := strings.Split(line, ":")[9]
135
if ltidx := strings.Index(uid, "<"); ltidx != -1 {
136
subuid := parseduid[ltidx+1:]
137
if gtidx := strings.Index(subuid, ">"); gtidx != -1 {
138
parseduid = subuid[:gtidx]
141
parseduids = append(parseduids, parseduid)
147
// getPolicy parses policy.json into policyContent.
148
func getPolicy(policyPath string) (policyContent, error) {
149
var policyContentStruct policyContent
150
policyContent, err := os.ReadFile(policyPath)
152
return policyContentStruct, fmt.Errorf("unable to read policy file: %w", err)
154
if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
155
return policyContentStruct, fmt.Errorf("could not parse trust policies from %s: %w", policyPath, err)
157
return policyContentStruct, nil
160
var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "sigstoreSigned": "sigstoreSigned", "reject": "reject"}
162
func trustTypeDescription(trustType string) string {
163
trustDescription, exist := typeDescription[trustType]
165
logrus.Warnf("Invalid trust type %s", trustType)
167
return trustDescription
170
// AddPolicyEntriesInput collects some parameters to AddPolicyEntries,
171
// primarily so that the callers use named values instead of just strings in a sequence.
172
type AddPolicyEntriesInput struct {
173
Scope string // "default" or a docker/atomic scope name
175
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.
178
// AddPolicyEntries adds one or more policy entries necessary to implement AddPolicyEntriesInput.
179
func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error {
181
policyContentStruct genericPolicyContent
182
newReposContent []repoContent
184
trustType := input.Type
185
if trustType == "accept" {
186
trustType = "insecureAcceptAnything"
188
pubkeysfile := input.PubKeyFiles
190
// The error messages in validation failures use input.Type instead of trustType to match the user’s input.
192
case "insecureAcceptAnything", "reject":
193
if len(pubkeysfile) != 0 {
194
return fmt.Errorf("%d public keys unexpectedly provided for trust type %v", len(pubkeysfile), input.Type)
196
newReposContent = append(newReposContent, repoContent{Type: trustType})
199
if len(pubkeysfile) == 0 {
200
return errors.New("at least one public key must be defined for type 'signedBy'")
202
for _, filepath := range pubkeysfile {
203
newReposContent = append(newReposContent, repoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath})
206
case "sigstoreSigned":
207
if len(pubkeysfile) == 0 {
208
return errors.New("at least one public key must be defined for type 'sigstoreSigned'")
210
for _, filepath := range pubkeysfile {
211
newReposContent = append(newReposContent, repoContent{Type: trustType, KeyPath: filepath})
215
return fmt.Errorf("unknown trust type %q", input.Type)
217
newReposJSON, err := json.Marshal(newReposContent)
222
err = fileutils.Exists(policyPath)
223
if !os.IsNotExist(err) {
224
policyContent, err := os.ReadFile(policyPath)
228
if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
229
return errors.New("could not read trust policies")
232
if input.Scope == "default" {
233
policyContentStruct.Default = json.RawMessage(newReposJSON)
235
if len(policyContentStruct.Default) == 0 {
236
return errors.New("default trust policy must be set")
238
registryExists := false
239
for transport, transportval := range policyContentStruct.Transports {
240
_, registryExists = transportval[input.Scope]
242
policyContentStruct.Transports[transport][input.Scope] = json.RawMessage(newReposJSON)
247
if policyContentStruct.Transports == nil {
248
policyContentStruct.Transports = make(map[string]genericRepoMap)
250
if policyContentStruct.Transports["docker"] == nil {
251
policyContentStruct.Transports["docker"] = make(map[string]json.RawMessage)
253
policyContentStruct.Transports["docker"][input.Scope] = json.RawMessage(newReposJSON)
257
data, err := json.MarshalIndent(policyContentStruct, "", " ")
259
return fmt.Errorf("setting trust policy: %w", err)
261
return os.WriteFile(policyPath, data, 0644)