Skip to content

Commit f946d7f

Browse files
authored
Merge pull request #510 from aryan9600/pgp-passphrase
Add support for commit signing PGP key passphrases
2 parents 0fb73e6 + cf455f2 commit f946d7f

File tree

4 files changed

+47
-14
lines changed

4 files changed

+47
-14
lines changed

docs/spec/v1beta1/imageupdateautomations.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,21 @@ will result in commits with the author `Fluxbot <[email protected]>`.
226226

227227
The optional `signingKey` field can be used to provide a key to sign commits with. It holds a
228228
reference to a secret, which is expected to have a file called `git.asc` containing an
229-
ASCII-armoured PGP key.
229+
ASCII-armoured PGP key. If the private key is protected by a password, you can specify the same
230+
in the secret using the `passphrase` key.
231+
232+
```yaml
233+
---
234+
apiVersion: v1
235+
kind: Secret
236+
metadata:
237+
name: signing-key
238+
namespace: default
239+
stringData:
240+
git.asc: |
241+
<ARMOR ENCODED PGP KEY>
242+
passphrase: <private-key-passphrase>
243+
```
230244

231245
The `messageTemplate` field is a string which will be used as a template for the commit message. If
232246
empty, there is a default message; but you will likely want to provide your own, especially if you

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ require (
2727
github.com/onsi/gomega v1.27.6
2828
github.com/otiai10/copy v1.9.0
2929
github.com/spf13/pflag v1.0.5
30-
golang.org/x/crypto v0.7.0
3130
k8s.io/api v0.26.3
3231
k8s.io/apimachinery v0.26.3
3332
k8s.io/client-go v0.26.3
@@ -120,6 +119,7 @@ require (
120119
go.uber.org/atomic v1.10.0 // indirect
121120
go.uber.org/multierr v1.8.0 // indirect
122121
go.uber.org/zap v1.24.0 // indirect
122+
golang.org/x/crypto v0.7.0 // indirect
123123
golang.org/x/mod v0.9.0 // indirect
124124
golang.org/x/net v0.8.0 // indirect
125125
golang.org/x/oauth2 v0.6.0 // indirect

internal/controllers/imageupdateautomation_controller.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ import (
6767
"github.com/fluxcd/image-automation-controller/pkg/update"
6868
)
6969

70-
const originRemote = "origin"
71-
72-
const defaultMessageTemplate = `Update from image update automation`
73-
74-
const repoRefKey = ".spec.gitRepository"
75-
76-
const signingSecretKey = "git.asc"
70+
const (
71+
originRemote = "origin"
72+
defaultMessageTemplate = `Update from image update automation`
73+
repoRefKey = ".spec.gitRepository"
74+
signingSecretKey = "git.asc"
75+
signingPassphraseKey = "passphrase"
76+
)
7777

7878
// TemplateData is the type of the value given to the commit message
7979
// template.
@@ -590,7 +590,19 @@ func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context,
590590
if len(entities) > 1 {
591591
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
592592
}
593-
return entities[0], nil
593+
594+
entity := entities[0]
595+
if entity.PrivateKey.Encrypted {
596+
passphrase, ok := secret.Data[signingPassphraseKey]
597+
if !ok {
598+
return nil, fmt.Errorf("can not use passphrase protected signing key without '%s' field present in secret %s",
599+
signingPassphraseKey, secretName)
600+
}
601+
if err = entity.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
602+
return nil, fmt.Errorf("could not decrypt private key of the signing key present in secret %s: %w", secretName, err)
603+
}
604+
}
605+
return entity, nil
594606
}
595607

596608
// --- events, metrics

internal/controllers/update_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ import (
3131
"testing"
3232
"time"
3333

34+
"github.com/ProtonMail/go-crypto/openpgp"
35+
"github.com/ProtonMail/go-crypto/openpgp/armor"
3436
securejoin "github.com/cyphar/filepath-securejoin"
3537
"github.com/go-logr/logr"
3638
. "github.com/onsi/gomega"
3739
"github.com/otiai10/copy"
38-
"golang.org/x/crypto/openpgp"
39-
"golang.org/x/crypto/openpgp/armor"
4040
corev1 "k8s.io/api/core/v1"
4141
apimeta "k8s.io/apimachinery/pkg/api/meta"
4242
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -417,7 +417,7 @@ func TestImageAutomationReconciler_signedCommit(t *testing.T) {
417417
kr := openpgp.EntityList([]*openpgp.Entity{pgpEntity})
418418
signature := strings.NewReader(commit.PGPSignature)
419419

420-
_, err = openpgp.CheckArmoredDetachedSignature(kr, content, signature)
420+
_, err = openpgp.CheckArmoredDetachedSignature(kr, content, signature, nil)
421421
g.Expect(err).ToNot(HaveOccurred())
422422
},
423423
)
@@ -1581,6 +1581,7 @@ func createSigningKeyPair(kClient client.Client, name, namespace string) (*openp
15811581
if err != nil {
15821582
return nil, err
15831583
}
1584+
15841585
// Configure OpenPGP armor encoder.
15851586
b := bytes.NewBuffer(nil)
15861587
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
@@ -1594,10 +1595,16 @@ func createSigningKeyPair(kClient client.Client, name, namespace string) (*openp
15941595
if err = w.Close(); err != nil {
15951596
return nil, err
15961597
}
1598+
1599+
passphrase := "abcde12345"
1600+
if err = pgpEntity.PrivateKey.Encrypt([]byte(passphrase)); err != nil {
1601+
return nil, err
1602+
}
15971603
// Create the secret containing signing key.
15981604
sec := &corev1.Secret{
15991605
Data: map[string][]byte{
1600-
"git.asc": b.Bytes(),
1606+
signingSecretKey: b.Bytes(),
1607+
signingPassphraseKey: []byte(passphrase),
16011608
},
16021609
}
16031610
sec.Name = name

0 commit comments

Comments
 (0)