aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.packit.yaml18
-rw-r--r--Makefile2
-rw-r--r--cmd/podman/images/trust_set.go4
-rw-r--r--cmd/podman/parse/net.go9
-rw-r--r--cmd/podman/parse/parse.go18
-rw-r--r--cmd/podman/parse/parse_windows.go5
-rw-r--r--contrib/pkginstaller/Makefile14
-rw-r--r--docs/source/markdown/.gitignore2
-rw-r--r--docs/source/markdown/options/color.md3
-rw-r--r--docs/source/markdown/options/follow.md7
-rw-r--r--docs/source/markdown/options/names.md3
-rw-r--r--docs/source/markdown/options/since.md6
-rw-r--r--docs/source/markdown/options/tail.md4
-rw-r--r--docs/source/markdown/options/timestamps.md3
-rw-r--r--docs/source/markdown/options/until.md6
-rw-r--r--docs/source/markdown/podman-image-trust.1.md9
-rw-r--r--docs/source/markdown/podman-logs.1.md.in (renamed from docs/source/markdown/podman-logs.1.md)39
-rw-r--r--docs/source/markdown/podman-pod-logs.1.md.in (renamed from docs/source/markdown/podman-pod-logs.1.md)38
-rw-r--r--libpod/container_internal_unsupported.go8
-rw-r--r--libpod/define/exec_codes.go4
-rw-r--r--pkg/api/handlers/compat/events.go6
-rw-r--r--pkg/domain/infra/abi/trust.go141
-rw-r--r--pkg/machine/config.go2
-rw-r--r--pkg/systemd/notifyproxy/notifyproxy_test.go2
-rw-r--r--pkg/trust/config.go12
-rw-r--r--pkg/trust/policy.go248
-rw-r--r--pkg/trust/policy_test.go196
-rw-r--r--pkg/trust/registries.go126
-rw-r--r--pkg/trust/testdata/default.yaml25
-rw-r--r--pkg/trust/testdata/quay.io.yaml3
-rw-r--r--pkg/trust/testdata/redhat.yaml5
-rw-r--r--pkg/trust/trust.go296
-rw-r--r--pkg/trust/trust_test.go376
-rw-r--r--test/apiv2/10-images.at19
-rw-r--r--test/apiv2/12-imagesMore.at2
-rw-r--r--test/apiv2/20-containers.at20
-rw-r--r--test/apiv2/23-containersArchive.at96
-rwxr-xr-xtest/apiv2/test-apiv219
-rw-r--r--test/e2e/restart_test.go2
-rw-r--r--test/e2e/run_test.go2
-rw-r--r--test/system/200-pod.bats19
41 files changed, 1279 insertions, 540 deletions
diff --git a/.packit.yaml b/.packit.yaml
deleted file mode 100644
index 3d7b49297..000000000
--- a/.packit.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-# See the documentation for more information:
-# https://packit.dev/docs/configuration/
-
-upstream_package_name: podman
-downstream_package_name: podman
-
-actions:
- post-upstream-clone:
- - "curl -O https://src.fedoraproject.org/rpms/podman/raw/main/f/podman.spec"
-
-jobs:
- - job: production_build
- trigger: pull_request
- targets: &production_dist_targets
- - fedora-36
- - fedora-37
- - fedora-rawhide
- scratch: true
diff --git a/Makefile b/Makefile
index 4818ee122..d10c9cf19 100644
--- a/Makefile
+++ b/Makefile
@@ -267,7 +267,7 @@ test/version/version: version/version.go
.PHONY: codespell
codespell:
- codespell -S bin,vendor,.git,go.sum,.cirrus.yml,"RELEASE_NOTES.md,*.xz,*.gz,*.ps1,*.tar,swagger.yaml,*.tgz,bin2img,*ico,*.png,*.1,*.5,copyimg,*.orig,apidoc.go" -L pullrequest,uint,iff,od,seeked,splitted,marge,erro,hist,ether -w
+ codespell -S bin,vendor,.git,go.sum,.cirrus.yml,"RELEASE_NOTES.md,*.xz,*.gz,*.ps1,*.tar,swagger.yaml,*.tgz,bin2img,*ico,*.png,*.1,*.5,copyimg,*.orig,apidoc.go" -L clos,ans,pullrequest,uint,iff,od,seeked,splitted,marge,erro,hist,ether -w
.PHONY: validate
validate: lint .gitvalidation validate.completions man-page-check swagger-check tests-included tests-expect-exit pr-removes-fixed-skips
diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go
index 832e9f724..e7339f0b1 100644
--- a/cmd/podman/images/trust_set.go
+++ b/cmd/podman/images/trust_set.go
@@ -53,7 +53,7 @@ File(s) must exist before using this command`)
}
func setTrust(cmd *cobra.Command, args []string) error {
- validTrustTypes := []string{"accept", "insecureAcceptAnything", "reject", "signedBy"}
+ validTrustTypes := []string{"accept", "insecureAcceptAnything", "reject", "signedBy", "sigstoreSigned"}
valid, err := isValidImageURI(args[0])
if err != nil || !valid {
@@ -61,7 +61,7 @@ func setTrust(cmd *cobra.Command, args []string) error {
}
if !util.StringInSlice(setOptions.Type, validTrustTypes) {
- return fmt.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type)
+ return fmt.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy', 'sigstoreSigned')", setOptions.Type)
}
return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions)
}
diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go
index 9228c7127..a5c7a0d95 100644
--- a/cmd/podman/parse/net.go
+++ b/cmd/podman/parse/net.go
@@ -151,15 +151,6 @@ func parseEnvOrLabelFile(envOrLabel map[string]string, filename, configType stri
return scanner.Err()
}
-// ValidateFileName returns an error if filename contains ":"
-// as it is currently not supported
-func ValidateFileName(filename string) error {
- if strings.Contains(filename, ":") {
- return fmt.Errorf("invalid filename (should not contain ':') %q", filename)
- }
- return nil
-}
-
// ValidURL checks a string urlStr is a url or not
func ValidURL(urlStr string) error {
url, err := url.ParseRequestURI(urlStr)
diff --git a/cmd/podman/parse/parse.go b/cmd/podman/parse/parse.go
new file mode 100644
index 000000000..47db066d3
--- /dev/null
+++ b/cmd/podman/parse/parse.go
@@ -0,0 +1,18 @@
+//go:build !windows
+// +build !windows
+
+package parse
+
+import (
+ "fmt"
+ "strings"
+)
+
+// ValidateFileName returns an error if filename contains ":"
+// as it is currently not supported
+func ValidateFileName(filename string) error {
+ if strings.Contains(filename, ":") {
+ return fmt.Errorf("invalid filename (should not contain ':') %q", filename)
+ }
+ return nil
+}
diff --git a/cmd/podman/parse/parse_windows.go b/cmd/podman/parse/parse_windows.go
new file mode 100644
index 000000000..794f4216d
--- /dev/null
+++ b/cmd/podman/parse/parse_windows.go
@@ -0,0 +1,5 @@
+package parse
+
+func ValidateFileName(filename string) error {
+ return nil
+}
diff --git a/contrib/pkginstaller/Makefile b/contrib/pkginstaller/Makefile
index c84a08482..b7636fe14 100644
--- a/contrib/pkginstaller/Makefile
+++ b/contrib/pkginstaller/Makefile
@@ -1,7 +1,6 @@
SHELL := bash
ARCH ?= aarch64
-PODMAN_VERSION ?= 4.1.0
GVPROXY_VERSION ?= 0.4.0
QEMU_VERSION ?= 7.0.0-2
GVPROXY_RELEASE_URL ?= https://github.com/containers/gvisor-tap-vsock/releases/download/v$(GVPROXY_VERSION)/gvproxy-darwin
@@ -13,6 +12,9 @@ PKG_NAME := podman-installer-macos-$(ARCH).pkg
default: pkginstaller
+podman_version:
+ make -C ../../ test/version/version
+
$(TMP_DOWNLOAD)/gvproxy:
mkdir -p $(TMP_DOWNLOAD)
cd $(TMP_DOWNLOAD) && curl -sLo gvproxy $(GVPROXY_RELEASE_URL)
@@ -21,7 +23,7 @@ $(TMP_DOWNLOAD)/podman-machine-qemu-$(ARCH)-$(QEMU_VERSION).tar.xz:
mkdir -p $(TMP_DOWNLOAD)
cd $(TMP_DOWNLOAD) && curl -sLO $(QEMU_RELEASE_URL)
-packagedir: package_root Distribution welcome.html
+packagedir: podman_version package_root Distribution welcome.html
mkdir -p $(PACKAGE_DIR)
cp -r Resources $(PACKAGE_DIR)/
cp welcome.html $(PACKAGE_DIR)/Resources/
@@ -30,7 +32,7 @@ packagedir: package_root Distribution welcome.html
cp -r $(PACKAGE_ROOT) $(PACKAGE_DIR)/
cp package.sh $(PACKAGE_DIR)/
cd $(PACKAGE_DIR) && pkgbuild --analyze --root ./root component.plist
- echo -n $(PODMAN_VERSION) > $(PACKAGE_DIR)/VERSION
+ ../../test/version/version > $(PACKAGE_DIR)/VERSION
echo -n $(ARCH) > $(PACKAGE_DIR)/ARCH
cp ../../LICENSE $(PACKAGE_DIR)/Resources/LICENSE.txt
cp hvf.entitlements $(PACKAGE_DIR)/
@@ -41,8 +43,8 @@ package_root: clean-pkgroot $(TMP_DOWNLOAD)/podman-machine-qemu-$(ARCH)-$(QEMU_V
cp $(TMP_DOWNLOAD)/gvproxy $(PACKAGE_ROOT)/podman/bin/
chmod a+x $(PACKAGE_ROOT)/podman/bin/*
-%: %.in
- @sed -e 's/__VERSION__/'$(PODMAN_VERSION)'/g' $< >$@
+%: %.in podman_version
+ @sed -e 's/__VERSION__/'$(shell ../../test/version/version)'/g' $< >$@
pkginstaller: packagedir
cd $(PACKAGE_DIR) && ./package.sh ..
@@ -55,7 +57,7 @@ notarize: _notarize
.PHONY: clean clean-pkgroot
clean:
- rm -rf $(TMP_DOWNLOAD) $(PACKAGE_ROOT) $(PACKAGE_DIR) Distribution welcome.html
+ rm -rf $(TMP_DOWNLOAD) $(PACKAGE_ROOT) $(PACKAGE_DIR) Distribution welcome.html ../../test/version/version
clean-pkgroot:
rm -rf $(PACKAGE_ROOT) $(PACKAGE_DIR) Distribution welcome.html
diff --git a/docs/source/markdown/.gitignore b/docs/source/markdown/.gitignore
index 85aed3be0..8a0d553ba 100644
--- a/docs/source/markdown/.gitignore
+++ b/docs/source/markdown/.gitignore
@@ -9,11 +9,13 @@ podman-kill.1.md
podman-kube-play.1.md
podman-login.1.md
podman-logout.1.md
+podman-logs.1.md
podman-manifest-add.1.md
podman-manifest-push.1.md
podman-pause.1.md
podman-pod-clone.1.md
podman-pod-create.1.md
+podman-pod-logs.1.md
podman-pod-rm.1.md
podman-pod-start.1.md
podman-pod-stop.1.md
diff --git a/docs/source/markdown/options/color.md b/docs/source/markdown/options/color.md
new file mode 100644
index 000000000..343c79c88
--- /dev/null
+++ b/docs/source/markdown/options/color.md
@@ -0,0 +1,3 @@
+#### **--color**
+
+Output the containers with different colors in the log.
diff --git a/docs/source/markdown/options/follow.md b/docs/source/markdown/options/follow.md
new file mode 100644
index 000000000..75b65cf49
--- /dev/null
+++ b/docs/source/markdown/options/follow.md
@@ -0,0 +1,7 @@
+#### **--follow**, **-f**
+
+Follow log output. Default is false.
+
+Note: If you are following a <<container|pod>> which is removed by `podman <<container|pod>> rm`
+or removed on exit (`podman run --rm ...`), then there is a chance that the log
+file will be removed before `podman<< pod|>> logs` reads the final content.
diff --git a/docs/source/markdown/options/names.md b/docs/source/markdown/options/names.md
new file mode 100644
index 000000000..54fda40ee
--- /dev/null
+++ b/docs/source/markdown/options/names.md
@@ -0,0 +1,3 @@
+#### **--names**, **-n**
+
+Output the container names instead of the container IDs in the log.
diff --git a/docs/source/markdown/options/since.md b/docs/source/markdown/options/since.md
new file mode 100644
index 000000000..9f20722df
--- /dev/null
+++ b/docs/source/markdown/options/since.md
@@ -0,0 +1,6 @@
+#### **--since**=*TIMESTAMP*
+
+Show logs since TIMESTAMP. The --since option can be Unix timestamps, date formatted timestamps, or Go duration
+strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
+time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
+and 2006-01-02.
diff --git a/docs/source/markdown/options/tail.md b/docs/source/markdown/options/tail.md
new file mode 100644
index 000000000..463b6fc3f
--- /dev/null
+++ b/docs/source/markdown/options/tail.md
@@ -0,0 +1,4 @@
+#### **--tail**=*LINES*
+
+Output the specified number of LINES at the end of the logs. LINES must be an integer. Defaults to -1,
+which prints all lines
diff --git a/docs/source/markdown/options/timestamps.md b/docs/source/markdown/options/timestamps.md
new file mode 100644
index 000000000..a449216aa
--- /dev/null
+++ b/docs/source/markdown/options/timestamps.md
@@ -0,0 +1,3 @@
+#### **--timestamps**, **-t**
+
+Show timestamps in the log outputs. The default is false
diff --git a/docs/source/markdown/options/until.md b/docs/source/markdown/options/until.md
new file mode 100644
index 000000000..d656d976b
--- /dev/null
+++ b/docs/source/markdown/options/until.md
@@ -0,0 +1,6 @@
+#### **--until**=*TIMESTAMP*
+
+Show logs until TIMESTAMP. The --until option can be Unix timestamps, date formatted timestamps, or Go duration
+strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
+time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
+and 2006-01-02.
diff --git a/docs/source/markdown/podman-image-trust.1.md b/docs/source/markdown/podman-image-trust.1.md
index 4e80bdcf5..2a7da82cc 100644
--- a/docs/source/markdown/podman-image-trust.1.md
+++ b/docs/source/markdown/podman-image-trust.1.md
@@ -32,7 +32,8 @@ Trust **type** provides a way to:
Allowlist ("accept") or
Denylist ("reject") registries or
-Require signature (“signedBy”).
+Require a simple signing signature (“signedBy”),
+Require a sigstore signature ("sigstoreSigned").
Trust may be updated using the command **podman image trust set** for an existing trust scope.
@@ -45,12 +46,14 @@ Trust may be updated using the command **podman image trust set** for an existin
#### **--pubkeysfile**, **-f**=*KEY1*
A path to an exported public key on the local system. Key paths
will be referenced in policy.json. Any path to a file may be used but locating the file in **/etc/pki/containers** is recommended. Options may be used multiple times to
- require an image be signed by multiple keys. The **--pubkeysfile** option is required for the **signedBy** type.
+ require an image be signed by multiple keys. The **--pubkeysfile** option is required for the **signedBy** and **sigstoreSigned** types.
#### **--type**, **-t**=*value*
The trust type for this policy entry.
Accepted values:
- **signedBy** (default): Require signatures with corresponding list of
+ **signedBy** (default): Require simple signing signatures with corresponding list of
+ public keys
+ **sigstoreSigned**: Require sigstore signatures with corresponding list of
public keys
**accept**: do not require any signatures for this
registry scope
diff --git a/docs/source/markdown/podman-logs.1.md b/docs/source/markdown/podman-logs.1.md.in
index 6ce6c3812..7b0c45cf0 100644
--- a/docs/source/markdown/podman-logs.1.md
+++ b/docs/source/markdown/podman-logs.1.md.in
@@ -15,49 +15,24 @@ any logs at the time you execute podman logs).
## OPTIONS
-#### **--color**
+@@option color
-Output the containers with different colors in the log.
-
-#### **--follow**, **-f**
-
-Follow log output. Default is false.
-
-Note: If you are following a container which is removed `podman container rm`
-or removed on exit `podman run --rm ...`, then there is a chance that the log
-file will be removed before `podman logs` reads the final content.
+@@option follow
#### **--latest**, **-l**
Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
to run containers such as CRI-O, the last started container could be from either of those methods. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines)
-#### **--names**, **-n**
-
-Output the container name in the log
-
-#### **--since**=*TIMESTAMP*
-
-Show logs since TIMESTAMP. The --since option can be Unix timestamps, date formatted timestamps, or Go duration
-strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
-time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
-and 2006-01-02.
-
-#### **--tail**=*LINES*
-
-Output the specified number of LINES at the end of the logs. LINES must be an integer. Defaults to -1,
-which prints all lines
+@@option names
-#### **--timestamps**, **-t**
+@@option since
-Show timestamps in the log outputs. The default is false
+@@option tail
-#### **--until**=*TIMESTAMP*
+@@option timestamps
-Show logs until TIMESTAMP. The --until option can be Unix timestamps, date formatted timestamps, or Go duration
-strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
-time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
-and 2006-01-02.
+@@option until
## EXAMPLE
diff --git a/docs/source/markdown/podman-pod-logs.1.md b/docs/source/markdown/podman-pod-logs.1.md.in
index 7fc1d4b90..391f620f8 100644
--- a/docs/source/markdown/podman-pod-logs.1.md
+++ b/docs/source/markdown/podman-pod-logs.1.md.in
@@ -13,51 +13,27 @@ Note: Long running command of `podman pod log` with a `-f` or `--follow` needs t
## OPTIONS
-#### **--color**
-
-Output the containers with different colors in the log.
+@@option color
#### **--container**, **-c**
By default `podman pod logs` retrieves logs for all the containers available within the pod differentiate by field `container`. However there are use-cases where user would want to limit the log stream only to a particular container of a pod for such cases `-c` can be used like `podman pod logs -c ctrNameorID podname`.
-#### **--follow**, **-f**
-
-Follow log output. Default is false.
-
-Note: If you are following a pod which is removed `podman pod rm`, then there is a
-chance that the log file will be removed before `podman pod logs` reads the final content.
+@@option follow
#### **--latest**, **-l**
Instead of providing the pod name or id, get logs of the last created pod. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines)
-#### **--names**, **-n**
-
-Output the container names instead of the container IDs in the log.
-
-#### **--since**=*TIMESTAMP*
-
-Show logs since TIMESTAMP. The --since option can be Unix timestamps, date formatted timestamps, or Go duration
-strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
-time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
-and 2006-01-02.
-
-#### **--tail**=*LINES*
-
-Output the specified number of LINES at the end of the logs. LINES must be an integer. Defaults to -1,
-which prints all lines
+@@option names
-#### **--timestamps**, **-t**
+@@option since
-Show timestamps in the log outputs. The default is false
+@@option tail
-#### **--until**=*TIMESTAMP*
+@@option timestamps
-Show logs until TIMESTAMP. The --until option can be Unix timestamps, date formatted timestamps, or Go duration
-strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
-time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
-and 2006-01-02.
+@@option until
## EXAMPLE
diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go
index de92ff260..074aeee47 100644
--- a/libpod/container_internal_unsupported.go
+++ b/libpod/container_internal_unsupported.go
@@ -69,21 +69,21 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// getHostsEntries returns the container ip host entries for the correct netmode
func (c *Container) getHostsEntries() (etchosts.HostEntries, error) {
- return nil, errors.New("unspported (*Container) getHostsEntries")
+ return nil, errors.New("unsupported (*Container) getHostsEntries")
}
// Fix ownership and permissions of the specified volume if necessary.
func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error {
- return errors.New("unspported (*Container) fixVolumePermissions")
+ return errors.New("unsupported (*Container) fixVolumePermissions")
}
func (c *Container) expectPodCgroup() (bool, error) {
- return false, errors.New("unspported (*Container) expectPodCgroup")
+ return false, errors.New("unsupported (*Container) expectPodCgroup")
}
// Get cgroup path in a format suitable for the OCI spec
func (c *Container) getOCICgroupPath() (string, error) {
- return "", errors.New("unspported (*Container) getOCICgroupPath")
+ return "", errors.New("unsupported (*Container) getOCICgroupPath")
}
func getLocalhostHostEntry(c *Container) etchosts.HostEntries {
diff --git a/libpod/define/exec_codes.go b/libpod/define/exec_codes.go
index 3f2da4910..a84730e72 100644
--- a/libpod/define/exec_codes.go
+++ b/libpod/define/exec_codes.go
@@ -11,8 +11,8 @@ const (
// ExecErrorCodeGeneric is the default error code to return from an exec session if libpod failed
// prior to calling the runtime
ExecErrorCodeGeneric = 125
- // ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command
- // an example of this can be found by trying to execute a directory:
+ // ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command.
+ // An example of this can be found by trying to execute a directory:
// `podman exec -l /etc`
ExecErrorCodeCannotInvoke = 126
// ExecErrorCodeNotFound is the error code to return when a command cannot be found
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 18fb35966..105404a0d 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -89,6 +89,12 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
}
e := entities.ConvertToEntitiesEvent(*evt)
+ // Some events differ between Libpod and Docker endpoints.
+ // Handle these differences for Docker-compat.
+ if !utils.IsLibpodRequest(r) && e.Type == "image" && e.Status == "remove" {
+ e.Status = "delete"
+ e.Action = "delete"
+ }
if !utils.IsLibpodRequest(r) && e.Status == "died" {
e.Status = "die"
e.Action = "die"
diff --git a/pkg/domain/infra/abi/trust.go b/pkg/domain/infra/abi/trust.go
index 0e3d8fad9..c58ddff06 100644
--- a/pkg/domain/infra/abi/trust.go
+++ b/pkg/domain/infra/abi/trust.go
@@ -2,16 +2,11 @@ package abi
import (
"context"
- "encoding/json"
- "errors"
"fmt"
"io/ioutil"
- "os"
- "strings"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/trust"
- "github.com/sirupsen/logrus"
)
func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options entities.ShowTrustOptions) (*entities.ShowTrustReport, error) {
@@ -34,11 +29,7 @@ func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options ent
if len(options.RegistryPath) > 0 {
report.SystemRegistriesDirPath = options.RegistryPath
}
- policyContentStruct, err := trust.GetPolicy(policyPath)
- if err != nil {
- return nil, fmt.Errorf("could not read trust policies: %w", err)
- }
- report.Policies, err = getPolicyShowOutput(policyContentStruct, report.SystemRegistriesDirPath)
+ report.Policies, err = trust.PolicyDescription(policyPath, report.SystemRegistriesDirPath)
if err != nil {
return nil, fmt.Errorf("could not show trust policies: %w", err)
}
@@ -46,133 +37,19 @@ func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options ent
}
func (ir *ImageEngine) SetTrust(ctx context.Context, args []string, options entities.SetTrustOptions) error {
- 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'")
+ if len(args) != 1 {
+ return fmt.Errorf("SetTrust called with unexpected %d args", len(args))
}
+ scope := args[0]
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 args[0] == "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[args[0]]
- if registryExists {
- policyContentStruct.Transports[transport][args[0]] = 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"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...)
- }
- }
-
- data, err := json.MarshalIndent(policyContentStruct, "", " ")
- if err != nil {
- return fmt.Errorf("error setting trust policy: %w", err)
- }
- return ioutil.WriteFile(policyPath, data, 0644)
-}
-
-func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]*trust.Policy, error) {
- var output []*trust.Policy
-
- registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath)
- if err != nil {
- return nil, err
- }
-
- if len(policyContentStruct.Default) > 0 {
- defaultPolicyStruct := trust.Policy{
- Transport: "all",
- Name: "* (default)",
- RepoName: "default",
- Type: trustTypeDescription(policyContentStruct.Default[0].Type),
- }
- output = append(output, &defaultPolicyStruct)
- }
- for transport, transval := range policyContentStruct.Transports {
- if transport == "docker" {
- transport = "repository"
- }
- for repo, repoval := range transval {
- tempTrustShowOutput := trust.Policy{
- Name: repo,
- RepoName: repo,
- Transport: transport,
- Type: trustTypeDescription(repoval[0].Type),
- }
- // TODO - keyarr is not used and I don't know its intent; commenting out for now for someone to fix later
- // keyarr := []string{}
- uids := []string{}
- for _, repoele := range repoval {
- if len(repoele.KeyPath) > 0 {
- // keyarr = append(keyarr, repoele.KeyPath)
- uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...)
- }
- if len(repoele.KeyData) > 0 {
- // keyarr = append(keyarr, string(repoele.KeyData))
- uids = append(uids, trust.GetGPGIdFromKeyData(repoele.KeyData)...)
- }
- }
- tempTrustShowOutput.GPGId = strings.Join(uids, ", ")
-
- registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs)
- if registryNamespace != nil {
- tempTrustShowOutput.SignatureStore = registryNamespace.SigStore
- }
- output = append(output, &tempTrustShowOutput)
- }
- }
- return output, 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
+ return trust.AddPolicyEntries(policyPath, trust.AddPolicyEntriesInput{
+ Scope: scope,
+ Type: options.Type,
+ PubKeyFiles: options.PubKeysFile,
+ })
}
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 5162006db..54aa9e1b7 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -175,7 +175,7 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url
return uri
}
-// GetCacheDir returns the dir where VM images are downladed into when pulled
+// GetCacheDir returns the dir where VM images are downloaded into when pulled
func GetCacheDir(vmType string) (string, error) {
dataDir, err := GetDataDir(vmType)
if err != nil {
diff --git a/pkg/systemd/notifyproxy/notifyproxy_test.go b/pkg/systemd/notifyproxy/notifyproxy_test.go
index edad95659..ce63fc9cd 100644
--- a/pkg/systemd/notifyproxy/notifyproxy_test.go
+++ b/pkg/systemd/notifyproxy/notifyproxy_test.go
@@ -37,7 +37,7 @@ func TestWaitAndClose(t *testing.T) {
time.Sleep(250 * time.Millisecond)
select {
case err := <-ch:
- t.Fatalf("Should stil be waiting but received %v", err)
+ t.Fatalf("Should still be waiting but received %v", err)
default:
}
diff --git a/pkg/trust/config.go b/pkg/trust/config.go
deleted file mode 100644
index 6186d4cbd..000000000
--- a/pkg/trust/config.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package trust
-
-// Policy describes a basic trust policy configuration
-type Policy struct {
- Transport string `json:"transport"`
- Name string `json:"name,omitempty"`
- RepoName string `json:"repo_name,omitempty"`
- Keys []string `json:"keys,omitempty"`
- SignatureStore string `json:"sigstore,omitempty"`
- Type string `json:"type"`
- GPGId string `json:"gpg_id,omitempty"`
-}
diff --git a/pkg/trust/policy.go b/pkg/trust/policy.go
new file mode 100644
index 000000000..326fe17af
--- /dev/null
+++ b/pkg/trust/policy.go
@@ -0,0 +1,248 @@
+package trust
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/image/v5/types"
+ "github.com/sirupsen/logrus"
+)
+
+// policyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy)
+type policyContent struct {
+ Default []repoContent `json:"default"`
+ Transports transportsContent `json:"transports,omitempty"`
+}
+
+// transportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports)
+type transportsContent map[string]repoMap
+
+// repoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes)
+type repoMap map[string][]repoContent
+
+// repoContent is a single policy requirement (one of possibly several for a scope), representing all of the individual alternatives in a single merged struct
+// (= c/image/v5/signature.{PolicyRequirement,pr*})
+type repoContent struct {
+ Type string `json:"type"`
+ KeyType string `json:"keyType,omitempty"`
+ KeyPath string `json:"keyPath,omitempty"`
+ KeyPaths []string `json:"keyPaths,omitempty"`
+ KeyData string `json:"keyData,omitempty"`
+ SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"`
+}
+
+// genericPolicyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy), using generic data for individual requirements.
+type genericPolicyContent struct {
+ Default json.RawMessage `json:"default"`
+ Transports genericTransportsContent `json:"transports,omitempty"`
+}
+
+// genericTransportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports), using generic data for individual requirements.
+type genericTransportsContent map[string]genericRepoMap
+
+// genericRepoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes)
+type genericRepoMap map[string]json.RawMessage
+
+// 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
+}
+
+// gpgIDReader returns GPG key IDs of keys stored at the provided path.
+// It exists only for tests, production code should always use getGPGIdFromKeyPath.
+type gpgIDReader func(string) []string
+
+// 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
+}
+
+// getGPGIdFromKeyPath returns GPG key IDs of keys stored at the provided path.
+func getGPGIdFromKeyPath(path string) []string {
+ cmd := exec.Command("gpg2", "--with-colons", path)
+ results, err := cmd.Output()
+ if err != nil {
+ logrus.Errorf("Getting key identity: %s", err)
+ return nil
+ }
+ return parseUids(results)
+}
+
+// getGPGIdFromKeyData returns GPG key IDs of keys in the provided keyring.
+func getGPGIdFromKeyData(idReader gpgIDReader, key string) []string {
+ decodeKey, err := base64.StdEncoding.DecodeString(key)
+ if err != nil {
+ logrus.Errorf("%s, error decoding key data", err)
+ return nil
+ }
+ tmpfileName, err := createTmpFile("", "", decodeKey)
+ if err != nil {
+ logrus.Errorf("Creating key date temp file %s", err)
+ }
+ defer os.Remove(tmpfileName)
+ return idReader(tmpfileName)
+}
+
+func parseUids(colonDelimitKeys []byte) []string {
+ var parseduids []string
+ scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys))
+ 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 parseduids
+}
+
+// getPolicy parses policy.json into policyContent.
+func getPolicy(policyPath string) (policyContent, error) {
+ var policyContentStruct policyContent
+ policyContent, err := ioutil.ReadFile(policyPath)
+ if err != nil {
+ return policyContentStruct, fmt.Errorf("unable to read policy file: %w", err)
+ }
+ if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
+ return policyContentStruct, fmt.Errorf("could not parse trust policies from %s: %w", policyPath, err)
+ }
+ return policyContentStruct, nil
+}
+
+var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "sigstoreSigned": "sigstoreSigned", "reject": "reject"}
+
+func trustTypeDescription(trustType string) string {
+ trustDescription, exist := typeDescription[trustType]
+ if !exist {
+ logrus.Warnf("Invalid trust type %s", trustType)
+ }
+ return trustDescription
+}
+
+// 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 genericPolicyContent
+ newReposContent []repoContent
+ )
+ trustType := input.Type
+ if trustType == "accept" {
+ trustType = "insecureAcceptAnything"
+ }
+ pubkeysfile := input.PubKeyFiles
+
+ // The error messages in validation failures use input.Type instead of trustType to match the user’s input.
+ switch trustType {
+ case "insecureAcceptAnything", "reject":
+ if len(pubkeysfile) != 0 {
+ return fmt.Errorf("%d public keys unexpectedly provided for trust type %v", len(pubkeysfile), input.Type)
+ }
+ newReposContent = append(newReposContent, repoContent{Type: trustType})
+
+ case "signedBy":
+ if len(pubkeysfile) == 0 {
+ return errors.New("at least one public key must be defined for type 'signedBy'")
+ }
+ for _, filepath := range pubkeysfile {
+ newReposContent = append(newReposContent, repoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath})
+ }
+
+ case "sigstoreSigned":
+ if len(pubkeysfile) == 0 {
+ return errors.New("at least one public key must be defined for type 'sigstoreSigned'")
+ }
+ for _, filepath := range pubkeysfile {
+ newReposContent = append(newReposContent, repoContent{Type: trustType, KeyPath: filepath})
+ }
+
+ default:
+ return fmt.Errorf("unknown trust type %q", input.Type)
+ }
+ newReposJSON, err := json.Marshal(newReposContent)
+ if err != nil {
+ return err
+ }
+
+ _, 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 input.Scope == "default" {
+ policyContentStruct.Default = json.RawMessage(newReposJSON)
+ } 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] = json.RawMessage(newReposJSON)
+ break
+ }
+ }
+ if !registryExists {
+ if policyContentStruct.Transports == nil {
+ policyContentStruct.Transports = make(map[string]genericRepoMap)
+ }
+ if policyContentStruct.Transports["docker"] == nil {
+ policyContentStruct.Transports["docker"] = make(map[string]json.RawMessage)
+ }
+ policyContentStruct.Transports["docker"][input.Scope] = json.RawMessage(newReposJSON)
+ }
+ }
+
+ 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..3952b72c3
--- /dev/null
+++ b/pkg/trust/policy_test.go
@@ -0,0 +1,196 @@
+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)
+
+ // Invalid input:
+ for _, invalid := range []AddPolicyEntriesInput{
+ {
+ Scope: "default",
+ Type: "accept",
+ PubKeyFiles: []string{"/does-not-make-sense"},
+ },
+ {
+ Scope: "default",
+ Type: "insecureAcceptAnything",
+ PubKeyFiles: []string{"/does-not-make-sense"},
+ },
+ {
+ Scope: "default",
+ Type: "reject",
+ PubKeyFiles: []string{"/does-not-make-sense"},
+ },
+ {
+ Scope: "default",
+ Type: "signedBy",
+ PubKeyFiles: []string{}, // A key is missing
+ },
+ {
+ Scope: "default",
+ Type: "sigstoreSigned",
+ PubKeyFiles: []string{}, // A key is missing
+ },
+ {
+ Scope: "default",
+ Type: "this-is-unknown",
+ PubKeyFiles: []string{},
+ },
+ } {
+ err := AddPolicyEntries(policyPath, invalid)
+ assert.Error(t, err, "%#v", invalid)
+ }
+
+ 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)
+ err = AddPolicyEntries(policyPath, AddPolicyEntriesInput{
+ Scope: "quay.io/sigstore-signed",
+ Type: "sigstoreSigned",
+ 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()),
+ },
+ "quay.io/sigstore-signed": {
+ xNewPRSigstoreSignedKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSigstoreSignedKeyPath(t, "/2.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ },
+ },
+ }, parsedPolicy)
+
+ // Test that completely unknown JSON is preserved
+ jsonWithUnknownData := `{
+ "default": [
+ {
+ "type": "this is unknown",
+ "unknown field": "should be preserved"
+ }
+ ],
+ "transports":
+ {
+ "docker-daemon":
+ {
+ "": [{
+ "type":"this is unknown 2",
+ "unknown field 2": "should be preserved 2"
+ }]
+ }
+ }
+}`
+ err = os.WriteFile(policyPath, []byte(jsonWithUnknownData), 0600)
+ require.NoError(t, err)
+ err = AddPolicyEntries(policyPath, AddPolicyEntriesInput{
+ Scope: "quay.io/innocuous",
+ Type: "signedBy",
+ PubKeyFiles: []string{"/1.pub"},
+ })
+ require.NoError(t, err)
+ updatedJSONWithUnknownData, err := os.ReadFile(policyPath)
+ require.NoError(t, err)
+ // Decode updatedJSONWithUnknownData so that this test does not depend on details of the encoding.
+ // To reduce noise in the constants below:
+ type a = []interface{}
+ type m = map[string]interface{}
+ var parsedUpdatedJSON m
+ err = json.Unmarshal(updatedJSONWithUnknownData, &parsedUpdatedJSON)
+ require.NoError(t, err)
+ assert.Equal(t, m{
+ "default": a{
+ m{
+ "type": "this is unknown",
+ "unknown field": "should be preserved",
+ },
+ },
+ "transports": m{
+ "docker-daemon": m{
+ "": a{
+ m{
+ "type": "this is unknown 2",
+ "unknown field 2": "should be preserved 2",
+ },
+ },
+ },
+ "docker": m{
+ "quay.io/innocuous": a{
+ m{
+ "type": "signedBy",
+ "keyType": "GPGKeys",
+ "keyPath": "/1.pub",
+ },
+ },
+ },
+ },
+ }, parsedUpdatedJSON)
+}
+
+// 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
+}
+
+// xNewPRSignedByKeyPaths is a wrapper for NewPRSignedByKeyPaths which must not fail.
+func xNewPRSignedByKeyPaths(t *testing.T, keyPaths []string, signedIdentity signature.PolicyReferenceMatch) signature.PolicyRequirement {
+ pr, err := signature.NewPRSignedByKeyPaths(signature.SBKeyTypeGPGKeys, keyPaths, signedIdentity)
+ require.NoError(t, err)
+ return pr
+}
+
+// xNewPRSigstoreSignedKeyPath is a wrapper for NewPRSigstoreSignedKeyPath which must not fail.
+func xNewPRSigstoreSignedKeyPath(t *testing.T, keyPath string, signedIdentity signature.PolicyReferenceMatch) signature.PolicyRequirement {
+ pr, err := signature.NewPRSigstoreSignedKeyPath(keyPath, signedIdentity)
+ require.NoError(t, err)
+ return pr
+}
diff --git a/pkg/trust/registries.go b/pkg/trust/registries.go
new file mode 100644
index 000000000..0adc38232
--- /dev/null
+++ b/pkg/trust/registries.go
@@ -0,0 +1,126 @@
+package trust
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/image/v5/types"
+ "github.com/docker/docker/pkg/homedir"
+ "github.com/ghodss/yaml"
+)
+
+// 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 {
+ Lookaside string `json:"lookaside"` // For reading, and if LookasideStaging is not present, for writing.
+ LookasideStaging string `json:"lookaside-staging"` // For writing only.
+ SigStore string `json:"sigstore"` // For reading, and if SigStoreStaging is not present, for writing.
+ SigStoreStaging string `json:"sigstore-staging"` // For writing only.
+}
+
+// systemRegistriesDirPath is the path to registries.d.
+const systemRegistriesDirPath = "/etc/containers/registries.d"
+
+// userRegistriesDir is the path to the per user registries.d.
+var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d")
+
+// RegistriesDirPath returns a path to registries.d
+func RegistriesDirPath(sys *types.SystemContext) string {
+ if sys != nil && sys.RegistriesDirPath != "" {
+ return sys.RegistriesDirPath
+ }
+ userRegistriesDirPath := filepath.Join(homedir.Get(), userRegistriesDir)
+ if _, err := os.Stat(userRegistriesDirPath); err == nil {
+ return userRegistriesDirPath
+ }
+ if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
+ return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
+ }
+
+ return systemRegistriesDirPath
+}
+
+// loadAndMergeConfig loads registries.d 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, fmt.Errorf("error parsing %s: %w", configPath, err)
+ }
+ if config.DefaultDocker != nil {
+ if mergedConfig.DefaultDocker != nil {
+ return nil, fmt.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, fmt.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
+}
+
+// registriesDConfigurationForScope returns registries.d configuration for the provided scope.
+// scope can be "" to return only the global default configuration entry.
+func registriesDConfigurationForScope(registryConfigs *registryConfiguration, scope string) *registryNamespace {
+ searchScope := scope
+ if searchScope != "" {
+ if !strings.Contains(searchScope, "/") {
+ val, exists := registryConfigs.Docker[searchScope]
+ if exists {
+ return &val
+ }
+ }
+ for range strings.Split(scope, "/") {
+ val, exists := registryConfigs.Docker[searchScope]
+ if exists {
+ return &val
+ }
+ if strings.Contains(searchScope, "/") {
+ searchScope = searchScope[:strings.LastIndex(searchScope, "/")]
+ }
+ }
+ }
+ return registryConfigs.DefaultDocker
+}
diff --git a/pkg/trust/testdata/default.yaml b/pkg/trust/testdata/default.yaml
new file mode 100644
index 000000000..31bcd35ef
--- /dev/null
+++ b/pkg/trust/testdata/default.yaml
@@ -0,0 +1,25 @@
+# This is a default registries.d configuration file. You may
+# add to this file or create additional files in registries.d/.
+#
+# lookaside: indicates a location that is read and write
+# lookaside-staging: indicates a location that is only for write
+#
+# lookaside and lookaside-staging take a value of the following:
+# lookaside: {schema}://location
+#
+# For reading signatures, schema may be http, https, or file.
+# For writing signatures, schema may only be file.
+
+# This is the default signature write location for docker registries.
+default-docker:
+# lookaside: file:///var/lib/containers/sigstore
+ lookaside-staging: file:///var/lib/containers/sigstore
+
+# The 'docker' indicator here is the start of the configuration
+# for docker registries.
+#
+# docker:
+#
+# privateregistry.com:
+# lookaside: http://privateregistry.com/sigstore/
+# lookaside-staging: /mnt/nfs/privateregistry/sigstore
diff --git a/pkg/trust/testdata/quay.io.yaml b/pkg/trust/testdata/quay.io.yaml
new file mode 100644
index 000000000..80071596d
--- /dev/null
+++ b/pkg/trust/testdata/quay.io.yaml
@@ -0,0 +1,3 @@
+docker:
+ quay.io/multi-signed:
+ lookaside: https://quay.example.com/sigstore
diff --git a/pkg/trust/testdata/redhat.yaml b/pkg/trust/testdata/redhat.yaml
new file mode 100644
index 000000000..8e40a4174
--- /dev/null
+++ b/pkg/trust/testdata/redhat.yaml
@@ -0,0 +1,5 @@
+docker:
+ registry.redhat.io:
+ sigstore: https://registry.redhat.io/containers/sigstore
+ registry.access.redhat.com:
+ sigstore: https://registry.redhat.io/containers/sigstore
diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go
index 663a1b5e2..07d144bc1 100644
--- a/pkg/trust/trust.go
+++ b/pkg/trust/trust.go
@@ -1,243 +1,127 @@
package trust
import (
- "bufio"
- "bytes"
- "encoding/base64"
- "encoding/json"
"fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
+ "sort"
"strings"
-
- "github.com/containers/image/v5/types"
- "github.com/docker/docker/pkg/homedir"
- "github.com/ghodss/yaml"
- "github.com/sirupsen/logrus"
)
-// PolicyContent struct for policy.json file
-type PolicyContent struct {
- Default []RepoContent `json:"default"`
- Transports TransportsContent `json:"transports,omitempty"`
-}
-
-// 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"`
+// Policy describes a basic trust policy configuration
+type Policy struct {
+ Transport string `json:"transport"`
+ Name string `json:"name,omitempty"`
+ RepoName string `json:"repo_name,omitempty"`
+ Keys []string `json:"keys,omitempty"`
+ SignatureStore string `json:"sigstore,omitempty"`
+ Type string `json:"type"`
+ GPGId string `json:"gpg_id,omitempty"`
}
-// 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.
+// PolicyDescription returns an user-focused description of the policy in policyPath and registries.d data from registriesDirPath.
+func PolicyDescription(policyPath, registriesDirPath string) ([]*Policy, error) {
+ return policyDescriptionWithGPGIDReader(policyPath, registriesDirPath, getGPGIdFromKeyPath)
}
-// ShowOutput keep the fields for image trust show command
-type ShowOutput struct {
- Repo string
- Trusttype string
- GPGid string
- Sigstore string
-}
-
-// systemRegistriesDirPath is the path to registries.d.
-const systemRegistriesDirPath = "/etc/containers/registries.d"
-
-// userRegistriesDir is the path to the per user registries.d.
-var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d")
-
-// 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 {
- if sys != nil && sys.RegistriesDirPath != "" {
- return sys.RegistriesDirPath
- }
- userRegistriesDirPath := filepath.Join(homedir.Get(), userRegistriesDir)
- if _, err := os.Stat(userRegistriesDirPath); err == nil {
- return userRegistriesDirPath
+// policyDescriptionWithGPGIDReader is PolicyDescription with a gpgIDReader parameter. It exists only to make testing easier.
+func policyDescriptionWithGPGIDReader(policyPath, registriesDirPath string, idReader gpgIDReader) ([]*Policy, error) {
+ policyContentStruct, err := getPolicy(policyPath)
+ if err != nil {
+ return nil, fmt.Errorf("could not read trust policies: %w", err)
}
- if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
- return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
+ res, err := getPolicyShowOutput(policyContentStruct, registriesDirPath, idReader)
+ if err != nil {
+ return nil, fmt.Errorf("could not show trust policies: %w", err)
}
-
- return systemRegistriesDirPath
+ return res, nil
}
-// LoadAndMergeConfig loads configuration files in dirPath
-func LoadAndMergeConfig(dirPath string) (*RegistryConfiguration, error) {
- mergedConfig := RegistryConfiguration{Docker: map[string]RegistryNamespace{}}
- dockerDefaultMergedFrom := ""
- nsMergedFrom := map[string]string{}
+func getPolicyShowOutput(policyContentStruct policyContent, systemRegistriesDirPath string, idReader gpgIDReader) ([]*Policy, error) {
+ var output []*Policy
- dir, err := os.Open(dirPath)
+ registryConfigs, err := loadAndMergeConfig(systemRegistriesDirPath)
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
+
+ if len(policyContentStruct.Default) > 0 {
+ template := Policy{
+ Transport: "all",
+ Name: "* (default)",
+ RepoName: "default",
}
- var config RegistryConfiguration
- err = yaml.Unmarshal(configBytes, &config)
- if err != nil {
- return nil, fmt.Errorf("error parsing %s: %w", configPath, err)
+ output = append(output, descriptionsOfPolicyRequirements(policyContentStruct.Default, template, registryConfigs, "", idReader)...)
+ }
+ // FIXME: This should use x/exp/maps.Keys after we update to Go 1.18.
+ transports := []string{}
+ for t := range policyContentStruct.Transports {
+ transports = append(transports, t)
+ }
+ sort.Strings(transports)
+ for _, transport := range transports {
+ transval := policyContentStruct.Transports[transport]
+ if transport == "docker" {
+ transport = "repository"
}
- if config.DefaultDocker != nil {
- if mergedConfig.DefaultDocker != nil {
- return nil, fmt.Errorf(`error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
- dockerDefaultMergedFrom, configPath)
- }
- mergedConfig.DefaultDocker = config.DefaultDocker
- dockerDefaultMergedFrom = configPath
+
+ // FIXME: This should use x/exp/maps.Keys after we update to Go 1.18.
+ scopes := []string{}
+ for s := range transval {
+ scopes = append(scopes, s)
}
- for nsName, nsConfig := range config.Docker { // includes config.Docker == nil
- if _, ok := mergedConfig.Docker[nsName]; ok {
- return nil, fmt.Errorf(`error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
- nsName, nsMergedFrom[nsName], configPath)
+ sort.Strings(scopes)
+ for _, repo := range scopes {
+ repoval := transval[repo]
+ template := Policy{
+ Transport: transport,
+ Name: repo,
+ RepoName: repo,
}
- mergedConfig.Docker[nsName] = nsConfig
- nsMergedFrom[nsName] = configPath
+ output = append(output, descriptionsOfPolicyRequirements(repoval, template, registryConfigs, repo, idReader)...)
}
}
- return &mergedConfig, nil
+ return output, nil
}
-// HaveMatchRegistry checks if trust settings for the registry have been configured 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, "/")]
+// descriptionsOfPolicyRequirements turns reqs into user-readable policy entries, with Transport/Name/Reponame coming from template, potentially looking up scope (which may be "") in registryConfigs.
+func descriptionsOfPolicyRequirements(reqs []repoContent, template Policy, registryConfigs *registryConfiguration, scope string, idReader gpgIDReader) []*Policy {
+ res := []*Policy{}
+
+ var lookasidePath string
+ registryNamespace := registriesDConfigurationForScope(registryConfigs, scope)
+ if registryNamespace != nil {
+ if registryNamespace.Lookaside != "" {
+ lookasidePath = registryNamespace.Lookaside
+ } else { // incl. registryNamespace.SigStore == ""
+ lookasidePath = registryNamespace.SigStore
}
}
- return registryConfigs.DefaultDocker
-}
-
-// 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
-}
-
-// GetGPGIdFromKeyPath return user keyring from key path
-func GetGPGIdFromKeyPath(path string) []string {
- cmd := exec.Command("gpg2", "--with-colons", path)
- results, err := cmd.Output()
- if err != nil {
- logrus.Errorf("Getting key identity: %s", err)
- return nil
- }
- return parseUids(results)
-}
-// GetGPGIdFromKeyData return user keyring from keydata
-func GetGPGIdFromKeyData(key string) []string {
- decodeKey, err := base64.StdEncoding.DecodeString(key)
- if err != nil {
- logrus.Errorf("%s, error decoding key data", err)
- return nil
- }
- tmpfileName, err := CreateTmpFile("", "", decodeKey)
- if err != nil {
- logrus.Errorf("Creating key date temp file %s", err)
- }
- defer os.Remove(tmpfileName)
- return GetGPGIdFromKeyPath(tmpfileName)
-}
+ for _, repoele := range reqs {
+ entry := template
+ entry.Type = trustTypeDescription(repoele.Type)
-func parseUids(colonDelimitKeys []byte) []string {
- var parseduids []string
- scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys))
- for scanner.Scan() {
- line := scanner.Text()
- if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
- uid := strings.Split(line, ":")[9]
- if uid == "" {
- continue
+ var gpgIDString string
+ switch repoele.Type {
+ case "signedBy":
+ uids := []string{}
+ if len(repoele.KeyPath) > 0 {
+ uids = append(uids, idReader(repoele.KeyPath)...)
}
- parseduid := uid
- if strings.Contains(uid, "<") && strings.Contains(uid, ">") {
- parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0]
+ for _, path := range repoele.KeyPaths {
+ uids = append(uids, idReader(path)...)
}
- parseduids = append(parseduids, parseduid)
+ if len(repoele.KeyData) > 0 {
+ uids = append(uids, getGPGIdFromKeyData(idReader, repoele.KeyData)...)
+ }
+ gpgIDString = strings.Join(uids, ", ")
+
+ case "sigstoreSigned":
+ gpgIDString = "N/A" // We could potentially return key fingerprints here, but they would not be _GPG_ fingerprints.
}
+ entry.GPGId = gpgIDString
+ entry.SignatureStore = lookasidePath // We do this even for sigstoreSigned and things like type: reject, to show that the sigstore is being read.
+ res = append(res, &entry)
}
- return parseduids
-}
-// GetPolicy parse policy.json into PolicyContent struct
-func GetPolicy(policyPath string) (PolicyContent, error) {
- var policyContentStruct PolicyContent
- policyContent, err := ioutil.ReadFile(policyPath)
- if err != nil {
- return policyContentStruct, fmt.Errorf("unable to read policy file: %w", err)
- }
- if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
- return policyContentStruct, fmt.Errorf("could not parse trust policies from %s: %w", policyPath, err)
- }
- return policyContentStruct, nil
+ return res
}
diff --git a/pkg/trust/trust_test.go b/pkg/trust/trust_test.go
new file mode 100644
index 000000000..22b780fc9
--- /dev/null
+++ b/pkg/trust/trust_test.go
@@ -0,0 +1,376 @@
+package trust
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/containers/image/v5/signature"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestPolicyDescription(t *testing.T) {
+ tempDir := t.TempDir()
+ policyPath := filepath.Join(tempDir, "policy.json")
+
+ // Override getGPGIdFromKeyPath because we don't want to bother with (and spend the unit-test time on) generating valid GPG keys, and running the real GPG binary.
+ // Instead of reading the files at all, just expect file names like /id1,id2,...,idN.pub
+ idReader := func(keyPath string) []string {
+ require.True(t, strings.HasPrefix(keyPath, "/"))
+ require.True(t, strings.HasSuffix(keyPath, ".pub"))
+ return strings.Split(keyPath[1:len(keyPath)-4], ",")
+ }
+
+ for _, c := range []struct {
+ policy *signature.Policy
+ expected []*Policy
+ }{
+ {
+ &signature.Policy{
+ Default: signature.PolicyRequirements{
+ signature.NewPRReject(),
+ },
+ Transports: map[string]signature.PolicyTransportScopes{
+ "docker": {
+ "quay.io/accepted": {
+ signature.NewPRInsecureAcceptAnything(),
+ },
+ "registry.redhat.io": {
+ xNewPRSignedByKeyPath(t, "/redhat.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ "registry.access.redhat.com": {
+ xNewPRSignedByKeyPaths(t, []string{"/redhat.pub", "/redhat-beta.pub"}, signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ "quay.io/multi-signed": {
+ xNewPRSignedByKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSignedByKeyPath(t, "/2,3.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ "quay.io/sigstore-signed": {
+ xNewPRSigstoreSignedKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSigstoreSignedKeyPath(t, "/2.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ },
+ },
+ },
+ []*Policy{
+ {
+ Transport: "all",
+ Name: "* (default)",
+ RepoName: "default",
+ Type: "reject",
+ },
+ {
+ Transport: "repository",
+ Name: "quay.io/accepted",
+ RepoName: "quay.io/accepted",
+ Type: "accept",
+ },
+ {
+ Transport: "repository",
+ Name: "quay.io/multi-signed",
+ RepoName: "quay.io/multi-signed",
+ Type: "signed",
+ SignatureStore: "https://quay.example.com/sigstore",
+ GPGId: "1",
+ },
+ {
+ Transport: "repository",
+ Name: "quay.io/multi-signed",
+ RepoName: "quay.io/multi-signed",
+ Type: "signed",
+ SignatureStore: "https://quay.example.com/sigstore",
+ GPGId: "2, 3",
+ },
+ {
+ Transport: "repository",
+ Name: "quay.io/sigstore-signed",
+ RepoName: "quay.io/sigstore-signed",
+ Type: "sigstoreSigned",
+ SignatureStore: "",
+ GPGId: "N/A",
+ },
+ {
+ Transport: "repository",
+ Name: "quay.io/sigstore-signed",
+ RepoName: "quay.io/sigstore-signed",
+ Type: "sigstoreSigned",
+ SignatureStore: "",
+ GPGId: "N/A",
+ },
+ {
+ Transport: "repository",
+ Name: "registry.access.redhat.com",
+ RepoName: "registry.access.redhat.com",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "redhat, redhat-beta",
+ }, {
+ Transport: "repository",
+ Name: "registry.redhat.io",
+ RepoName: "registry.redhat.io",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "redhat",
+ },
+ },
+ },
+ {
+ &signature.Policy{
+ Default: signature.PolicyRequirements{
+ xNewPRSignedByKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSignedByKeyPath(t, "/2,3.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ },
+ []*Policy{
+ {
+ Transport: "all",
+ Name: "* (default)",
+ RepoName: "default",
+ Type: "signed",
+ SignatureStore: "",
+ GPGId: "1",
+ },
+ {
+ Transport: "all",
+ Name: "* (default)",
+ RepoName: "default",
+ Type: "signed",
+ SignatureStore: "",
+ GPGId: "2, 3",
+ },
+ },
+ },
+ } {
+ policyJSON, err := json.Marshal(c.policy)
+ require.NoError(t, err)
+ err = os.WriteFile(policyPath, policyJSON, 0600)
+ require.NoError(t, err)
+
+ res, err := policyDescriptionWithGPGIDReader(policyPath, "./testdata", idReader)
+ require.NoError(t, err)
+ assert.Equal(t, c.expected, res)
+ }
+}
+
+func TestDescriptionsOfPolicyRequirements(t *testing.T) {
+ // Override getGPGIdFromKeyPath because we don't want to bother with (and spend the unit-test time on) generating valid GPG keys, and running the real GPG binary.
+ // Instead of reading the files at all, just expect file names like /id1,id2,...,idN.pub
+ idReader := func(keyPath string) []string {
+ require.True(t, strings.HasPrefix(keyPath, "/"))
+ require.True(t, strings.HasSuffix(keyPath, ".pub"))
+ return strings.Split(keyPath[1:len(keyPath)-4], ",")
+ }
+
+ template := Policy{
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ }
+ registryConfigs, err := loadAndMergeConfig("./testdata")
+ require.NoError(t, err)
+
+ for _, c := range []struct {
+ scope string
+ reqs signature.PolicyRequirements
+ expected []*Policy
+ }{
+ {
+ "",
+ signature.PolicyRequirements{
+ signature.NewPRReject(),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "reject",
+ },
+ },
+ },
+ {
+ "quay.io/accepted",
+ signature.PolicyRequirements{
+ signature.NewPRInsecureAcceptAnything(),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "accept",
+ },
+ },
+ },
+ {
+ "registry.redhat.io",
+ signature.PolicyRequirements{
+ xNewPRSignedByKeyPath(t, "/redhat.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "redhat",
+ },
+ },
+ },
+ {
+ "registry.access.redhat.com",
+ signature.PolicyRequirements{
+ xNewPRSignedByKeyPaths(t, []string{"/redhat.pub", "/redhat-beta.pub"}, signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "redhat, redhat-beta",
+ },
+ },
+ },
+ {
+ "quay.io/multi-signed",
+ signature.PolicyRequirements{
+ xNewPRSignedByKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSignedByKeyPath(t, "/2,3.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://quay.example.com/sigstore",
+ GPGId: "1",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://quay.example.com/sigstore",
+ GPGId: "2, 3",
+ },
+ },
+ }, {
+ "quay.io/sigstore-signed",
+ signature.PolicyRequirements{
+ xNewPRSigstoreSignedKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSigstoreSignedKeyPath(t, "/2.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "sigstoreSigned",
+ SignatureStore: "",
+ GPGId: "N/A",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "sigstoreSigned",
+ SignatureStore: "",
+ GPGId: "N/A",
+ },
+ },
+ },
+ { // Multiple kinds of requirements are represented individually.
+ "registry.redhat.io",
+ signature.PolicyRequirements{
+ signature.NewPRReject(),
+ signature.NewPRInsecureAcceptAnything(),
+ xNewPRSignedByKeyPath(t, "/redhat.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSignedByKeyPaths(t, []string{"/redhat.pub", "/redhat-beta.pub"}, signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSignedByKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSignedByKeyPath(t, "/2,3.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSigstoreSignedKeyPath(t, "/1.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ xNewPRSigstoreSignedKeyPath(t, "/2.pub", signature.NewPRMMatchRepoDigestOrExact()),
+ },
+ []*Policy{
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ Type: "reject",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ Type: "accept",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "redhat",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "redhat, redhat-beta",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "1",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "signed",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "2, 3",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "sigstoreSigned",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "N/A",
+ },
+ {
+ Transport: "transport",
+ Name: "name",
+ RepoName: "repoName",
+ Type: "sigstoreSigned",
+ SignatureStore: "https://registry.redhat.io/containers/sigstore",
+ GPGId: "N/A",
+ },
+ },
+ },
+ } {
+ reqsJSON, err := json.Marshal(c.reqs)
+ require.NoError(t, err)
+ var parsedRegs []repoContent
+ err = json.Unmarshal(reqsJSON, &parsedRegs)
+ require.NoError(t, err)
+
+ res := descriptionsOfPolicyRequirements(parsedRegs, template, registryConfigs, c.scope, idReader)
+ assert.Equal(t, c.expected, res)
+ }
+}
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at
index 4fd954e37..86ee2a1f5 100644
--- a/test/apiv2/10-images.at
+++ b/test/apiv2/10-images.at
@@ -239,4 +239,23 @@ fi
cleanBuildTest
+# compat API vs libpod API event differences:
+# on image removal, libpod produces 'remove' events.
+# compat produces 'delete' events.
+podman image build -t test:test -<<EOF
+from $IMAGE
+EOF
+
+START=$(date +%s)
+
+t DELETE libpod/images/test:test 200
+# HACK HACK HACK There is a race around events being added to the journal
+# This sleep seems to avoid the race.
+# If it fails and begins to flake, investigate a retry loop.
+sleep 1
+t GET "libpod/events?stream=false&since=$START" 200 \
+ 'select(.status | contains("remove")).Action=remove'
+t GET "events?stream=false&since=$START" 200 \
+ 'select(.status | contains("delete")).Action=delete'
+
# vim: filetype=sh
diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at
index 498d67569..be56152f1 100644
--- a/test/apiv2/12-imagesMore.at
+++ b/test/apiv2/12-imagesMore.at
@@ -63,7 +63,7 @@ podman pull -q $IMAGE
# test podman image SCP
# ssh needs to work so we can validate that the failure is past argument parsing
-podman system connection add --default test ssh://$USER@localhost/run/user/$UID/podman/podman.sock
+podman system connection add --default test ssh://$USER@localhost/run/user/$MYUID/podman/podman.sock
# should fail but need to check the output...
# status 125 here means that the save/load fails due to
# cirrus weirdness with exec.Command. All of the args have been parsed successfully.
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index ac3626cf1..655462f16 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -529,21 +529,21 @@ sleep 3
t GET containers/status-test/json 200 .State.Status="exited"
# test podman generate spec as input for the api
-podman create --name=specgen alpine_labels
+cname=specgen$(random_string 10)
+podman create --name=$cname $IMAGE
TMPD=$(mktemp -d podman-apiv2-test.build.XXXXXXXX)
-podman generate spec -f ${TMPD}/input.txt -c specgen
+podman generate spec -f ${TMPD}/myspec.json -c $cname
-curl -XPOST -o ${TMPD}/response.txt --dump-header ${TMPD}/headers.txt -H content-type:application/json http://$HOST:$PORT/v4.0.0/libpod/containers/create -d "@${TMPD}/input.txt"
+# Create a container based on that spec
+t POST libpod/containers/create ${TMPD}/myspec.json 201 \
+ .Id~[0-9a-f]\\{64\\}
-if ! grep -q '201 Created' "${TMPD}/headers.txt"; then
- cat "${TMPD}/headers.txt"
- cat "${TMPD}/response.txt"
- echo -e "${red}NOK: container create failed"
- rm -rf $TMPD
- exit 1
-fi
+# Verify
+t GET libpod/containers/$cname/json 200 \
+ .ImageName=$IMAGE \
+ .Name=$cname
rm -rf $TMPD
diff --git a/test/apiv2/23-containersArchive.at b/test/apiv2/23-containersArchive.at
index c55164780..3ff4465b9 100644
--- a/test/apiv2/23-containersArchive.at
+++ b/test/apiv2/23-containersArchive.at
@@ -11,14 +11,26 @@ podman pull $IMAGE &>/dev/null
# Ensure clean slate
podman rm -a -f &>/dev/null
-CTR="ArchiveTestingCtr"
+CTR="ArchiveTestingCtr$(random_string 5)"
TMPD=$(mktemp -d podman-apiv2-test.archive.XXXXXXXX)
HELLO_TAR="${TMPD}/hello.tar"
-echo "Hello" > $TMPD/hello.txt
+HELLO_S="Hello_$(random_string 8)"
+echo "$HELLO_S" > $TMPD/hello.txt
tar --owner=1042 --group=1043 --format=posix -C $TMPD -cvf ${HELLO_TAR} hello.txt &> /dev/null
+# Start a container, and wait for it. (I know we don't actually do anything
+# if we time out. If we do, subsequent tests will fail. I just want to avoid
+# a race between container-start and tests-start)
podman run -d --name "${CTR}" "${IMAGE}" top
+timeout=10
+while [[ $timeout -gt 0 ]]; do
+ if podman container exists "${CTR}"; then
+ break
+ fi
+ timeout=$((timeout - 1))
+ sleep 1
+done
function cleanUpArchiveTest() {
podman container stop "${CTR}" &> /dev/null
@@ -30,63 +42,47 @@ t HEAD "containers/nonExistentCtr/archive?path=%2F" 404
t HEAD "containers/${CTR}/archive?path=%2Fnon%2Fexistent%2Fpath" 404
t HEAD "containers/${CTR}/archive?path=%2Fetc%2Fpasswd" 200
-curl "http://$HOST:$PORT/containers/${CTR}/archive?path=%2Ftmp%2F" \
- -X PUT \
- -H "Content-Type: application/x-tar" \
- --upload-file "${HELLO_TAR}" &> /dev/null
+# Send tarfile to container...
+t PUT "/containers/${CTR}/archive?path=%2Ftmp%2F" ${HELLO_TAR} 200 ''
-if ! podman exec -it "${CTR}" "grep" "Hello" "/tmp/hello.txt" &> /dev/null ; then
- echo -e "${red}NOK: The hello.txt file has not been uploaded.${nc}" 1>&2;
- cleanUpArchiveTest
- exit 1
-fi
+# ...and 'exec cat file' to confirm that it got extracted into place.
+cat >$TMPD/exec.json <<EOF
+{ "AttachStdout":true,"Cmd":["cat","/tmp/hello.txt"]}
+EOF
+t POST containers/${CTR}/exec $TMPD/exec.json 201 .Id~[0-9a-f]\\{64\\}
+eid=$(jq -r '.Id' <<<"$output")
+# The 017 is the byte length
+t POST exec/$eid/start 200 $'\001\017'$HELLO_S
+# Now fetch hello.txt back as a tarfile
t HEAD "containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" 200
+t GET "containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" 200
-curl "http://$HOST:$PORT/containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" \
- --dump-header "${TMPD}/headers.txt" \
- -o "${TMPD}/body.tar" \
- -X GET &> /dev/null
+# Check important-looking header
+PATH_STAT="$(grep X-Docker-Container-Path-Stat "$WORKDIR/curl.headers.out" | cut -d " " -f 2 | base64 -d --ignore-garbage)"
-PATH_STAT="$(grep X-Docker-Container-Path-Stat "${TMPD}/headers.txt" | cut -d " " -f 2 | base64 -d --ignore-garbage)"
+is "$(jq -r .name <<<$PATH_STAT)" "hello.txt" "Docker-Path-Stat .name"
+is "$(jq -r .size <<<$PATH_STAT)" "15" "Docker-Path-Stat .size"
-ARCHIVE_TEST_ERROR=""
+# Check filename and its contents
+tar_tf=$(tar tf $WORKDIR/curl.result.out)
+is "$tar_tf" "hello.txt" "fetched tarball: file name"
-if [ "$(echo "${PATH_STAT}" | jq ".name")" != '"hello.txt"' ]; then
- echo -e "${red}NOK: Wrong name in X-Docker-Container-Path-Stat header.${nc}" 1>&2;
- ARCHIVE_TEST_ERROR="1"
-fi
+tar_contents=$(tar xf $WORKDIR/curl.result.out --to-stdout)
+is "$tar_contents" "$HELLO_S" "fetched tarball: file contents"
-if [ "$(echo "${PATH_STAT}" | jq ".size")" != "6" ]; then
- echo -e "${red}NOK: Wrong size in X-Docker-Container-Path-Stat header.${nc}" 1>&2;
- ARCHIVE_TEST_ERROR="1"
-fi
-
-if ! tar -tf "${TMPD}/body.tar" | grep "hello.txt" &> /dev/null; then
- echo -e "${red}NOK: Body doesn't contain expected file.${nc}" 1>&2;
- ARCHIVE_TEST_ERROR="1"
-fi
-
-if [ "$(tar -xf "${TMPD}/body.tar" hello.txt --to-stdout)" != "Hello" ]; then
- echo -e "${red}NOK: Content of file doesn't match.${nc}" 1>&2;
- ARCHIVE_TEST_ERROR="1"
-fi
+# TODO: uid/gid should be also preserved on way back (GET request)
+# right now it ends up as 0/0 instead of 1042/1043
+tar_uidgid=$(tar tvf $WORKDIR/curl.result.out | awk '{print $2}')
+is "$tar_uidgid" "0/0" "fetched tarball: file uid/gid"
-# test if uid/gid was set correctly in the server
-uidngid=$($PODMAN_BIN --root $WORKDIR/server_root exec "${CTR}" stat -c "%u:%g" "/tmp/hello.txt")
-if [[ "${uidngid}" != "1042:1043" ]]; then
- echo -e "${red}NOK: UID/GID of the file doesn't match.${nc}" 1>&2;
- ARCHIVE_TEST_ERROR="1"
-fi
+# test if uid/gid was set correctly in the server. Again, via exec.
+cat >$TMPD/exec.json <<EOF
+{ "AttachStdout":true,"Cmd":["stat","-c","%u:%g","/tmp/hello.txt"]}
+EOF
-# TODO: uid/gid should be also preserved on way back (GET request)
-# right now it ends up as root:root instead of 1042:1043
-#if [[ "$(tar -tvf "${TMPD}/body.tar")" != *"1042/1043"* ]]; then
-# echo -e "${red}NOK: UID/GID of the file doesn't match.${nc}" 1>&2;
-# ARCHIVE_TEST_ERROR="1"
-#fi
+t POST containers/${CTR}/exec $TMPD/exec.json 201 .Id~[0-9a-f]\\{64\\}
+eid=$(jq -r '.Id' <<<"$output")
+t POST exec/$eid/start 200 $'\001\012'1042:1043
cleanUpArchiveTest
-if [[ "${ARCHIVE_TEST_ERROR}" ]] ; then
- exit 1;
-fi
diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2
index 0c3c6e672..0eb2d1b30 100755
--- a/test/apiv2/test-apiv2
+++ b/test/apiv2/test-apiv2
@@ -24,7 +24,7 @@ REGISTRY_IMAGE="${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/registry
# BEGIN setup
USER=$PODMAN_ROOTLESS_USER
-UID=$PODMAN_ROOTLESS_UID
+MYUID=$PODMAN_ROOTLESS_UID
TMPDIR=${TMPDIR:-/tmp}
WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX)
@@ -135,7 +135,8 @@ function like() {
##############
function _show_ok() {
local ok=$1
- local testname=$2
+ # Exec tests include control characters; filter them out
+ local testname=$(tr -d \\012 <<<"$2"|cat -vT)
# If output is a tty, colorize pass/fail
local red=
@@ -254,14 +255,24 @@ function t() {
# Slurp the command line until we see a 3-digit status code.
if [[ $method = "POST" || $method == "PUT" || $method = "DELETE" ]]; then
local -a post_args
+
+ if [[ $method = "POST" ]]; then
+ function _add_curl_args() { curl_args+=(--data-binary @$1); }
+ else
+ function _add_curl_args() { curl_args+=(--upload-file $1); }
+ fi
+
for arg; do
case "$arg" in
*=*) post_args+=("$arg");
shift;;
- *.tar) curl_args+=(--data-binary @$arg);
+ *.json) _add_curl_args $arg;
+ content_type="application/json";
+ shift;;
+ *.tar) _add_curl_args $arg;
content_type="application/x-tar";
shift;;
- *.yaml) curl_args+=(--data-binary @$arg);
+ *.yaml) _add_curl_args $arg;
shift;;
application/*) content_type="$arg";
shift;;
diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go
index dd0070f54..9df884292 100644
--- a/test/e2e/restart_test.go
+++ b/test/e2e/restart_test.go
@@ -228,7 +228,7 @@ var _ = Describe("Podman restart", func() {
Expect(beforeRestart.OutputToString()).To(Equal(afterRestart.OutputToString()))
})
- It("podman restart all stoped containers with --all", func() {
+ It("podman restart all stopped containers with --all", func() {
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index b2840d544..1f6b6fa3d 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -1236,7 +1236,7 @@ USER mail`, BB)
It("podman run --mount type=bind,bind-nonrecursive", func() {
SkipIfRootless("FIXME: rootless users are not allowed to mount bind-nonrecursive")
- session := podmanTest.Podman([]string{"run", "--mount", "type=bind,bind-nonrecursive,slave,src=/,target=/host", fedoraMinimal, "findmnt", "-nR", "/host"})
+ session := podmanTest.Podman([]string{"run", "--mount", "type=bind,bind-nonrecursive,private,src=/sys,target=/host-sys", fedoraMinimal, "findmnt", "-nR", "/host-sys"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToStringArray()).To(HaveLen(1))
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
index b9063ad1b..e3a9ec4c3 100644
--- a/test/system/200-pod.bats
+++ b/test/system/200-pod.bats
@@ -61,7 +61,7 @@ function teardown() {
@test "podman pod create - custom infra image" {
- skip_if_remote "CONTAINERS_CONF only effects server side"
+ skip_if_remote "CONTAINERS_CONF only affects server side"
image="i.do/not/exist:image"
tmpdir=$PODMAN_TMPDIR/pod-test
mkdir -p $tmpdir
@@ -478,7 +478,6 @@ spec:
}
@test "pod resource limits" {
- skip_if_aarch64 "FIXME: #15074 - flakes on aarch64 non-remote"
skip_if_remote "resource limits only implemented on non-remote"
skip_if_rootless "resource limits only work with root"
skip_if_cgroupsv1 "resource limits only meaningful on cgroups V2"
@@ -493,30 +492,24 @@ spec:
lomajmin=$(losetup -l --noheadings --output MAJ:MIN $LOOPDEVICE | tr -d ' ')
run grep -w bfq /sys/block/$(basename ${LOOPDEVICE})/queue/scheduler
if [ $status -ne 0 ]; then
+ losetup -d $LOOPDEVICE
+ LOOPDEVICE=
skip "BFQ scheduler is not supported on the system"
- if [ -f ${lofile} ]; then
- run_podman '?' rm -t 0 --all --force --ignore
-
- while read path dev; do
- if [[ "$path" == "$lofile" ]]; then
- losetup -d $dev
- fi
- done < <(losetup -l --noheadings --output BACK-FILE,NAME)
- rm ${lofile}
- fi
fi
echo bfq > /sys/block/$(basename ${LOOPDEVICE})/queue/scheduler
+ # FIXME: #15464: blkio-weight-device not working
expected_limits="
cpu.max | 500000 100000
memory.max | 5242880
memory.swap.max | 1068498944
+io.bfq.weight | default 50
io.max | $lomajmin rbps=1048576 wbps=1048576 riops=max wiops=max
"
for cgm in systemd cgroupfs; do
local name=resources-$cgm
- run_podman --cgroup-manager=$cgm pod create --name=$name --cpus=5 --memory=5m --memory-swap=1g --cpu-shares=1000 --cpuset-cpus=0 --cpuset-mems=0 --device-read-bps=${LOOPDEVICE}:1mb --device-write-bps=${LOOPDEVICE}:1mb --blkio-weight-device=${LOOPDEVICE}:123 --blkio-weight=50
+ run_podman --cgroup-manager=$cgm pod create --name=$name --cpus=5 --memory=5m --memory-swap=1g --cpu-shares=1000 --cpuset-cpus=0 --cpuset-mems=0 --device-read-bps=${LOOPDEVICE}:1mb --device-write-bps=${LOOPDEVICE}:1mb --blkio-weight=50
run_podman --cgroup-manager=$cgm pod start $name
run_podman pod inspect --format '{{.CgroupPath}}' $name
local cgroup_path="$output"