Skip to content

Commit a65f79e

Browse files
authored
Merge pull request #1479 from abregar/issues-309-691
Add config key for 'gpgKeys' and allow multiple keyRefs when signing with gpg, fixing Issues #309 and #691
2 parents 7d23196 + c6a9f82 commit a65f79e

18 files changed

+357
-159
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,4 @@ List of contributors, in chronological order:
7979
* Ato Araki (https://github.com/atotto)
8080
* Roman Lebedev (https://github.com/LebedevRI)
8181
* Brian Witt (https://github.com/bwitt)
82+
* Ales Bregar (https://github.com/abregar)

api/publish.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616
type signingParams struct {
1717
// Don't sign published repository
1818
Skip bool ` json:"Skip" example:"false"`
19-
// GPG key ID to use when signing the release, if not specified default key is used
20-
GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"`
19+
// GPG key ID(s) to use when signing the release, separated by comma, and if not specified, default configured key(s) are used
20+
GpgKey string ` json:"GpgKey" example:"KEY_ID_a, KEY_ID_b"`
2121
// GPG keyring to use (instead of default)
2222
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
2323
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
@@ -41,7 +41,21 @@ func getSigner(options *signingParams) (pgp.Signer, error) {
4141
}
4242

4343
signer := context.GetSigner()
44-
signer.SetKey(options.GpgKey)
44+
45+
var multiGpgKeys []string
46+
// REST params have priority over config
47+
if options.GpgKey != "" {
48+
for _, p := range strings.Split(options.GpgKey, ",") {
49+
if t := strings.TrimSpace(p); t != "" {
50+
multiGpgKeys = append(multiGpgKeys, t)
51+
}
52+
}
53+
} else if len(context.Config().GpgKeys) > 0 {
54+
multiGpgKeys = context.Config().GpgKeys
55+
}
56+
for _, gpgKey := range multiGpgKeys {
57+
signer.SetKey(gpgKey)
58+
}
4559
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
4660
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
4761

cmd/publish.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cmd
22

33
import (
4+
"strings"
5+
46
"github.com/aptly-dev/aptly/pgp"
57
"github.com/smira/commander"
68
"github.com/smira/flag"
@@ -12,7 +14,20 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
1214
}
1315

1416
signer := context.GetSigner()
15-
signer.SetKey(flags.Lookup("gpg-key").Value.String())
17+
18+
var gpgKeys []string
19+
20+
// CLI args have priority over config
21+
cliKeys := flags.Lookup("gpg-key").Value.Get().([]string)
22+
if len(cliKeys) > 0 {
23+
gpgKeys = cliKeys
24+
} else if len(context.Config().GpgKeys) > 0 {
25+
gpgKeys = context.Config().GpgKeys
26+
}
27+
28+
for _, gpgKey := range gpgKeys {
29+
signer.SetKey(gpgKey)
30+
}
1631
signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String())
1732
signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
1833
signer.SetBatch(flags.Lookup("batch").Value.Get().(bool))
@@ -26,6 +41,23 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
2641

2742
}
2843

44+
type gpgKeyFlag struct {
45+
gpgKeys []string
46+
}
47+
48+
func (k *gpgKeyFlag) Set(value string) error {
49+
k.gpgKeys = append(k.gpgKeys, value)
50+
return nil
51+
}
52+
53+
func (k *gpgKeyFlag) Get() interface{} {
54+
return k.gpgKeys
55+
}
56+
57+
func (k *gpgKeyFlag) String() string {
58+
return strings.Join(k.gpgKeys, ",")
59+
}
60+
2961
func makeCmdPublish() *commander.Command {
3062
return &commander.Command{
3163
UsageLine: "publish",

cmd/publish_repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Example:
3434
}
3535
cmd.Flag.String("distribution", "", "distribution name to publish")
3636
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
37-
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
37+
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
3838
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
3939
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
4040
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")

cmd/publish_snapshot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ Example:
234234
}
235235
cmd.Flag.String("distribution", "", "distribution name to publish")
236236
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
237-
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
237+
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
238238
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
239239
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
240240
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")

cmd/publish_switch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ This command would switch published repository (with one component) named ppa/wh
155155
`,
156156
Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
157157
}
158-
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
158+
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
159159
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
160160
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
161161
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")

cmd/publish_update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ Example:
127127
`,
128128
Flag: *flag.NewFlagSet("aptly-publish-update", flag.ExitOnError),
129129
}
130-
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
130+
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
131131
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
132132
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
133133
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")

docs/Publish.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,26 @@ Repositories can be published to local directories, Amazon S3 buckets, Azure or
1111

1212
GPG key is required to sign any published repository. The key pari should be generated before publishing.
1313

14-
Publiс part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
14+
Public part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
15+
16+
* Multiple signing keys can be defined in aptly.conf using the gpgKeys array:
17+
```
18+
"gpgKeys": [
19+
"KEY_ID_x",
20+
"KEY_ID_y"
21+
]
22+
```
23+
24+
* It is also possible to pass multiple keys via the CLI using the repeatable `--gpg-key` flag:
25+
```
26+
aptly publish repo my-repo --gpg-key=KEY_ID_a --gpg-key=KEY_ID_b
27+
```
28+
* When using the REST API, the `gpgKey` parameter supports a comma-separated list of key IDs:
29+
```
30+
"gpgKey": "KEY_ID_a,KEY_ID_b"
31+
```
32+
* If `--gpg-key` is specified on the command line, or `gpgKey` is provided via the REST API, it takes precedence over any gpgKeys configuration in aptly.conf.
33+
* With multi-key support, aptly will sign all Release files (both clearsigned and detached signatures) with each provided key, ensuring a smooth key rotation process while maintaining compatibility for existing clients.
1534

1635
#### Parameters
1736

pgp/gnupg.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var (
2222
type GpgSigner struct {
2323
gpg string
2424
version GPGVersion
25-
keyRef string
25+
keyRefs []string
2626
keyring, secretKeyring string
2727
passphrase, passphraseFile string
2828
batch bool
@@ -35,7 +35,14 @@ func (g *GpgSigner) SetBatch(batch bool) {
3535

3636
// SetKey sets key ID to use when signing files
3737
func (g *GpgSigner) SetKey(keyRef string) {
38-
g.keyRef = keyRef
38+
keyRef = strings.TrimSpace(keyRef)
39+
if keyRef != "" {
40+
if g.keyRefs == nil {
41+
g.keyRefs = []string{keyRef}
42+
} else {
43+
g.keyRefs = append(g.keyRefs, keyRef)
44+
}
45+
}
3946
}
4047

4148
// SetKeyRing allows to set custom keyring and secretkeyring
@@ -57,8 +64,8 @@ func (g *GpgSigner) gpgArgs() []string {
5764
args = append(args, "--secret-keyring", g.secretKeyring)
5865
}
5966

60-
if g.keyRef != "" {
61-
args = append(args, "-u", g.keyRef)
67+
for _, k := range g.keyRefs {
68+
args = append(args, "-u", k)
6269
}
6370

6471
if g.passphrase != "" || g.passphraseFile != "" {

system/files/aptly-dual.pub

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-----BEGIN PGP PUBLIC KEY BLOCK-----
2+
3+
mQGiBFL7pY8RBAC5uHg/9AuGJ7EF7RYty89IDLeqvlPe710eDQpJ+itsOaA/5rr3
4+
IV1LMlqHpM2rkZkAPpARwjrga2ByJ1ww77Zq2uPqJIO2LZYWTLXic9Zity2OVu3Z
5+
XwtdsqagIMfT5dAgNmhe5lL7qgGUwYcFFa52s7U4qO0z2FfwHW1IQrnMpwCg5RQh
6+
Uqs5iUKdDtoeQjX5mWgQhjEEAI1zfXUvvcOrRsDlGNKYZigZiWC6J46jeR8Nnf9C
7+
WwhXS2fzQaJyDq9DorkvPZgWUAaLLCdfGETqLzDKajynhS1+OnfFQNzvkvEPRBSb
8+
C5k+GOF2E1E9rGXb31+1XZTcdIprp4/F3RNLLWNUwfgPLWJx9NzHTYqgBStecHkC
9+
ySZRA/9PNFAbeJZ27HNuzoGnAa0piZDLeAAHsM1V6cosMh7U1IZqjZcrMC9YXNxH
10+
2D90PvoBvpufCMRzL/fOVPT1JzQGYoKIX17Nmzvdq/a4YyLWRODjvWXd94bae2Xd
11+
Vy03DYhfp8VOVJW6HuAX9JN6MKXSNxaibgOPjU822Hxd1iCIQ7QtQXB0bHkgVGVz
12+
dGVyIChkb24ndCB1c2UgaXQpIDx0ZXN0QGFwdGx5LmluZm8+iGIEExECACIFAlL7
13+
pY8CGyMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECHbuJwW2z5t2sQAoNn+
14+
0cADZa66HZNY2qJi44Oq4hjaAJsHzj9JKAHEpdix5N7b6QvaZQZYhrkBDQRS+6WP
15+
EAQA9BX+kbIM6VJYoyY9vUHXfAF4E2y2M7vl9knZ+jMPfMbI7dE3gRJQb3mngST5
16+
7eZWawo1DNE6h3LbHsB4mpro9XLUXUMBgXRsOq4D5E0ygvDZ/tJhy0AwFiTOXKEs
17+
/erzmbF7j/TWh4LVHXFI9DrnN0+EeF/mQC/wzX7WGCKe70cAAwUEAMr7959zUYNp
18+
E3v4IquIJpD22bT/FiyQjFG8yGy36c+7mOP3VWi0lz5yFqqeR9NDFuLDSwOEi0nB
19+
zXNmimLy+hIwMaHjbQLjLODmy/T9wKCgeAmK1ygT6YBGJJflThZ05M80T5hBtRA9
20+
z2eoTn0wbi6MLmD/rbEt+lUPfSA4V0t2iEkEGBECAAkFAlL7pY8CGwwACgkQIdu4
21+
nBbbPm05hgCgvYatZXRbEdZ91jJCQi1KI7lJ5Y8AnjvrHU0g84mE45QZFegZzzQo
22+
9relmDMEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqz
23+
Y8yWdcj3HUy0L0FwdGx5IFNlY29uZGFyeSBTaWduaW5nIEtleSA8YXB0bHlAZXhh
24+
bXBsZS5jb20+iJYEExYKAD4WIQSu4W3wGDVPZ/5fXHK79OGUNOkeTgUCZ3YCRgIb
25+
AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC79OGUNOkeTid/AP9A
26+
kIMn2qI5TqZgzrnPt7SN16VvpMppPb2H0m0P6knQKQD8DHcLcrqAl2cjcEuntv75
27+
gOnEvmPDAO6S1rc8UgcWdQQ=
28+
=XPoo
29+
-----END PGP PUBLIC KEY BLOCK-----

0 commit comments

Comments
 (0)