Skip to content

Commit cf455f2

Browse files
committed
add support for commit sigining PGP key passphrases
Add support for decrypting the private key of the commit signing PGP key. The secret specified in `spec.commit.signingKey.secretRef` can now optionally have a `passphrase` key where it's value is the password to be used for decryptin the private key. Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent 0fb73e6 commit cf455f2

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)