mirror of
https://github.com/containers/podman.git
synced 2025-07-15 03:02:52 +08:00
Support podman image trust command
Display the trust policy of the host system. The trust policy is stored in the /etc/containers/policy.json file and defines a scope of registries or repositories. Signed-off-by: Qi Wang <qiwan@redhat.com>
This commit is contained in:
@ -19,6 +19,7 @@ var (
|
|||||||
rmImageCommand,
|
rmImageCommand,
|
||||||
saveCommand,
|
saveCommand,
|
||||||
tagCommand,
|
tagCommand,
|
||||||
|
trustCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
imageDescription = "Manage images"
|
imageDescription = "Manage images"
|
||||||
|
293
cmd/podman/trust.go
Normal file
293
cmd/podman/trust.go
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
"github.com/containers/libpod/cmd/podman/formats"
|
||||||
|
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
||||||
|
"github.com/containers/libpod/libpod/image"
|
||||||
|
"github.com/containers/libpod/pkg/trust"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
setTrustFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "type, t",
|
||||||
|
Usage: "Trust type, accept values: signedBy(default), accept, reject.",
|
||||||
|
Value: "signedBy",
|
||||||
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "pubkeysfile, f",
|
||||||
|
Usage: `Path of installed public key(s) to trust for TARGET.
|
||||||
|
Absolute path to keys is added to policy.json. May
|
||||||
|
used multiple times to define multiple public keys.
|
||||||
|
File(s) must exist before using this command.`,
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "policypath",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
showTrustFlags = []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "raw",
|
||||||
|
Usage: "Output raw policy file",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "json, j",
|
||||||
|
Usage: "Output as json",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "policypath",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "registrypath",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
setTrustDescription = "Set default trust policy or add a new trust policy for a registry"
|
||||||
|
setTrustCommand = cli.Command{
|
||||||
|
Name: "set",
|
||||||
|
Usage: "Set default trust policy or a new trust policy for a registry",
|
||||||
|
Description: setTrustDescription,
|
||||||
|
Flags: sortFlags(setTrustFlags),
|
||||||
|
ArgsUsage: "default | REGISTRY[/REPOSITORY]",
|
||||||
|
Action: setTrustCmd,
|
||||||
|
OnUsageError: usageErrorHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
showTrustDescription = "Display trust policy for the system"
|
||||||
|
showTrustCommand = cli.Command{
|
||||||
|
Name: "show",
|
||||||
|
Usage: "Display trust policy for the system",
|
||||||
|
Description: showTrustDescription,
|
||||||
|
Flags: sortFlags(showTrustFlags),
|
||||||
|
Action: showTrustCmd,
|
||||||
|
ArgsUsage: "",
|
||||||
|
UseShortOptionHandling: true,
|
||||||
|
OnUsageError: usageErrorHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
trustSubCommands = []cli.Command{
|
||||||
|
setTrustCommand,
|
||||||
|
showTrustCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
trustDescription = fmt.Sprintf(`Manages the trust policy of the host system. (%s)
|
||||||
|
Trust policy describes a registry scope that must be signed by public keys.`, getDefaultPolicyPath())
|
||||||
|
trustCommand = cli.Command{
|
||||||
|
Name: "trust",
|
||||||
|
Usage: "Manage container image trust policy",
|
||||||
|
Description: trustDescription,
|
||||||
|
ArgsUsage: "{set,show} ...",
|
||||||
|
Subcommands: trustSubCommands,
|
||||||
|
OnUsageError: usageErrorHandler,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func showTrustCmd(c *cli.Context) error {
|
||||||
|
runtime, err := libpodruntime.GetRuntime(c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not create runtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
policyPath string
|
||||||
|
systemRegistriesDirPath string
|
||||||
|
)
|
||||||
|
if c.IsSet("policypath") {
|
||||||
|
policyPath = c.String("policypath")
|
||||||
|
} else {
|
||||||
|
policyPath = trust.DefaultPolicyPath(runtime.SystemContext())
|
||||||
|
}
|
||||||
|
policyContent, err := ioutil.ReadFile(policyPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to read %s", policyPath)
|
||||||
|
}
|
||||||
|
if c.IsSet("registrypath") {
|
||||||
|
systemRegistriesDirPath = c.String("registrypath")
|
||||||
|
} else {
|
||||||
|
systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool("raw") {
|
||||||
|
_, err := os.Stdout.Write(policyContent)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read trust policies")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var policyContentStruct trust.PolicyContent
|
||||||
|
if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
|
||||||
|
return errors.Errorf("could not read trust policies")
|
||||||
|
}
|
||||||
|
policyJSON, err := trust.GetPolicyJSON(policyContentStruct, systemRegistriesDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error reading registry config file")
|
||||||
|
}
|
||||||
|
if c.Bool("json") {
|
||||||
|
var outjson interface{}
|
||||||
|
outjson = policyJSON
|
||||||
|
out := formats.JSONStruct{Output: outjson}
|
||||||
|
return formats.Writer(out).Out()
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedRepos := sortPolicyJSONKey(policyJSON)
|
||||||
|
type policydefault struct {
|
||||||
|
Repo string
|
||||||
|
Trusttype string
|
||||||
|
GPGid string
|
||||||
|
Sigstore string
|
||||||
|
}
|
||||||
|
var policyoutput []policydefault
|
||||||
|
for _, repo := range sortedRepos {
|
||||||
|
repoval := policyJSON[repo]
|
||||||
|
var defaultstruct policydefault
|
||||||
|
defaultstruct.Repo = repo
|
||||||
|
if repoval["type"] != nil {
|
||||||
|
defaultstruct.Trusttype = trustTypeDescription(repoval["type"].(string))
|
||||||
|
}
|
||||||
|
if repoval["keys"] != nil && len(repoval["keys"].([]string)) > 0 {
|
||||||
|
defaultstruct.GPGid = trust.GetGPGId(repoval["keys"].([]string))
|
||||||
|
}
|
||||||
|
if repoval["sigstore"] != nil {
|
||||||
|
defaultstruct.Sigstore = repoval["sigstore"].(string)
|
||||||
|
}
|
||||||
|
policyoutput = append(policyoutput, defaultstruct)
|
||||||
|
}
|
||||||
|
var output []interface{}
|
||||||
|
for _, ele := range policyoutput {
|
||||||
|
output = append(output, interface{}(ele))
|
||||||
|
}
|
||||||
|
out := formats.StdoutTemplateArray{Output: output, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"}
|
||||||
|
return formats.Writer(out).Out()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTrustCmd(c *cli.Context) error {
|
||||||
|
runtime, err := libpodruntime.GetRuntime(c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not create runtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := c.Args()
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.Errorf("default or a registry name must be specified")
|
||||||
|
}
|
||||||
|
valid, err := image.IsValidImageURI(args[0])
|
||||||
|
if err != nil || !valid {
|
||||||
|
return errors.Wrapf(err, "invalid image uri %s", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
trusttype := c.String("type")
|
||||||
|
if !isValidTrustType(trusttype) {
|
||||||
|
return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype)
|
||||||
|
}
|
||||||
|
if trusttype == "accept" {
|
||||||
|
trusttype = "insecureAcceptAnything"
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeysfile := c.StringSlice("pubkeysfile")
|
||||||
|
if len(pubkeysfile) == 0 && trusttype == "signedBy" {
|
||||||
|
return errors.Errorf("At least one public key must be defined for type 'signedBy'")
|
||||||
|
}
|
||||||
|
|
||||||
|
var policyPath string
|
||||||
|
if c.IsSet("policypath") {
|
||||||
|
policyPath = c.String("policypath")
|
||||||
|
} else {
|
||||||
|
policyPath = trust.DefaultPolicyPath(runtime.SystemContext())
|
||||||
|
}
|
||||||
|
var policyContentStruct trust.PolicyContent
|
||||||
|
_, err = os.Stat(policyPath)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
policyContent, err := ioutil.ReadFile(policyPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to read %s", policyPath)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
|
||||||
|
return errors.Errorf("could not read trust policies")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newReposContent []trust.RepoContent
|
||||||
|
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 args[0] == "default" {
|
||||||
|
policyContentStruct.Default = newReposContent
|
||||||
|
} else {
|
||||||
|
exists := false
|
||||||
|
for transport, transportval := range policyContentStruct.Transports {
|
||||||
|
_, exists = transportval[args[0]]
|
||||||
|
if exists {
|
||||||
|
policyContentStruct.Transports[transport][args[0]] = newReposContent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
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"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(policyContentStruct, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error setting trust policy")
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(policyPath, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error setting trust policy")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"}
|
||||||
|
|
||||||
|
func trustTypeDescription(trustType string) string {
|
||||||
|
trustDescription, exist := typeDescription[trustType]
|
||||||
|
if !exist {
|
||||||
|
logrus.Warnf("invalid trust type %s", trustType)
|
||||||
|
}
|
||||||
|
return trustDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortPolicyJSONKey(m map[string]map[string]interface{}) []string {
|
||||||
|
keys := make([]string, len(m))
|
||||||
|
i := 0
|
||||||
|
for k := range m {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidTrustType(t string) bool {
|
||||||
|
if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultPolicyPath() string {
|
||||||
|
return trust.DefaultPolicyPath(&types.SystemContext{})
|
||||||
|
}
|
81
docs/podman-image-trust.1.md
Normal file
81
docs/podman-image-trust.1.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
% podman-image-trust "1"
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
podman\-trust - Manage container image trust policy
|
||||||
|
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
**podman image trust set|show**
|
||||||
|
[**-h**|**--help**]
|
||||||
|
[**-j**|**--json**]
|
||||||
|
[**--raw**]
|
||||||
|
[**-f**|**--pubkeysfile** KEY1 [**f**|**--pubkeysfile** KEY2,...]]
|
||||||
|
[**-t**|**--type** signedBy|accept|reject]
|
||||||
|
REGISTRY[/REPOSITORY]
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
Manages the trust policy of the host system. Trust policy describes
|
||||||
|
a registry scope (registry and/or repository) that must be signed by public keys. Trust
|
||||||
|
is defined in **/etc/containers/policy.json**. Trust is enforced when a user attempts to pull
|
||||||
|
an image from a registry.
|
||||||
|
|
||||||
|
Trust scope is evaluated by most specific to least specific. In other words, policy may
|
||||||
|
be defined for an entire registry, but refined for a particular repository in that
|
||||||
|
registry. See below for examples.
|
||||||
|
|
||||||
|
Trust **type** provides a way to whitelist ("accept") or blacklist
|
||||||
|
("reject") registries.
|
||||||
|
|
||||||
|
Trust may be updated using the command **podman image trust set** for an existing trust scope.
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
**-h** **--help**
|
||||||
|
Print usage statement.
|
||||||
|
|
||||||
|
**-f** **--pubkeysfile**
|
||||||
|
A path to an exported public key on the local system. Key paths
|
||||||
|
will be referenced in policy.json. Any path may be used but path
|
||||||
|
**/etc/pki/containers** is recommended. Option may be used multiple times to
|
||||||
|
require an image be sigend by multiple keys. One of **--pubkeys** or
|
||||||
|
**--pubkeysfile** is required for **signedBy** type.
|
||||||
|
|
||||||
|
**-t** **--type**
|
||||||
|
The trust type for this policy entry. Accepted values:
|
||||||
|
**signedBy** (default): Require signatures with corresponding list of
|
||||||
|
public keys
|
||||||
|
**accept**: do not require any signatures for this
|
||||||
|
registry scope
|
||||||
|
**reject**: do not accept images for this registry scope
|
||||||
|
|
||||||
|
# show OPTIONS
|
||||||
|
|
||||||
|
**--raw**
|
||||||
|
Output trust policy file as raw JSON
|
||||||
|
|
||||||
|
**-j** **--json**
|
||||||
|
Output trust as JSON for machine parsing
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
Accept all unsigned images from a registry
|
||||||
|
|
||||||
|
podman image trust set --type accept docker.io
|
||||||
|
|
||||||
|
Modify default trust policy
|
||||||
|
|
||||||
|
podman image trust set -t reject default
|
||||||
|
|
||||||
|
Display system trust policy
|
||||||
|
|
||||||
|
podman image trust show
|
||||||
|
|
||||||
|
Display trust policy file
|
||||||
|
|
||||||
|
podman image trust show --raw
|
||||||
|
|
||||||
|
Display trust as JSON
|
||||||
|
|
||||||
|
podman image trust show --json
|
||||||
|
|
||||||
|
# HISTORY
|
||||||
|
December 2018, originally compiled by Qi Wang (qiwan at redhat dot com)
|
@ -26,6 +26,7 @@ The image command allows you to manage images
|
|||||||
| rm | [podman-rm(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
|
| rm | [podman-rm(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
|
||||||
| save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. |
|
| save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. |
|
||||||
| tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
|
| tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
|
||||||
|
| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container image trust policy.
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
podman
|
podman
|
||||||
|
@ -2,6 +2,8 @@ package image
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cp "github.com/containers/image/copy"
|
cp "github.com/containers/image/copy"
|
||||||
@ -117,3 +119,23 @@ func GetAdditionalTags(images []string) ([]reference.NamedTagged, error) {
|
|||||||
}
|
}
|
||||||
return allTags, nil
|
return allTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsValidImageURI checks if image name has valid format
|
||||||
|
func IsValidImageURI(imguri string) (bool, error) {
|
||||||
|
uri := "http://" + imguri
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
|
||||||
|
}
|
||||||
|
reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`)
|
||||||
|
ret := reg.FindAllString(u.Host, -1)
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
|
||||||
|
}
|
||||||
|
reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`)
|
||||||
|
ret = reg.FindAllString(u.Fragment, -1)
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
@ -877,3 +877,8 @@ func (r *Runtime) generateName() (string, error) {
|
|||||||
func (r *Runtime) ImageRuntime() *image.Runtime {
|
func (r *Runtime) ImageRuntime() *image.Runtime {
|
||||||
return r.imageRuntime
|
return r.imageRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemContext returns the imagecontext
|
||||||
|
func (r *Runtime) SystemContext() *types.SystemContext {
|
||||||
|
return r.imageContext
|
||||||
|
}
|
||||||
|
250
pkg/trust/trust.go
Normal file
250
pkg/trust/trust.go
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PolicyContent struct for policy.json file
|
||||||
|
type PolicyContent struct {
|
||||||
|
Default []RepoContent `json:"default"`
|
||||||
|
Transports TransportsContent `json:"transports"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoContent struct used under each repo
|
||||||
|
type RepoContent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
KeyType string `json:"keyType,omitempty"`
|
||||||
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
KeyData string `json:"keyData,omitempty"`
|
||||||
|
SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoMap map repo name to policycontent for each repo
|
||||||
|
type RepoMap map[string][]RepoContent
|
||||||
|
|
||||||
|
// TransportsContent struct for content under "transports"
|
||||||
|
type TransportsContent map[string]RepoMap
|
||||||
|
|
||||||
|
// RegistryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all.
|
||||||
|
// NOTE: Keep this in sync with docs/registries.d.md!
|
||||||
|
type RegistryConfiguration struct {
|
||||||
|
DefaultDocker *RegistryNamespace `json:"default-docker"`
|
||||||
|
// The key is a namespace, using fully-expanded Docker reference format or parent namespaces (per dockerReference.PolicyConfiguration*),
|
||||||
|
Docker map[string]RegistryNamespace `json:"docker"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistryNamespace defines lookaside locations for a single namespace.
|
||||||
|
type RegistryNamespace struct {
|
||||||
|
SigStore string `json:"sigstore"` // For reading, and if SigStoreStaging is not present, for writing.
|
||||||
|
SigStoreStaging string `json:"sigstore-staging"` // For writing only.
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPolicyPath returns a path to the default policy of the system.
|
||||||
|
func DefaultPolicyPath(sys *types.SystemContext) string {
|
||||||
|
systemDefaultPolicyPath := "/etc/containers/policy.json"
|
||||||
|
if sys != nil {
|
||||||
|
if sys.SignaturePolicyPath != "" {
|
||||||
|
return sys.SignaturePolicyPath
|
||||||
|
}
|
||||||
|
if sys.RootForImplicitAbsolutePaths != "" {
|
||||||
|
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return systemDefaultPolicyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistriesDirPath returns a path to registries.d
|
||||||
|
func RegistriesDirPath(sys *types.SystemContext) string {
|
||||||
|
systemRegistriesDirPath := "/etc/containers/registries.d"
|
||||||
|
if sys != nil {
|
||||||
|
if sys.RegistriesDirPath != "" {
|
||||||
|
return sys.RegistriesDirPath
|
||||||
|
}
|
||||||
|
if sys.RootForImplicitAbsolutePaths != "" {
|
||||||
|
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return systemRegistriesDirPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndMergeConfig loads configuration files in dirPath
|
||||||
|
func LoadAndMergeConfig(dirPath string) (*RegistryConfiguration, error) {
|
||||||
|
mergedConfig := RegistryConfiguration{Docker: map[string]RegistryNamespace{}}
|
||||||
|
dockerDefaultMergedFrom := ""
|
||||||
|
nsMergedFrom := map[string]string{}
|
||||||
|
|
||||||
|
dir, err := os.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return &mergedConfig, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configNames, err := dir.Readdirnames(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, configName := range configNames {
|
||||||
|
if !strings.HasSuffix(configName, ".yaml") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configPath := filepath.Join(dirPath, configName)
|
||||||
|
configBytes, err := ioutil.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var config RegistryConfiguration
|
||||||
|
err = yaml.Unmarshal(configBytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Error parsing %s", configPath)
|
||||||
|
}
|
||||||
|
if config.DefaultDocker != nil {
|
||||||
|
if mergedConfig.DefaultDocker != nil {
|
||||||
|
return nil, errors.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
|
||||||
|
dockerDefaultMergedFrom, configPath)
|
||||||
|
}
|
||||||
|
mergedConfig.DefaultDocker = config.DefaultDocker
|
||||||
|
dockerDefaultMergedFrom = configPath
|
||||||
|
}
|
||||||
|
for nsName, nsConfig := range config.Docker { // includes config.Docker == nil
|
||||||
|
if _, ok := mergedConfig.Docker[nsName]; ok {
|
||||||
|
return nil, errors.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
|
||||||
|
nsName, nsMergedFrom[nsName], configPath)
|
||||||
|
}
|
||||||
|
mergedConfig.Docker[nsName] = nsConfig
|
||||||
|
nsMergedFrom[nsName] = configPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &mergedConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HaveMatchRegistry checks if trust settings for the registry have been configed in yaml file
|
||||||
|
func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *RegistryNamespace {
|
||||||
|
searchKey := key
|
||||||
|
if !strings.Contains(searchKey, "/") {
|
||||||
|
val, exists := registryConfigs.Docker[searchKey]
|
||||||
|
if exists {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for range strings.Split(key, "/") {
|
||||||
|
val, exists := registryConfigs.Docker[searchKey]
|
||||||
|
if exists {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
if strings.Contains(searchKey, "/") {
|
||||||
|
searchKey = searchKey[:strings.LastIndex(searchKey, "/")]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTmpFile creates a temp file under dir and writes the content into it
|
||||||
|
func CreateTmpFile(dir, pattern string, content []byte) (string, error) {
|
||||||
|
tmpfile, err := ioutil.TempFile(dir, pattern)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer tmpfile.Close()
|
||||||
|
|
||||||
|
if _, err := tmpfile.Write(content); err != nil {
|
||||||
|
return "", err
|
||||||
|
|
||||||
|
}
|
||||||
|
return tmpfile.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGPGId return GPG identity, either bracketed <email> or ID string
|
||||||
|
// comma separated if more than one key
|
||||||
|
func GetGPGId(keys []string) string {
|
||||||
|
for _, k := range keys {
|
||||||
|
if _, err := os.Stat(k); err != nil {
|
||||||
|
decodeKey, err := base64.StdEncoding.DecodeString(k)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("error decoding key data")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpfileName, err := CreateTmpFile("/run/", "", decodeKey)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("error creating key date temp file %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpfileName)
|
||||||
|
k = tmpfileName
|
||||||
|
}
|
||||||
|
cmd := exec.Command("gpg2", "--with-colons", k)
|
||||||
|
results, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("error get key identity: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resultsStr := *(*string)(unsafe.Pointer(&results))
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(resultsStr))
|
||||||
|
var parseduids []string
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
|
||||||
|
uid := strings.Split(line, ":")[9]
|
||||||
|
if uid == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parseduid := uid
|
||||||
|
if strings.Contains(uid, "<") && strings.Contains(uid, ">") {
|
||||||
|
parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0]
|
||||||
|
}
|
||||||
|
parseduids = append(parseduids, parseduid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parseduids, ",")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPolicyJSON return the struct to show policy.json in json format
|
||||||
|
func GetPolicyJSON(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) {
|
||||||
|
registryConfigs, err := LoadAndMergeConfig(systemRegistriesDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
policyJSON := make(map[string]map[string]interface{})
|
||||||
|
if len(policyContentStruct.Default) > 0 {
|
||||||
|
policyJSON["* (default)"] = make(map[string]interface{})
|
||||||
|
policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type
|
||||||
|
}
|
||||||
|
for transname, transval := range policyContentStruct.Transports {
|
||||||
|
for repo, repoval := range transval {
|
||||||
|
policyJSON[repo] = make(map[string]interface{})
|
||||||
|
policyJSON[repo]["type"] = repoval[0].Type
|
||||||
|
policyJSON[repo]["transport"] = transname
|
||||||
|
for _, repoele := range repoval {
|
||||||
|
keyarr := []string{}
|
||||||
|
if len(repoele.KeyPath) > 0 {
|
||||||
|
keyarr = append(keyarr, repoele.KeyPath)
|
||||||
|
}
|
||||||
|
if len(repoele.KeyData) > 0 {
|
||||||
|
keyarr = append(keyarr, string(repoele.KeyData))
|
||||||
|
}
|
||||||
|
policyJSON[repo]["keys"] = keyarr
|
||||||
|
}
|
||||||
|
policyJSON[repo]["sigstore"] = ""
|
||||||
|
registryNamespace := HaveMatchRegistry(repo, registryConfigs)
|
||||||
|
if registryNamespace != nil {
|
||||||
|
policyJSON[repo]["sigstore"] = registryNamespace.SigStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return policyJSON, nil
|
||||||
|
}
|
72
test/e2e/trust_test.go
Normal file
72
test/e2e/trust_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
. "github.com/containers/libpod/test/utils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Podman trust", func() {
|
||||||
|
var (
|
||||||
|
tempdir string
|
||||||
|
err error
|
||||||
|
podmanTest *PodmanTestIntegration
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
tempdir, err = CreateTempDirInTempDir()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
podmanTest = PodmanTestCreate(tempdir)
|
||||||
|
podmanTest.RestoreAllArtifacts()
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
podmanTest.Cleanup()
|
||||||
|
f := CurrentGinkgoTestDescription()
|
||||||
|
timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
|
||||||
|
GinkgoWriter.Write([]byte(timedResult))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman image trust show", func() {
|
||||||
|
path, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
session := podmanTest.Podman([]string{"image", "trust", "show", "--registrypath", filepath.Dir(path), "--policypath", filepath.Join(filepath.Dir(path), "policy.json")})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
outArray := session.OutputToStringArray()
|
||||||
|
Expect(len(outArray)).To(Equal(3))
|
||||||
|
Expect(outArray[0]).Should(ContainSubstring("accept"))
|
||||||
|
Expect(outArray[1]).Should(ContainSubstring("reject"))
|
||||||
|
Expect(outArray[2]).Should(ContainSubstring("signed"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman image trust set", func() {
|
||||||
|
path, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
session := podmanTest.Podman([]string{"image", "trust", "set", "--policypath", filepath.Join(filepath.Dir(path), "trust_set_test.json"), "-t", "accept", "default"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
var teststruct map[string][]map[string]string
|
||||||
|
policyContent, err := ioutil.ReadFile(filepath.Join(filepath.Dir(path), "trust_set_test.json"))
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(policyContent, &teststruct)
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
Expect(teststruct["default"][0]["type"]).To(Equal("insecureAcceptAnything"))
|
||||||
|
})
|
||||||
|
})
|
Reference in New Issue
Block a user