podman

Форк
0
/
policy.go 
262 строки · 8.9 Кб
1
package trust
2

3
import (
4
	"bufio"
5
	"bytes"
6
	"encoding/base64"
7
	"encoding/json"
8
	"errors"
9
	"fmt"
10
	"io/fs"
11
	"os"
12
	"os/exec"
13
	"path/filepath"
14
	"strings"
15

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"
21
)
22

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"`
27
}
28

29
// transportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports)
30
type transportsContent map[string]repoMap
31

32
// repoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes)
33
type repoMap map[string][]repoContent
34

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"`
44
}
45

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"`
50
}
51

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
54

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
57

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
62
	}
63

64
	userPolicyFilePath := filepath.Join(homedir.Get(), filepath.FromSlash(".config/containers/policy.json"))
65
	err := fileutils.Exists(userPolicyFilePath)
66
	if err == nil {
67
		return userPolicyFilePath
68
	}
69
	if !errors.Is(err, fs.ErrNotExist) {
70
		logrus.Warnf("Error trying to read local config file: %s", err.Error())
71
	}
72

73
	systemDefaultPolicyPath := config.DefaultSignaturePolicyPath
74
	if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
75
		return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
76
	}
77
	return systemDefaultPolicyPath
78
}
79

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
83

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)
87
	if err != nil {
88
		return "", err
89
	}
90
	defer tmpfile.Close()
91

92
	if _, err := tmpfile.Write(content); err != nil {
93
		return "", err
94
	}
95
	return tmpfile.Name(), nil
96
}
97

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()
102
	if err != nil {
103
		logrus.Errorf("Getting key identity: %s", err)
104
		return nil
105
	}
106
	return parseUids(results)
107
}
108

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)
112
	if err != nil {
113
		logrus.Errorf("%s, error decoding key data", err)
114
		return nil
115
	}
116
	tmpfileName, err := createTmpFile("", "", decodeKey)
117
	if err != nil {
118
		logrus.Errorf("Creating key date temp file %s", err)
119
	}
120
	defer os.Remove(tmpfileName)
121
	return idReader(tmpfileName)
122
}
123

124
func parseUids(colonDelimitKeys []byte) []string {
125
	var parseduids []string
126
	scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys))
127
	for scanner.Scan() {
128
		line := scanner.Text()
129
		if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
130
			uid := strings.Split(line, ":")[9]
131
			if uid == "" {
132
				continue
133
			}
134
			parseduid := uid
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]
139
				}
140
			}
141
			parseduids = append(parseduids, parseduid)
142
		}
143
	}
144
	return parseduids
145
}
146

147
// getPolicy parses policy.json into policyContent.
148
func getPolicy(policyPath string) (policyContent, error) {
149
	var policyContentStruct policyContent
150
	policyContent, err := os.ReadFile(policyPath)
151
	if err != nil {
152
		return policyContentStruct, fmt.Errorf("unable to read policy file: %w", err)
153
	}
154
	if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
155
		return policyContentStruct, fmt.Errorf("could not parse trust policies from %s: %w", policyPath, err)
156
	}
157
	return policyContentStruct, nil
158
}
159

160
var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "sigstoreSigned": "sigstoreSigned", "reject": "reject"}
161

162
func trustTypeDescription(trustType string) string {
163
	trustDescription, exist := typeDescription[trustType]
164
	if !exist {
165
		logrus.Warnf("Invalid trust type %s", trustType)
166
	}
167
	return trustDescription
168
}
169

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
174
	Type        string
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.
176
}
177

178
// AddPolicyEntries adds one or more policy entries necessary to implement AddPolicyEntriesInput.
179
func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error {
180
	var (
181
		policyContentStruct genericPolicyContent
182
		newReposContent     []repoContent
183
	)
184
	trustType := input.Type
185
	if trustType == "accept" {
186
		trustType = "insecureAcceptAnything"
187
	}
188
	pubkeysfile := input.PubKeyFiles
189

190
	// The error messages in validation failures use input.Type instead of trustType to match the user’s input.
191
	switch trustType {
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)
195
		}
196
		newReposContent = append(newReposContent, repoContent{Type: trustType})
197

198
	case "signedBy":
199
		if len(pubkeysfile) == 0 {
200
			return errors.New("at least one public key must be defined for type 'signedBy'")
201
		}
202
		for _, filepath := range pubkeysfile {
203
			newReposContent = append(newReposContent, repoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath})
204
		}
205

206
	case "sigstoreSigned":
207
		if len(pubkeysfile) == 0 {
208
			return errors.New("at least one public key must be defined for type 'sigstoreSigned'")
209
		}
210
		for _, filepath := range pubkeysfile {
211
			newReposContent = append(newReposContent, repoContent{Type: trustType, KeyPath: filepath})
212
		}
213

214
	default:
215
		return fmt.Errorf("unknown trust type %q", input.Type)
216
	}
217
	newReposJSON, err := json.Marshal(newReposContent)
218
	if err != nil {
219
		return err
220
	}
221

222
	err = fileutils.Exists(policyPath)
223
	if !os.IsNotExist(err) {
224
		policyContent, err := os.ReadFile(policyPath)
225
		if err != nil {
226
			return err
227
		}
228
		if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
229
			return errors.New("could not read trust policies")
230
		}
231
	}
232
	if input.Scope == "default" {
233
		policyContentStruct.Default = json.RawMessage(newReposJSON)
234
	} else {
235
		if len(policyContentStruct.Default) == 0 {
236
			return errors.New("default trust policy must be set")
237
		}
238
		registryExists := false
239
		for transport, transportval := range policyContentStruct.Transports {
240
			_, registryExists = transportval[input.Scope]
241
			if registryExists {
242
				policyContentStruct.Transports[transport][input.Scope] = json.RawMessage(newReposJSON)
243
				break
244
			}
245
		}
246
		if !registryExists {
247
			if policyContentStruct.Transports == nil {
248
				policyContentStruct.Transports = make(map[string]genericRepoMap)
249
			}
250
			if policyContentStruct.Transports["docker"] == nil {
251
				policyContentStruct.Transports["docker"] = make(map[string]json.RawMessage)
252
			}
253
			policyContentStruct.Transports["docker"][input.Scope] = json.RawMessage(newReposJSON)
254
		}
255
	}
256

257
	data, err := json.MarshalIndent(policyContentStruct, "", "    ")
258
	if err != nil {
259
		return fmt.Errorf("setting trust policy: %w", err)
260
	}
261
	return os.WriteFile(policyPath, data, 0644)
262
}
263

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.