From 4aa56f1013c0025b9407836c6f432fd4cb1fd43b Mon Sep 17 00:00:00 2001 From: LWJ Date: Wed, 24 Mar 2021 21:10:23 +0000 Subject: [PATCH 1/5] Add SigningKey to CommitSpec Signed-off-by: LWJ --- api/v1alpha1/imageupdateautomation_types.go | 13 ++ api/v1alpha1/zz_generated.deepcopy.go | 28 +++- ...lkit.fluxcd.io_imageupdateautomations.yaml | 17 ++ .../imageupdateautomation_controller.go | 42 ++++- controllers/update_test.go | 157 ++++++++++++++++++ docs/api/image-automation.md | 48 ++++++ go.mod | 1 + 7 files changed, 303 insertions(+), 3 deletions(-) diff --git a/api/v1alpha1/imageupdateautomation_types.go b/api/v1alpha1/imageupdateautomation_types.go index 108f500d..509498c2 100644 --- a/api/v1alpha1/imageupdateautomation_types.go +++ b/api/v1alpha1/imageupdateautomation_types.go @@ -106,6 +106,9 @@ type CommitSpec struct { // AuthorEmail gives the email to provide when making a commit // +required AuthorEmail string `json:"authorEmail"` + // SigningKey provides the option to sign commits with a GPG key + // +optional + SigningKey *SigningKey `json:"signingKey,omitempty"` // MessageTemplate provides a template for the commit message, // into which will be interpolated the details of the change made. // +optional @@ -142,6 +145,16 @@ type ImageUpdateAutomationStatus struct { meta.ReconcileRequestStatus `json:",inline"` } +// SigningKey references a Kubernetes secret that contains a GPG keypair +type SigningKey struct { + // SecretRef holds the name to a secret that contains a 'value' key + // with the ASCII Armored file (.asc) containing the GPG signing + // keypair as the value. It must be in the same namespace as the + // ImageUpdateAutomation. + // +required + SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` +} + const ( // GitNotAvailableReason is used for ConditionReady when the // automation run cannot proceed because the git repository is diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 87b54472..03829585 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/fluxcd/pkg/apis/meta" "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -28,6 +29,11 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CommitSpec) DeepCopyInto(out *CommitSpec) { *out = *in + if in.SigningKey != nil { + in, out := &in.SigningKey, &out.SigningKey + *out = new(SigningKey) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec. @@ -125,7 +131,7 @@ func (in *ImageUpdateAutomationSpec) DeepCopyInto(out *ImageUpdateAutomationSpec *out = new(UpdateStrategy) **out = **in } - out.Commit = in.Commit + in.Commit.DeepCopyInto(&out.Commit) if in.Push != nil { in, out := &in.Push, &out.Push *out = new(PushSpec) @@ -189,6 +195,26 @@ func (in *PushSpec) DeepCopy() *PushSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SigningKey) DeepCopyInto(out *SigningKey) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(meta.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKey. +func (in *SigningKey) DeepCopy() *SigningKey { + if in == nil { + return nil + } + out := new(SigningKey) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) { *out = *in diff --git a/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml b/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml index 629548ea..88d7a920 100644 --- a/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml +++ b/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml @@ -80,6 +80,23 @@ spec: message, into which will be interpolated the details of the change made. type: string + signingKey: + description: SigningKey provides the option to sign commits with + a GPG key + properties: + secretRef: + description: SecretRef holds the name to a secret that contains + a 'value' key with the ASCII Armored file (.asc) containing + the GPG signing keypair as the value. It must be in the + same namespace as the ImageUpdateAutomation. + properties: + name: + description: Name of the referent + type: string + required: + - name + type: object + type: object required: - authorEmail - authorName diff --git a/controllers/imageupdateautomation_controller.go b/controllers/imageupdateautomation_controller.go index b893e2a9..5c3954fe 100644 --- a/controllers/imageupdateautomation_controller.go +++ b/controllers/imageupdateautomation_controller.go @@ -17,9 +17,11 @@ limitations under the License. package controllers import ( + "bytes" "context" "errors" "fmt" + "golang.org/x/crypto/openpgp" "io/ioutil" "math" "os" @@ -227,10 +229,15 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr var statusMessage string + var signingEntity *openpgp.Entity + if auto.Spec.Commit.SigningKey != nil { + signingEntity, err = r.getSigningEntity(ctx, auto) + } + // The status message depends on what happens next. Since there's // more than one way to succeed, there's some if..else below, and // early returns only on failure. - if rev, err := commitAll(ctx, repo, &auto.Spec.Commit, templateValues); err != nil { + if rev, err := commitAll(repo, &auto.Spec.Commit, templateValues, signingEntity); err != nil { if err == errNoChanges { r.event(ctx, auto, events.EventSeverityInfo, "no updates made") log.V(debug).Info("no changes made in working directory; no commit") @@ -439,7 +446,7 @@ func switchBranch(repo *gogit.Repository, pushBranch string) error { var errNoChanges error = errors.New("no changes made to working directory") -func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateData) (string, error) { +func commitAll(repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateData, ent *openpgp.Entity) (string, error) { working, err := repo.Worktree() if err != nil { return "", err @@ -473,6 +480,7 @@ func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.Comm Email: commit.AuthorEmail, When: time.Now(), }, + SignKey: ent, }); err != nil { return "", err } @@ -480,6 +488,36 @@ func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.Comm return rev.String(), nil } +// getSigningEntity retrieves an OpenPGP entity referenced by the +// provided imagev1.ImageUpdateAutomation for git commit signing +func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, auto imagev1.ImageUpdateAutomation) (*openpgp.Entity, error) { + // get kubernetes secret + secretName := types.NamespacedName{ + Namespace: auto.GetNamespace(), + Name: auto.Spec.Commit.SigningKey.SecretRef.Name, + } + var secret corev1.Secret + if err := r.Get(ctx, secretName, &secret); err != nil { + return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err) + } + + // get data from secret + data, ok := secret.Data["value"] + if !ok { + return nil, fmt.Errorf("signing key secret '%s' does not contain a 'value' key", secretName) + } + + // read entity from secret value + entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err) + } + 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 +} + // push pushes the branch given to the origin using the git library // indicated by `impl`. It's passed both the path to the repo and a // gogit.Repository value, since the latter may as well be used if the diff --git a/controllers/update_test.go b/controllers/update_test.go index 2bbc6054..58a10483 100644 --- a/controllers/update_test.go +++ b/controllers/update_test.go @@ -20,6 +20,8 @@ import ( "bytes" "context" "fmt" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/armor" "io/ioutil" "math/rand" "net/url" @@ -388,6 +390,161 @@ Images: }) }) + Context("commit signing", func() { + + var localRepo *git.Repository + + // generate keypair for signing + pgpEntity, err := openpgp.NewEntity("", "", "", nil) + Expect(err).ToNot(HaveOccurred()) + + BeforeEach(func() { + Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed()) + repoURL := gitServer.HTTPAddressWithCredentials() + repositoryPath + var err error + localRepo, err = git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{ + URL: repoURL, + RemoteName: "origin", + ReferenceName: plumbing.NewBranchReferenceName(branch), + }) + Expect(err).ToNot(HaveOccurred()) + + gitRepoKey := types.NamespacedName{ + Name: "image-auto-" + randStringRunes(5), + Namespace: namespace.Name, + } + gitRepo := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: gitRepoKey.Name, + Namespace: namespace.Name, + }, + Spec: sourcev1.GitRepositorySpec{ + URL: repoURL, + Interval: metav1.Duration{Duration: time.Minute}, + }, + } + Expect(k8sClient.Create(context.Background(), gitRepo)).To(Succeed()) + policyKey := types.NamespacedName{ + Name: "policy-" + randStringRunes(5), + Namespace: namespace.Name, + } + // NB not testing the image reflector controller; this + // will make a "fully formed" ImagePolicy object. + policy := &imagev1_reflect.ImagePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyKey.Name, + Namespace: policyKey.Namespace, + }, + Spec: imagev1_reflect.ImagePolicySpec{ + ImageRepositoryRef: meta.LocalObjectReference{ + Name: "not-expected-to-exist", + }, + Policy: imagev1_reflect.ImagePolicyChoice{ + SemVer: &imagev1_reflect.SemVerPolicy{ + Range: "1.x", + }, + }, + }, + Status: imagev1_reflect.ImagePolicyStatus{ + LatestImage: "helloworld:v1.0.0", + }, + } + Expect(k8sClient.Create(context.Background(), policy)).To(Succeed()) + Expect(k8sClient.Status().Update(context.Background(), policy)).To(Succeed()) + + // Insert a setter reference into the deployment file, + // before creating the automation object itself. + commitInRepo(repoURL, branch, "Install setter marker", func(tmp string) { + replaceMarker(tmp, policyKey) + }) + + // pull the head commit we just pushed, so it's not + // considered a new commit when checking for a commit + // made by automation. + waitForNewHead(localRepo, branch) + + // now create the automation object, and let it (one + // hopes!) make a commit itself. + updateKey := types.NamespacedName{ + Namespace: namespace.Name, + Name: "update-test", + } + updateBySetters := &imagev1.ImageUpdateAutomation{ + ObjectMeta: metav1.ObjectMeta{ + Name: updateKey.Name, + Namespace: updateKey.Namespace, + }, + Spec: imagev1.ImageUpdateAutomationSpec{ + Interval: metav1.Duration{Duration: 2 * time.Hour}, // this is to ensure any subsequent run should be outside the scope of the testing + Checkout: imagev1.GitCheckoutSpec{ + GitRepositoryRef: meta.LocalObjectReference{ + Name: gitRepoKey.Name, + }, + Branch: branch, + }, + Update: &imagev1.UpdateStrategy{ + Strategy: imagev1.UpdateStrategySetters, + }, + Commit: imagev1.CommitSpec{ + SigningKey: &imagev1.SigningKey{}, + }, + }, + } + + // configure OpenPGP armor encoder + b := bytes.NewBuffer(nil) + w, err := armor.Encode(b, openpgp.PrivateKeyType, nil) + Expect(err).ToNot(HaveOccurred()) + + // serialize private key + err = pgpEntity.SerializePrivate(w, nil) + Expect(err).ToNot(HaveOccurred()) + err = w.Close() + Expect(err).ToNot(HaveOccurred()) + + // create the secret containing signing key + sec := &corev1.Secret{ + Data: map[string][]byte{ + "value": b.Bytes(), + }, + } + sec.Name = "signing-key-secret-" + randStringRunes(5) + sec.Namespace = namespace.Name + Expect(k8sClient.Create(context.Background(), sec)).To(Succeed()) + updateBySetters.Spec.Commit.SigningKey.SecretRef = &meta.LocalObjectReference{Name: sec.Name} + + Expect(k8sClient.Create(context.Background(), updateBySetters)).To(Succeed()) + // wait for a new commit to be made by the controller + waitForNewHead(localRepo, branch) + }) + + AfterEach(func() { + Expect(k8sClient.Delete(context.Background(), namespace)).To(Succeed()) + }) + + It("signs the commit with the generated GPG key", func() { + head, _ := localRepo.Head() + commit, err := localRepo.CommitObject(head.Hash()) + Expect(err).ToNot(HaveOccurred()) + + // configure OpenPGP armor encoder + b := bytes.NewBuffer(nil) + w, err := armor.Encode(b, openpgp.PublicKeyType, nil) + Expect(err).ToNot(HaveOccurred()) + + // serialize public key + err = pgpEntity.Serialize(w) + Expect(err).ToNot(HaveOccurred()) + err = w.Close() + Expect(err).ToNot(HaveOccurred()) + + // verify commit + ent, err := commit.Verify(b.String()) + Expect(err).ToNot(HaveOccurred()) + Expect(ent.PrimaryKey.Fingerprint).To(Equal(pgpEntity.PrimaryKey.Fingerprint)) + }) + }) + endToEnd := func(impl, proto string) func() { return func() { var ( diff --git a/docs/api/image-automation.md b/docs/api/image-automation.md index 1446b195..7c2f64f7 100644 --- a/docs/api/image-automation.md +++ b/docs/api/image-automation.md @@ -53,6 +53,20 @@ string +signingKey
+ + +SigningKey + + + + +(Optional) +

SigningKey provides the option to sign commits with a GPG key

+ + + + messageTemplate
string @@ -502,6 +516,40 @@ starting point, if it doesn’t already exist.

+

SigningKey +

+

+(Appears on: +CommitSpec) +

+

SigningKey references a Kubernetes secret that contains a GPG file

+
+
+ + + + + + + + + + + + + +
FieldDescription
+secretRef
+ + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
+

SecretRef holds the name to a secret that contains a ‘value’ key with the GPG file as the value. It must be in the same namespace as the ImageUpdateAutomation.

+
+
+

UpdateStrategy

diff --git a/go.mod b/go.mod index c2e448dd..70feb477 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/onsi/gomega v1.10.2 github.com/otiai10/copy v1.2.0 github.com/spf13/pflag v1.0.5 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad k8s.io/api v0.20.2 k8s.io/apimachinery v0.20.2 k8s.io/client-go v0.20.2 From b668e99a91f9ba55088012f17a39f3a53c9a2978 Mon Sep 17 00:00:00 2001 From: LWJ Date: Wed, 24 Mar 2021 21:39:45 +0000 Subject: [PATCH 2/5] SigningKey modifications to align process with SOPS Signed-off-by: LWJ --- api/v1alpha1/imageupdateautomation_types.go | 4 ++-- .../image.toolkit.fluxcd.io_imageupdateautomations.yaml | 6 +++--- controllers/imageupdateautomation_controller.go | 4 ++-- docs/api/image-automation.md | 7 +++++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/api/v1alpha1/imageupdateautomation_types.go b/api/v1alpha1/imageupdateautomation_types.go index 509498c2..b54aae9a 100644 --- a/api/v1alpha1/imageupdateautomation_types.go +++ b/api/v1alpha1/imageupdateautomation_types.go @@ -147,8 +147,8 @@ type ImageUpdateAutomationStatus struct { // SigningKey references a Kubernetes secret that contains a GPG keypair type SigningKey struct { - // SecretRef holds the name to a secret that contains a 'value' key - // with the ASCII Armored file (.asc) containing the GPG signing + // SecretRef holds the name to a secret that contains a 'git.asc' key + // corresponding to the ASCII Armored file containing the GPG signing // keypair as the value. It must be in the same namespace as the // ImageUpdateAutomation. // +required diff --git a/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml b/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml index 88d7a920..19e14858 100644 --- a/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml +++ b/config/crd/bases/image.toolkit.fluxcd.io_imageupdateautomations.yaml @@ -86,9 +86,9 @@ spec: properties: secretRef: description: SecretRef holds the name to a secret that contains - a 'value' key with the ASCII Armored file (.asc) containing - the GPG signing keypair as the value. It must be in the - same namespace as the ImageUpdateAutomation. + a 'git.asc' key corresponding to the ASCII Armored file + containing the GPG signing keypair as the value. It must + be in the same namespace as the ImageUpdateAutomation. properties: name: description: Name of the referent diff --git a/controllers/imageupdateautomation_controller.go b/controllers/imageupdateautomation_controller.go index 5c3954fe..c280f990 100644 --- a/controllers/imageupdateautomation_controller.go +++ b/controllers/imageupdateautomation_controller.go @@ -502,9 +502,9 @@ func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, } // get data from secret - data, ok := secret.Data["value"] + data, ok := secret.Data["git.asc"] if !ok { - return nil, fmt.Errorf("signing key secret '%s' does not contain a 'value' key", secretName) + return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName) } // read entity from secret value diff --git a/docs/api/image-automation.md b/docs/api/image-automation.md index 7c2f64f7..1730597e 100644 --- a/docs/api/image-automation.md +++ b/docs/api/image-automation.md @@ -522,7 +522,7 @@ starting point, if it doesn’t already exist.

(Appears on: CommitSpec)

-

SigningKey references a Kubernetes secret that contains a GPG file

+

SigningKey references a Kubernetes secret that contains a GPG keypair

@@ -543,7 +543,10 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference From d71e0499efa52871c84de6478e0dc070474b5d93 Mon Sep 17 00:00:00 2001 From: LWJ Date: Wed, 24 Mar 2021 21:42:46 +0000 Subject: [PATCH 3/5] Fix SigningKey secret key in test Signed-off-by: LWJ --- controllers/update_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/update_test.go b/controllers/update_test.go index 58a10483..7b7cd2b6 100644 --- a/controllers/update_test.go +++ b/controllers/update_test.go @@ -505,7 +505,7 @@ Images: // create the secret containing signing key sec := &corev1.Secret{ Data: map[string][]byte{ - "value": b.Bytes(), + "git.asc": b.Bytes(), }, } sec.Name = "signing-key-secret-" + randStringRunes(5) From d1cfabf793bc669d62494e6a18cc1e482ef75f6b Mon Sep 17 00:00:00 2001 From: LWJ Date: Mon, 29 Mar 2021 18:15:57 +0100 Subject: [PATCH 4/5] Fix nil pointer dereference and minor refactor Signed-off-by: LWJ --- api/v1alpha1/imageupdateautomation_types.go | 2 +- api/v1alpha1/zz_generated.deepcopy.go | 9 +--- .../imageupdateautomation_controller.go | 4 +- controllers/update_test.go | 47 ++++++++++--------- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/api/v1alpha1/imageupdateautomation_types.go b/api/v1alpha1/imageupdateautomation_types.go index b54aae9a..4e33d225 100644 --- a/api/v1alpha1/imageupdateautomation_types.go +++ b/api/v1alpha1/imageupdateautomation_types.go @@ -152,7 +152,7 @@ type SigningKey struct { // keypair as the value. It must be in the same namespace as the // ImageUpdateAutomation. // +required - SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` + SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"` } const ( diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 03829585..5e4d4bd7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,6 @@ limitations under the License. package v1alpha1 import ( - "github.com/fluxcd/pkg/apis/meta" "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -32,7 +31,7 @@ func (in *CommitSpec) DeepCopyInto(out *CommitSpec) { if in.SigningKey != nil { in, out := &in.SigningKey, &out.SigningKey *out = new(SigningKey) - (*in).DeepCopyInto(*out) + **out = **in } } @@ -198,11 +197,7 @@ func (in *PushSpec) DeepCopy() *PushSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SigningKey) DeepCopyInto(out *SigningKey) { *out = *in - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - *out = new(meta.LocalObjectReference) - **out = **in - } + out.SecretRef = in.SecretRef } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKey. diff --git a/controllers/imageupdateautomation_controller.go b/controllers/imageupdateautomation_controller.go index c280f990..d18e4b75 100644 --- a/controllers/imageupdateautomation_controller.go +++ b/controllers/imageupdateautomation_controller.go @@ -72,6 +72,8 @@ const defaultMessageTemplate = `Update from image update automation` const repoRefKey = ".spec.gitRepository" const imagePolicyKey = ".spec.update.imagePolicy" +const signingSecretKey = "git.asc" + // TemplateData is the type of the value given to the commit message // template. type TemplateData struct { @@ -502,7 +504,7 @@ func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, } // get data from secret - data, ok := secret.Data["git.asc"] + data, ok := secret.Data[signingSecretKey] if !ok { return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName) } diff --git a/controllers/update_test.go b/controllers/update_test.go index 7b7cd2b6..c69b50c5 100644 --- a/controllers/update_test.go +++ b/controllers/update_test.go @@ -463,6 +463,27 @@ Images: // made by automation. waitForNewHead(localRepo, branch) + // configure OpenPGP armor encoder + b := bytes.NewBuffer(nil) + w, err := armor.Encode(b, openpgp.PrivateKeyType, nil) + Expect(err).ToNot(HaveOccurred()) + + // serialize private key + err = pgpEntity.SerializePrivate(w, nil) + Expect(err).ToNot(HaveOccurred()) + err = w.Close() + Expect(err).ToNot(HaveOccurred()) + + // create the secret containing signing key + sec := &corev1.Secret{ + Data: map[string][]byte{ + "git.asc": b.Bytes(), + }, + } + sec.Name = "signing-key-secret-" + randStringRunes(5) + sec.Namespace = namespace.Name + Expect(k8sClient.Create(context.Background(), sec)).To(Succeed()) + // now create the automation object, and let it (one // hopes!) make a commit itself. updateKey := types.NamespacedName{ @@ -486,33 +507,13 @@ Images: Strategy: imagev1.UpdateStrategySetters, }, Commit: imagev1.CommitSpec{ - SigningKey: &imagev1.SigningKey{}, + SigningKey: &imagev1.SigningKey{ + SecretRef: meta.LocalObjectReference{Name: sec.Name}, + }, }, }, } - // configure OpenPGP armor encoder - b := bytes.NewBuffer(nil) - w, err := armor.Encode(b, openpgp.PrivateKeyType, nil) - Expect(err).ToNot(HaveOccurred()) - - // serialize private key - err = pgpEntity.SerializePrivate(w, nil) - Expect(err).ToNot(HaveOccurred()) - err = w.Close() - Expect(err).ToNot(HaveOccurred()) - - // create the secret containing signing key - sec := &corev1.Secret{ - Data: map[string][]byte{ - "git.asc": b.Bytes(), - }, - } - sec.Name = "signing-key-secret-" + randStringRunes(5) - sec.Namespace = namespace.Name - Expect(k8sClient.Create(context.Background(), sec)).To(Succeed()) - updateBySetters.Spec.Commit.SigningKey.SecretRef = &meta.LocalObjectReference{Name: sec.Name} - Expect(k8sClient.Create(context.Background(), updateBySetters)).To(Succeed()) // wait for a new commit to be made by the controller waitForNewHead(localRepo, branch) From b63b5b277102aec3964d8aadc753fd808a40e4c7 Mon Sep 17 00:00:00 2001 From: LWJ Date: Tue, 30 Mar 2021 13:42:12 +0100 Subject: [PATCH 5/5] Catch OpenPGP failures in test Signed-off-by: LWJ --- controllers/update_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/controllers/update_test.go b/controllers/update_test.go index c69b50c5..4370e53d 100644 --- a/controllers/update_test.go +++ b/controllers/update_test.go @@ -392,11 +392,10 @@ Images: Context("commit signing", func() { - var localRepo *git.Repository - - // generate keypair for signing - pgpEntity, err := openpgp.NewEntity("", "", "", nil) - Expect(err).ToNot(HaveOccurred()) + var ( + localRepo *git.Repository + pgpEntity *openpgp.Entity + ) BeforeEach(func() { Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed()) @@ -463,6 +462,10 @@ Images: // made by automation. waitForNewHead(localRepo, branch) + // generate keypair for signing + pgpEntity, err = openpgp.NewEntity("", "", "", nil) + Expect(err).ToNot(HaveOccurred()) + // configure OpenPGP armor encoder b := bytes.NewBuffer(nil) w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
-

SecretRef holds the name to a secret that contains a ‘value’ key with the GPG file as the value. It must be in the same namespace as the ImageUpdateAutomation.

+

SecretRef holds the name to a secret that contains a ‘git.asc’ key +corresponding to the ASCII Armored file containing the GPG signing +keypair as the value. It must be in the same namespace as the +ImageUpdateAutomation.