Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@
SCRIPT_BASE: "./contrib/cirrus"

# Google-cloud VM Images
IMAGE_SUFFIX: "c20250721t181111z-f42f41d13"
# If you are updating IMAGE_SUFFIX: We are currently using rawhide for
# the containers_image_sequoia tests because the rust-podman-sequoia
# package is not available in earlier releases; once we update to a future
# Fedora release (or if the package is backported), switch back from Rawhide
# to the latest Fedora release.
IMAGE_SUFFIX: "c20250910t092246z-f42f41d13"
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
RAWHIDE_CACHE_IMAGE_NAME: "rawhide-${IMAGE_SUFFIX}"

# Container FQIN's
FEDORA_CONTAINER_FQIN: "quay.io/libpod/fedora_podman:${IMAGE_SUFFIX}"
Expand Down Expand Up @@ -71,7 +77,7 @@
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" build
"${SKOPEO_PATH}/${SCRIPT_BASE}/runner.sh" doccheck

osx_task:

Check warning on line 80 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L80

task "osx" depends on task "validate", but their only_if conditions are different
# Don't run for docs-only builds.
# Also don't run on release-branches or their PRs,
# since base container-image is not version-constrained.
Expand Down Expand Up @@ -110,7 +116,7 @@
task_cleanup_script: *mac_cleanup


cross_task:

Check warning on line 119 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L119

task "cross" depends on task "validate", but their only_if conditions are different
alias: cross
only_if: >-
$CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*'
Expand All @@ -133,7 +139,7 @@
"${GOSRC}/${SCRIPT_BASE}/runner.sh" cross


ostree-rs-ext_task:

Check warning on line 142 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L142

task "ostree-rs-ext" depends on task "validate", but their only_if conditions are different
alias: proxy_ostree_ext
only_if: *not_docs_or_release_branch
# WARNING: This task potentially performs a container image
Expand Down Expand Up @@ -174,7 +180,7 @@
##### repository's `.cirrus.yml`. Changes made here should be fully merged
##### prior to being manually duplicated and maintained in containers/image.
#####
test_skopeo_task:

Check warning on line 183 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L183

task "Skopeo Test" depends on task "validate", but their only_if conditions are different

Check warning on line 183 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L183

task "Skopeo Test w/ opengpg" depends on task "validate", but their only_if conditions are different

Check warning on line 183 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L183

task "Skopeo test w/ Sequoia (currently Rawhide)" depends on task "validate", but their only_if conditions are different
alias: test_skopeo
# Don't test for [CI:DOCS], [CI:BUILD].
only_if: >-
Expand All @@ -190,14 +196,21 @@
# Required to be 200gig, do not modify - has i/o performance impact
# according to gcloud CLI tool warning messages.
disk: 200
image_name: ${FEDORA_CACHE_IMAGE_NAME}
image_name: ${VM_IMAGE_NAME}
matrix:
- name: "Skopeo Test" # N/B: Name ref. by hack/get_fqin.sh
env:
BUILDTAGS: ''
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
- name: "Skopeo Test w/ opengpg"
env:
BUILDTAGS: *withopengpg
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
- name: "Skopeo test w/ Sequoia (currently Rawhide)"
env:
BUILDTAGS: 'containers_image_sequoia'
# If you are removing the use of rawhide, also remove the VM_IMAGE_NAME condition from runner.sh .
VM_IMAGE_NAME: ${RAWHIDE_CACHE_IMAGE_NAME}
setup_script: >-
"${GOSRC}/${SCRIPT_BASE}/runner.sh" setup
vendor_script: >-
Expand Down Expand Up @@ -226,6 +239,7 @@
# Space-separated list of images used by this repository state
IMGNAMES: |
${FEDORA_CACHE_IMAGE_NAME}
${RAWHIDE_CACHE_IMAGE_NAME}
build-push-${IMAGE_SUFFIX}
BUILDID: "${CIRRUS_BUILD_ID}"
REPOREF: "${CIRRUS_REPO_NAME}"
Expand All @@ -239,7 +253,7 @@
# Status aggregator for all tests. This task simply ensures a defined
# set of tasks all passed, and allows confirming that based on the status
# of this task.
success_task:

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "validate", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "doccheck", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "osx", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "cross", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "ostree-rs-ext", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "Skopeo Test", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "Skopeo Test w/ opengpg", but their only_if conditions are different

Check warning on line 256 in .cirrus.yml

View check run for this annotation

Cirrus CI / Build Parsing Results

.cirrus.yml#L256

task "Total Success" depends on task "Skopeo test w/ Sequoia (currently Rawhide)", but their only_if conditions are different
name: "Total Success"
alias: success
# N/B: ALL tasks must be listed here, minus their '_task' suffix.
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ GOBIN := $(shell $(GO) env GOBIN)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)

SEQUOIA_SONAME_DIR =

# N/B: This value is managed by Renovate, manual changes are
# possible, as long as they don't disturb the formatting
# (i.e. DO NOT ADD A 'v' prefix!)
Expand Down Expand Up @@ -82,7 +84,7 @@ CONTAINER_GOSRC = /src/github.com/containers/skopeo
CONTAINER_RUN ?= $(CONTAINER_CMD) --security-opt label=disable -v $(CURDIR):$(CONTAINER_GOSRC) -w $(CONTAINER_GOSRC) $(SKOPEO_CIDEV_CONTAINER_FQIN)

EXTRA_LDFLAGS ?=
SKOPEO_LDFLAGS := -ldflags '$(EXTRA_LDFLAGS)'
SKOPEO_LDFLAGS := -ldflags '-X go.podman.io/image/v5/signature/internal/sequoia.sequoiaLibraryDir=$(SEQUOIA_SONAME_DIR) $(EXTRA_LDFLAGS)'

MANPAGES_MD = $(wildcard docs/*.md)
MANPAGES ?= $(MANPAGES_MD:%.md=%)
Expand Down Expand Up @@ -251,7 +253,7 @@ validate-docs: bin/skopeo
hack/xref-helpmsgs-manpages

test-unit-local:
$(GO) test -tags "$(BUILDTAGS)" $$($(GO) list -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/containers/skopeo/\(integration\|vendor/.*\)$$')
$(GO) test $(SKOPEO_LDFLAGS) -tags "$(BUILDTAGS)" $$($(GO) list -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/containers/skopeo/\(integration\|vendor/.*\)$$')

vendor:
$(GO) mod tidy
Expand Down
7 changes: 2 additions & 5 deletions cmd/skopeo/signing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func assertTestFailed(t *testing.T, stdout string, err error, substring string)
}

func TestStandaloneSign(t *testing.T) {
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
t.Setenv("GNUPGHOME", "fixtures")
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
Expand All @@ -38,7 +39,6 @@ func TestStandaloneSign(t *testing.T) {

manifestPath := "fixtures/image.manifest.json"
dockerReference := "testing/manifest"
t.Setenv("GNUPGHOME", "fixtures")

// Invalid command-line arguments
for _, args := range [][]string{
Expand Down Expand Up @@ -87,9 +87,6 @@ func TestStandaloneSign(t *testing.T) {
require.NoError(t, err)
manifest, err := os.ReadFile(manifestPath)
require.NoError(t, err)
mech, err = signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
verified, err := signature.VerifyDockerManifestSignature(sig, manifest, dockerReference, mech, fixturesTestKeyFingerprint)
require.NoError(t, err)
assert.Equal(t, dockerReference, verified.DockerReference)
Expand Down
33 changes: 31 additions & 2 deletions cmd/skopeo/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"go.podman.io/image/v5/pkg/cli/sigstore"
"go.podman.io/image/v5/pkg/compression"
"go.podman.io/image/v5/signature/signer"
"go.podman.io/image/v5/signature/simplesequoia"
"go.podman.io/image/v5/storage"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/image/v5/types"
Expand Down Expand Up @@ -329,6 +330,7 @@ func (opts *imageDestOptions) warnAboutIneffectiveOptions(destTransport types.Im
type sharedCopyOptions struct {
removeSignatures bool // Do not copy signatures from the source image
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
signBySequoiaFingerprint string // Sign the image using a Sequoia-PGP key with the specified fingerprint
signBySigstoreParamFile string // Sign the image using a sigstore signature per configuration in a param file
signBySigstorePrivateKey string // Sign the image using a sigstore private key
signPassphraseFile string // Path pointing to a passphrase file when signing
Expand All @@ -342,6 +344,7 @@ func sharedCopyFlags() (pflag.FlagSet, *sharedCopyOptions) {
fs := pflag.FlagSet{}
fs.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from source")
fs.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
fs.StringVar(&opts.signBySequoiaFingerprint, "sign-by-sq-fingerprint", "", "Sign the image using a Sequoia-PGP key with the specified `FINGERPRINT`")
fs.StringVar(&opts.signBySigstoreParamFile, "sign-by-sigstore", "", "Sign the image using a sigstore parameter file at `PATH`")
fs.StringVar(&opts.signBySigstorePrivateKey, "sign-by-sigstore-private-key", "", "Sign the image using a sigstore private key at `PATH`")
fs.StringVar(&opts.signPassphraseFile, "sign-passphrase-file", "", "Read a passphrase for signing an image from `PATH`")
Expand All @@ -365,8 +368,20 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
// c/image/copy.Image does allow creating both simple signing and sigstore signatures simultaneously,
// with independent passphrases, but that would make the CLI probably too confusing.
// For now, use the passphrase with either, but only one of them.
if opts.signPassphraseFile != "" && opts.signByFingerprint != "" && opts.signBySigstorePrivateKey != "" {
return nil, nil, fmt.Errorf("Only one of --sign-by and sign-by-sigstore-private-key can be used with sign-passphrase-file")
if opts.signPassphraseFile != "" {
count := 0
if opts.signByFingerprint != "" {
count++
}
if opts.signBySequoiaFingerprint != "" {
count++
}
if opts.signBySigstorePrivateKey != "" {
count++
}
if count > 1 {
return nil, nil, fmt.Errorf("Only one of --sign-by, --sign-by-sq-fingerprint and --sign-by-sigstore-private-key can be used with --sign-passphrase-file")
}
}
var passphrase string
if opts.signPassphraseFile != "" {
Expand All @@ -382,6 +397,7 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
}
passphrase = p
} // opts.signByFingerprint triggers a GPG-agent passphrase prompt, possibly using a more secure channel, so we usually shouldn’t prompt ourselves if no passphrase was explicitly provided.
// With opts.signBySequoiaFingerprint, we don’t prompt for a passphrase (for now??): We don’t know whether the key requires a passphrase.
var passphraseBytes []byte
if passphrase != "" {
passphraseBytes = []byte(passphrase)
Expand Down Expand Up @@ -412,6 +428,19 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
}
signers = append(signers, signer)
}
if opts.signBySequoiaFingerprint != "" {
sqOpts := []simplesequoia.Option{
simplesequoia.WithKeyFingerprint(opts.signBySequoiaFingerprint),
}
if passphrase != "" {
sqOpts = append(sqOpts, simplesequoia.WithPassphrase(passphrase))
}
signer, err := simplesequoia.NewSigner(sqOpts...)
if err != nil {
return nil, nil, fmt.Errorf("Error using --sign-by-sq-fingerprint: %w", err)
}
signers = append(signers, signer)
}

succeeded = true
return &copy.Options{
Expand Down
5 changes: 5 additions & 0 deletions cmd/skopeo/utils_nosequoia_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !containers_image_sequoia

package main

const buildWithSequoia = false
5 changes: 5 additions & 0 deletions cmd/skopeo/utils_sequoia_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build containers_image_sequoia

package main

const buildWithSequoia = true
37 changes: 34 additions & 3 deletions cmd/skopeo/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"os"
"slices"
"testing"

imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -378,6 +379,7 @@ func TestSharedCopyOptionsCopyOptions(t *testing.T) {
// Set most flags to non-default values
// This should also test --sign-by-sigstore and --sign-by-sigstore-private-key; we would have
// to create test keys for that.
// This does not test --sign-by-sq-fingerprint, because that needs to be conditional based on buildWithSequoia.
opts = fakeSharedCopyOptions(t, []string{
"--remove-signatures",
"--sign-by", "gpgFingerprint",
Expand All @@ -395,12 +397,13 @@ func TestSharedCopyOptionsCopyOptions(t *testing.T) {
ForceManifestMIMEType: imgspecv1.MediaTypeImageManifest,
}, res)

// --sign-passphrase-file + --sign-by work
// --sign-passphrase-file:
passphraseFile, err := os.CreateTemp("", "passphrase") // Eventually we could refer to a passphrase fixture instead
require.NoError(t, err)
defer os.Remove(passphraseFile.Name())
_, err = passphraseFile.WriteString("test-passphrase")
require.NoError(t, err)
// --sign-passphrase-file + --sign-by work
opts = fakeSharedCopyOptions(t, []string{
"--sign-by", "gpgFingerprint",
"--sign-passphrase-file", passphraseFile.Name(),
Expand All @@ -414,14 +417,42 @@ func TestSharedCopyOptionsCopyOptions(t *testing.T) {
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
ReportWriter: &someStdout,
}, res)
// --sign-passphrase-file + --sign-by-sigstore-private-key should be tested here.
// If Sequoia is supported, --sign-passphrase-file + --sign-by-sq-fingerprint work
if buildWithSequoia {
opts = fakeSharedCopyOptions(t, []string{
"--sign-by-sq-fingerprint", "sqFingerprint",
"--sign-passphrase-file", passphraseFile.Name(),
})
res, cleanup, err = opts.copyOptions(&someStdout)
require.NoError(t, err)
defer cleanup()
assert.NotNil(t, res.Signers) // Contains a Sequoia signer
res.Signers = nil // To allow the comparison below
assert.Equal(t, &copy.Options{
SignPassphrase: "test-passphrase",
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
ReportWriter: &someStdout,
}, res)
}

// Invalid --format
opts = fakeSharedCopyOptions(t, []string{"--format", "invalid"})
_, _, err = opts.copyOptions(&someStdout)
assert.Error(t, err)

// More --sign-passphrase-file, --sign-by-sigstore-private-key, --sign-by-sigstore failure cases should be tested here.
// More --sign-by-sigstore-private-key, --sign-by-sigstore failure cases should be tested here.
// --sign-passphrase-file + more than one key option
for _, opts := range [][]string{
{"--sign-by", "gpgFingerprint", "--sign-by-sq-fingerprint", "sqFingerprint"},
{"--sign-by", "gpgFingerprint", "--sign-by-sigstore-private-key", "sigstorePrivateKey"},
{"--sign-by-sq-fingerprint", "sqFingerprint", "--sign-by-sigstore-private-key", "sigstorePrivateKey"},
} {
opts := fakeSharedCopyOptions(t, slices.Concat(opts, []string{
"--sign-passphrase-file", passphraseFile.Name(),
}))
_, _, err = opts.copyOptions(&someStdout)
assert.Error(t, err)
}

// --sign-passphrase-file not found
opts = fakeSharedCopyOptions(t, []string{
Expand Down
2 changes: 1 addition & 1 deletion contrib/cirrus/runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ _run_vendor() {

_run_build() {
make bin/skopeo BUILDTAGS="$BUILDTAGS"
make install PREFIX=/usr/local
make install PREFIX=/usr/local BUILDTAGS="$BUILDTAGS"
}

_run_cross() {
Expand Down
7 changes: 6 additions & 1 deletion docs/skopeo-copy.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,14 @@ See containers-sigstore-signing-params.yaml(5) for details about the file format

Add a sigstore signature using a private key at _path_ for an image name corresponding to _destination-image_

**--sign-by-sq-fingerprint** _fingerprint_

Add a “simple signing” signature using a Sequoia-PGP key with the specified _fingerprint_.

**--sign-passphrase-file** _path_

The passphare to use when signing with `--sign-by` or `--sign-by-sigstore-private-key`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
The passphrase to use when signing with `--sign-by`, `--sign-by-sigstore-private-key` or `--sign-by-sq-fingerprint`.
Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.

**--sign-identity** _reference_

Expand Down
7 changes: 6 additions & 1 deletion docs/skopeo-sync.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,14 @@ See containers-sigstore-signing-params.yaml(5) for details about the file format

Add a sigstore signature using a private key at _path_ for an image name corresponding to _destination-image_

**--sign-by-sq-fingerprint** _fingerprint_

Add a “simple signing” signature using a Sequoia-PGP key with the specified _fingerprint_.

**--sign-passphrase-file** _path_

The passphare to use when signing with `--sign-by` or `--sign-by-sigstore-private-key`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
The passphrase to use when signing with `--sign-by`, `--sign-by-sigstore-private-key` or `--sign-by-sq-fingerprint`.
Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.

**--src-creds** _username[:password]_ for accessing the source registry.

Expand Down
Loading