Skip to content

Commit 96eabba

Browse files
authored
Merge pull request #2645 from mtrmac/sequoia-cli
Add --sign-by-sq-fingerprint
2 parents 99e3d91 + f59e893 commit 96eabba

33 files changed

+470
-29
lines changed

.cirrus.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ env:
2121
SCRIPT_BASE: "./contrib/cirrus"
2222

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

2733
# Container FQIN's
2834
FEDORA_CONTAINER_FQIN: "quay.io/libpod/fedora_podman:${IMAGE_SUFFIX}"
@@ -190,14 +196,21 @@ test_skopeo_task:
190196
# Required to be 200gig, do not modify - has i/o performance impact
191197
# according to gcloud CLI tool warning messages.
192198
disk: 200
193-
image_name: ${FEDORA_CACHE_IMAGE_NAME}
199+
image_name: ${VM_IMAGE_NAME}
194200
matrix:
195201
- name: "Skopeo Test" # N/B: Name ref. by hack/get_fqin.sh
196202
env:
197203
BUILDTAGS: ''
204+
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
198205
- name: "Skopeo Test w/ opengpg"
199206
env:
200207
BUILDTAGS: *withopengpg
208+
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
209+
- name: "Skopeo test w/ Sequoia (currently Rawhide)"
210+
env:
211+
BUILDTAGS: 'containers_image_sequoia'
212+
# If you are removing the use of rawhide, also remove the VM_IMAGE_NAME condition from runner.sh .
213+
VM_IMAGE_NAME: ${RAWHIDE_CACHE_IMAGE_NAME}
201214
setup_script: >-
202215
"${GOSRC}/${SCRIPT_BASE}/runner.sh" setup
203216
vendor_script: >-
@@ -226,6 +239,7 @@ meta_task:
226239
# Space-separated list of images used by this repository state
227240
IMGNAMES: |
228241
${FEDORA_CACHE_IMAGE_NAME}
242+
${RAWHIDE_CACHE_IMAGE_NAME}
229243
build-push-${IMAGE_SUFFIX}
230244
BUILDID: "${CIRRUS_BUILD_ID}"
231245
REPOREF: "${CIRRUS_REPO_NAME}"

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ GOBIN := $(shell $(GO) env GOBIN)
2424
GOOS ?= $(shell go env GOOS)
2525
GOARCH ?= $(shell go env GOARCH)
2626

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

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

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

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

256258
vendor:
257259
$(GO) mod tidy

cmd/skopeo/signing_test.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ func assertTestFailed(t *testing.T, stdout string, err error, substring string)
2929
}
3030

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

3940
manifestPath := "fixtures/image.manifest.json"
4041
dockerReference := "testing/manifest"
41-
t.Setenv("GNUPGHOME", "fixtures")
4242

4343
// Invalid command-line arguments
4444
for _, args := range [][]string{
@@ -87,9 +87,6 @@ func TestStandaloneSign(t *testing.T) {
8787
require.NoError(t, err)
8888
manifest, err := os.ReadFile(manifestPath)
8989
require.NoError(t, err)
90-
mech, err = signature.NewGPGSigningMechanism()
91-
require.NoError(t, err)
92-
defer mech.Close()
9390
verified, err := signature.VerifyDockerManifestSignature(sig, manifest, dockerReference, mech, fixturesTestKeyFingerprint)
9491
require.NoError(t, err)
9592
assert.Equal(t, dockerReference, verified.DockerReference)

cmd/skopeo/utils.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"go.podman.io/image/v5/pkg/cli/sigstore"
2727
"go.podman.io/image/v5/pkg/compression"
2828
"go.podman.io/image/v5/signature/signer"
29+
"go.podman.io/image/v5/signature/simplesequoia"
2930
"go.podman.io/image/v5/storage"
3031
"go.podman.io/image/v5/transports/alltransports"
3132
"go.podman.io/image/v5/types"
@@ -329,6 +330,7 @@ func (opts *imageDestOptions) warnAboutIneffectiveOptions(destTransport types.Im
329330
type sharedCopyOptions struct {
330331
removeSignatures bool // Do not copy signatures from the source image
331332
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
333+
signBySequoiaFingerprint string // Sign the image using a Sequoia-PGP key with the specified fingerprint
332334
signBySigstoreParamFile string // Sign the image using a sigstore signature per configuration in a param file
333335
signBySigstorePrivateKey string // Sign the image using a sigstore private key
334336
signPassphraseFile string // Path pointing to a passphrase file when signing
@@ -342,6 +344,7 @@ func sharedCopyFlags() (pflag.FlagSet, *sharedCopyOptions) {
342344
fs := pflag.FlagSet{}
343345
fs.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from source")
344346
fs.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
347+
fs.StringVar(&opts.signBySequoiaFingerprint, "sign-by-sq-fingerprint", "", "Sign the image using a Sequoia-PGP key with the specified `FINGERPRINT`")
345348
fs.StringVar(&opts.signBySigstoreParamFile, "sign-by-sigstore", "", "Sign the image using a sigstore parameter file at `PATH`")
346349
fs.StringVar(&opts.signBySigstorePrivateKey, "sign-by-sigstore-private-key", "", "Sign the image using a sigstore private key at `PATH`")
347350
fs.StringVar(&opts.signPassphraseFile, "sign-passphrase-file", "", "Read a passphrase for signing an image from `PATH`")
@@ -365,8 +368,20 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
365368
// c/image/copy.Image does allow creating both simple signing and sigstore signatures simultaneously,
366369
// with independent passphrases, but that would make the CLI probably too confusing.
367370
// For now, use the passphrase with either, but only one of them.
368-
if opts.signPassphraseFile != "" && opts.signByFingerprint != "" && opts.signBySigstorePrivateKey != "" {
369-
return nil, nil, fmt.Errorf("Only one of --sign-by and sign-by-sigstore-private-key can be used with sign-passphrase-file")
371+
if opts.signPassphraseFile != "" {
372+
count := 0
373+
if opts.signByFingerprint != "" {
374+
count++
375+
}
376+
if opts.signBySequoiaFingerprint != "" {
377+
count++
378+
}
379+
if opts.signBySigstorePrivateKey != "" {
380+
count++
381+
}
382+
if count > 1 {
383+
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")
384+
}
370385
}
371386
var passphrase string
372387
if opts.signPassphraseFile != "" {
@@ -382,6 +397,7 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
382397
}
383398
passphrase = p
384399
} // 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.
400+
// With opts.signBySequoiaFingerprint, we don’t prompt for a passphrase (for now??): We don’t know whether the key requires a passphrase.
385401
var passphraseBytes []byte
386402
if passphrase != "" {
387403
passphraseBytes = []byte(passphrase)
@@ -412,6 +428,19 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
412428
}
413429
signers = append(signers, signer)
414430
}
431+
if opts.signBySequoiaFingerprint != "" {
432+
sqOpts := []simplesequoia.Option{
433+
simplesequoia.WithKeyFingerprint(opts.signBySequoiaFingerprint),
434+
}
435+
if passphrase != "" {
436+
sqOpts = append(sqOpts, simplesequoia.WithPassphrase(passphrase))
437+
}
438+
signer, err := simplesequoia.NewSigner(sqOpts...)
439+
if err != nil {
440+
return nil, nil, fmt.Errorf("Error using --sign-by-sq-fingerprint: %w", err)
441+
}
442+
signers = append(signers, signer)
443+
}
415444

416445
succeeded = true
417446
return &copy.Options{

cmd/skopeo/utils_nosequoia_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build !containers_image_sequoia
2+
3+
package main
4+
5+
const buildWithSequoia = false

cmd/skopeo/utils_sequoia_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build containers_image_sequoia
2+
3+
package main
4+
5+
const buildWithSequoia = true

cmd/skopeo/utils_test.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"os"
7+
"slices"
78
"testing"
89

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

398-
// --sign-passphrase-file + --sign-by work
400+
// --sign-passphrase-file:
399401
passphraseFile, err := os.CreateTemp("", "passphrase") // Eventually we could refer to a passphrase fixture instead
400402
require.NoError(t, err)
401403
defer os.Remove(passphraseFile.Name())
402404
_, err = passphraseFile.WriteString("test-passphrase")
403405
require.NoError(t, err)
406+
// --sign-passphrase-file + --sign-by work
404407
opts = fakeSharedCopyOptions(t, []string{
405408
"--sign-by", "gpgFingerprint",
406409
"--sign-passphrase-file", passphraseFile.Name(),
@@ -414,14 +417,42 @@ func TestSharedCopyOptionsCopyOptions(t *testing.T) {
414417
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
415418
ReportWriter: &someStdout,
416419
}, res)
417-
// --sign-passphrase-file + --sign-by-sigstore-private-key should be tested here.
420+
// If Sequoia is supported, --sign-passphrase-file + --sign-by-sq-fingerprint work
421+
if buildWithSequoia {
422+
opts = fakeSharedCopyOptions(t, []string{
423+
"--sign-by-sq-fingerprint", "sqFingerprint",
424+
"--sign-passphrase-file", passphraseFile.Name(),
425+
})
426+
res, cleanup, err = opts.copyOptions(&someStdout)
427+
require.NoError(t, err)
428+
defer cleanup()
429+
assert.NotNil(t, res.Signers) // Contains a Sequoia signer
430+
res.Signers = nil // To allow the comparison below
431+
assert.Equal(t, &copy.Options{
432+
SignPassphrase: "test-passphrase",
433+
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
434+
ReportWriter: &someStdout,
435+
}, res)
436+
}
418437

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

424-
// More --sign-passphrase-file, --sign-by-sigstore-private-key, --sign-by-sigstore failure cases should be tested here.
443+
// More --sign-by-sigstore-private-key, --sign-by-sigstore failure cases should be tested here.
444+
// --sign-passphrase-file + more than one key option
445+
for _, opts := range [][]string{
446+
{"--sign-by", "gpgFingerprint", "--sign-by-sq-fingerprint", "sqFingerprint"},
447+
{"--sign-by", "gpgFingerprint", "--sign-by-sigstore-private-key", "sigstorePrivateKey"},
448+
{"--sign-by-sq-fingerprint", "sqFingerprint", "--sign-by-sigstore-private-key", "sigstorePrivateKey"},
449+
} {
450+
opts := fakeSharedCopyOptions(t, slices.Concat(opts, []string{
451+
"--sign-passphrase-file", passphraseFile.Name(),
452+
}))
453+
_, _, err = opts.copyOptions(&someStdout)
454+
assert.Error(t, err)
455+
}
425456

426457
// --sign-passphrase-file not found
427458
opts = fakeSharedCopyOptions(t, []string{

contrib/cirrus/runner.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ _run_vendor() {
9999

100100
_run_build() {
101101
make bin/skopeo BUILDTAGS="$BUILDTAGS"
102-
make install PREFIX=/usr/local
102+
make install PREFIX=/usr/local BUILDTAGS="$BUILDTAGS"
103103
}
104104

105105
_run_cross() {

docs/skopeo-copy.1.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,14 @@ See containers-sigstore-signing-params.yaml(5) for details about the file format
107107

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

110+
**--sign-by-sq-fingerprint** _fingerprint_
111+
112+
Add a “simple signing” signature using a Sequoia-PGP key with the specified _fingerprint_.
113+
110114
**--sign-passphrase-file** _path_
111115

112-
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.
116+
The passphrase to use when signing with `--sign-by`, `--sign-by-sigstore-private-key` or `--sign-by-sq-fingerprint`.
117+
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.
113118

114119
**--sign-identity** _reference_
115120

docs/skopeo-sync.1.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,14 @@ See containers-sigstore-signing-params.yaml(5) for details about the file format
103103

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

106+
**--sign-by-sq-fingerprint** _fingerprint_
107+
108+
Add a “simple signing” signature using a Sequoia-PGP key with the specified _fingerprint_.
109+
106110
**--sign-passphrase-file** _path_
107111

108-
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.
112+
The passphrase to use when signing with `--sign-by`, `--sign-by-sigstore-private-key` or `--sign-by-sq-fingerprint`.
113+
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.
109114

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

0 commit comments

Comments
 (0)