Skip to content

Commit

Permalink
add support for commit sigining PGP key passphrases
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
aryan9600 committed May 2, 2023
1 parent 0fb73e6 commit cf455f2
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 14 deletions.
16 changes: 15 additions & 1 deletion docs/spec/v1beta1/imageupdateautomations.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,21 @@ will result in commits with the author `Fluxbot <[email protected]>`.

The optional `signingKey` field can be used to provide a key to sign commits with. It holds a
reference to a secret, which is expected to have a file called `git.asc` containing an
ASCII-armoured PGP key.
ASCII-armoured PGP key. If the private key is protected by a password, you can specify the same
in the secret using the `passphrase` key.

```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: signing-key
namespace: default
stringData:
git.asc: |
<ARMOR ENCODED PGP KEY>
passphrase: <private-key-passphrase>
```

The `messageTemplate` field is a string which will be used as a template for the commit message. If
empty, there is a default message; but you will likely want to provide your own, especially if you
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ require (
github.com/onsi/gomega v1.27.6
github.com/otiai10/copy v1.9.0
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.7.0
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
Expand Down Expand Up @@ -120,6 +119,7 @@ require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
Expand Down
28 changes: 20 additions & 8 deletions internal/controllers/imageupdateautomation_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ import (
"github.com/fluxcd/image-automation-controller/pkg/update"
)

const originRemote = "origin"

const defaultMessageTemplate = `Update from image update automation`

const repoRefKey = ".spec.gitRepository"

const signingSecretKey = "git.asc"
const (
originRemote = "origin"
defaultMessageTemplate = `Update from image update automation`
repoRefKey = ".spec.gitRepository"
signingSecretKey = "git.asc"
signingPassphraseKey = "passphrase"
)

// TemplateData is the type of the value given to the commit message
// template.
Expand Down Expand Up @@ -590,7 +590,19 @@ func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context,
if len(entities) > 1 {
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
}
return entities[0], nil

entity := entities[0]
if entity.PrivateKey.Encrypted {
passphrase, ok := secret.Data[signingPassphraseKey]
if !ok {
return nil, fmt.Errorf("can not use passphrase protected signing key without '%s' field present in secret %s",
signingPassphraseKey, secretName)
}
if err = entity.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
return nil, fmt.Errorf("could not decrypt private key of the signing key present in secret %s: %w", secretName, err)
}
}
return entity, nil
}

// --- events, metrics
Expand Down
15 changes: 11 additions & 4 deletions internal/controllers/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ import (
"testing"
"time"

"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-logr/logr"
. "github.com/onsi/gomega"
"github.com/otiai10/copy"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -417,7 +417,7 @@ func TestImageAutomationReconciler_signedCommit(t *testing.T) {
kr := openpgp.EntityList([]*openpgp.Entity{pgpEntity})
signature := strings.NewReader(commit.PGPSignature)

_, err = openpgp.CheckArmoredDetachedSignature(kr, content, signature)
_, err = openpgp.CheckArmoredDetachedSignature(kr, content, signature, nil)
g.Expect(err).ToNot(HaveOccurred())
},
)
Expand Down Expand Up @@ -1581,6 +1581,7 @@ func createSigningKeyPair(kClient client.Client, name, namespace string) (*openp
if err != nil {
return nil, err
}

// Configure OpenPGP armor encoder.
b := bytes.NewBuffer(nil)
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
Expand All @@ -1594,10 +1595,16 @@ func createSigningKeyPair(kClient client.Client, name, namespace string) (*openp
if err = w.Close(); err != nil {
return nil, err
}

passphrase := "abcde12345"
if err = pgpEntity.PrivateKey.Encrypt([]byte(passphrase)); err != nil {
return nil, err
}
// Create the secret containing signing key.
sec := &corev1.Secret{
Data: map[string][]byte{
"git.asc": b.Bytes(),
signingSecretKey: b.Bytes(),
signingPassphraseKey: []byte(passphrase),
},
}
sec.Name = name
Expand Down

0 comments on commit cf455f2

Please sign in to comment.