diff --git a/api/v1/gitrepository_types.go b/api/v1/gitrepository_types.go
index d7468fca6..f9fdaa906 100644
--- a/api/v1/gitrepository_types.go
+++ b/api/v1/gitrepository_types.go
@@ -242,7 +242,8 @@ type GitRepositoryVerification struct {
Mode GitVerificationMode `json:"mode,omitempty"`
// SecretRef specifies the Secret containing the public keys of trusted Git
- // authors.
+ // authors. PGP public keys must be stored under keys with the .asc suffix,
+ // and SSH public keys must be stored under keys with the .sshpub suffix.
// +required
SecretRef meta.LocalObjectReference `json:"secretRef"`
}
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
index 1ad69e5ed..aeae0699d 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
@@ -226,7 +226,8 @@ spec:
secretRef:
description: |-
SecretRef specifies the Secret containing the public keys of trusted Git
- authors.
+ authors. PGP public keys must be stored under keys with the .asc suffix,
+ and SSH public keys must be stored under keys with the .sshpub suffix.
properties:
name:
description: Name of the referent.
diff --git a/docs/api/v1/source.md b/docs/api/v1/source.md
index 647fa7269..78cbabcea 100644
--- a/docs/api/v1/source.md
+++ b/docs/api/v1/source.md
@@ -2491,7 +2491,8 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
SecretRef specifies the Secret containing the public keys of trusted Git
-authors.
+authors. PGP public keys must be stored under keys with the .asc suffix,
+and SSH public keys must be stored under keys with the .sshpub suffix.
|
diff --git a/docs/spec/v1/gitrepositories.md b/docs/spec/v1/gitrepositories.md
index 96e1590dc..97fecc4e7 100644
--- a/docs/spec/v1/gitrepositories.md
+++ b/docs/spec/v1/gitrepositories.md
@@ -639,7 +639,10 @@ signatures. The field offers two subfields:
the commit object pointed to by the tag.
- `.secretRef.name`, to specify a reference to a Secret in the same namespace as
- the GitRepository. Containing the (PGP) public keys of trusted Git authors.
+ the GitRepository. Containing the public keys of trusted Git authors. PGP
+ public keys must be stored under keys with the `.asc` suffix, and SSH public
+ keys must be stored under keys with the `.sshpub` suffix. Keys without a
+ recognized suffix are treated as PGP key rings for backward compatibility.
```yaml
---
@@ -695,6 +698,44 @@ kubectl create secret generic pgp-public-keys \
-o yaml
```
+#### SSH verification
+
+SSH-signed commits and tags can also be verified. Store SSH public keys in
+`authorized_keys` format under keys with the `.sshpub` suffix in the same
+Secret:
+
+```yaml
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: verification-keys
+ namespace: default
+type: Opaque
+data:
+ author1.asc:
+ author2.sshpub:
+```
+
+Generating an SSH key pair and creating the Secret:
+
+```sh
+# Generate an SSH key pair for signing
+ssh-keygen -t ed25519 -N '' -f /tmp/signing_key
+# Generate secret with the public key
+kubectl create secret generic verification-keys \
+ --from-file=author2.sshpub=/tmp/signing_key.pub \
+ -o yaml
+```
+
+A single Secret can contain both PGP (`.asc`) and SSH (`.sshpub`) keys. The
+controller detects the signature type of each Git object (PGP or SSH) and
+dispatches verification accordingly.
+
+PGP verification reports the PGP key ID in the success message (e.g.
+`5982D0279C227FFD`), while SSH verification reports the SHA256 fingerprint
+(e.g. `SHA256:uNiVztksCsDhcc0u9e8BgrJXVGDaf6s7kOsTmI9N7sM`).
+
### Ignore
`.spec.ignore` is an optional field to specify rules in [the `.gitignore`
diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go
index d5b39e7a9..a3c27c9b6 100644
--- a/internal/controller/gitrepository_controller.go
+++ b/internal/controller/gitrepository_controller.go
@@ -71,6 +71,41 @@ import (
"github.com/fluxcd/source-controller/internal/util"
)
+const (
+ // publicKeyPGPSuffix is the Secret data key suffix for PGP public keys.
+ publicKeyPGPSuffix = ".asc"
+ // publicKeySSHSuffix is the Secret data key suffix for SSH public keys.
+ publicKeySSHSuffix = ".sshpub"
+)
+
+// gitSigner abstracts the verification methods shared by git.Commit and git.Tag.
+type gitSigner interface {
+ SignatureType() string
+ VerifyPGP(keyRings ...string) (string, error)
+ VerifySSH(authorizedKeys ...string) (string, error)
+}
+
+// verifyGitObject dispatches signature verification based on the signature type
+// of the given git object. It returns the key identity (PGP key ID or SSH
+// fingerprint) on success, or an error if verification fails or the required
+// key type is missing from the Secret.
+func verifyGitObject(obj gitSigner, keyRings []string, authorizedKeys []string) (string, error) {
+ switch obj.SignatureType() {
+ case "openpgp":
+ if len(keyRings) == 0 {
+ return "", fmt.Errorf("PGP signature detected but no PGP public keys found in secret (keys with %s suffix)", publicKeyPGPSuffix)
+ }
+ return obj.VerifyPGP(keyRings...)
+ case "ssh":
+ if len(authorizedKeys) == 0 {
+ return "", fmt.Errorf("SSH signature detected but no SSH public keys found in secret (keys with %s suffix)", publicKeySSHSuffix)
+ }
+ return obj.VerifySSH(authorizedKeys...)
+ default:
+ return "", fmt.Errorf("unsupported signature type: %s", obj.SignatureType())
+ }
+}
+
// gitRepositoryReadyCondition contains the information required to summarize a
// v1.GitRepository Ready Condition.
var gitRepositoryReadyCondition = summarize.Conditions{
@@ -1093,7 +1128,7 @@ func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sour
return sreconcile.ResultSuccess, nil
}
- // Get secret with GPG data
+ // Get secret with public key data
publicKeySecret := types.NamespacedName{
Namespace: obj.Namespace,
Name: obj.Spec.Verification.SecretRef.Name,
@@ -1101,7 +1136,7 @@ func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sour
secret := &corev1.Secret{}
if err := r.Client.Get(ctx, publicKeySecret, secret); err != nil {
e := serror.NewGeneric(
- fmt.Errorf("PGP public keys secret error: %w", err),
+ fmt.Errorf("public keys secret error: %w", err),
"VerificationError",
)
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e)
@@ -1109,8 +1144,16 @@ func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sour
}
var keyRings []string
- for _, v := range secret.Data {
- keyRings = append(keyRings, string(v))
+ var authorizedKeys []string
+ for k, v := range secret.Data {
+ if strings.HasSuffix(k, publicKeySSHSuffix) {
+ authorizedKeys = append(authorizedKeys, string(v))
+ } else if strings.HasSuffix(k, publicKeyPGPSuffix) {
+ keyRings = append(keyRings, string(v))
+ } else {
+ // Provide fallback to support previous undocumented behavior
+ keyRings = append(keyRings, string(v))
+ }
}
var message strings.Builder
@@ -1140,38 +1183,34 @@ func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sour
return sreconcile.ResultEmpty, err
}
- // Verify tag with GPG data from secret
- tagEntity, err := tag.Verify(keyRings...)
+ entity, err := verifyGitObject(tag, keyRings, authorizedKeys)
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("signature verification of tag '%s' failed: %w", tag.String(), err),
"InvalidTagSignature",
)
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e)
- // Return error in the hope the secret changes
return sreconcile.ResultEmpty, e
}
- message.WriteString(fmt.Sprintf("verified signature of\n\t- tag '%s' with key '%s'", tag.String(), tagEntity))
+ message.WriteString(fmt.Sprintf("verified signature of\n\t- tag '%s' with key '%s'", tag.String(), entity))
}
if obj.Spec.Verification.VerifyHEAD() {
- // Verify commit with GPG data from secret
- headEntity, err := commit.Verify(keyRings...)
+ entity, err := verifyGitObject(&commit, keyRings, authorizedKeys)
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("signature verification of commit '%s' failed: %w", commit.Hash.String(), err),
"InvalidCommitSignature",
)
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e)
- // Return error in the hope the secret changes
return sreconcile.ResultEmpty, e
}
// If we also verified the tag previously, then append to the message.
if message.Len() > 0 {
- message.WriteString(fmt.Sprintf("\n\t- commit '%s' with key '%s'", commit.Hash.String(), headEntity))
+ message.WriteString(fmt.Sprintf("\n\t- commit '%s' with key '%s'", commit.Hash.String(), entity))
} else {
- message.WriteString(fmt.Sprintf("verified signature of\n\t- commit '%s' with key '%s'", commit.Hash.String(), headEntity))
+ message.WriteString(fmt.Sprintf("verified signature of\n\t- commit '%s' with key '%s'", commit.Hash.String(), entity))
}
}
diff --git a/internal/controller/gitrepository_controller_test.go b/internal/controller/gitrepository_controller_test.go
index 4b874e070..84b2074a8 100644
--- a/internal/controller/gitrepository_controller_test.go
+++ b/internal/controller/gitrepository_controller_test.go
@@ -168,6 +168,40 @@ zh1+m2pFDEhWZkaFtQbSEpXMIJ9DsCoyQL4Knl+89VxHsrIyAJsmGb3V8xvtv5w9
QuWtsDnYbvDHtTpu1NZChVrnr/l1k3C2fcLhV1s583AvhGMkbgSXkQ==
=Tdjz
-----END PGP PUBLIC KEY BLOCK-----
+`
+
+ encodedSSHCommitFixture = `tree 7c5bd8f246ab8e8c6a5749c3d2f44018aa029fb8
+author Test User 1767225600 +0000
+committer Test User 1767225600 +0000
+
+Test commit signed with ed25519
+`
+
+ signatureSSHCommitFixture = `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgrYSEhPKV/65kzG2JLYU+586anT
+AORbbZ0UW9qzon28EAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQD72vwusTGiRbH8ZtPm53vl065Ocv6Sp6VbHq4mkONAM0mzDLrD7BmAgWkjtmL2JpK
+msqgFJcKs6Z3E1zH86fQ0=
+-----END SSH SIGNATURE-----
+`
+
+ encodedSSHTagFixture = `object b01ca16de562c561f62427eb0a30cb775d1c1dab
+type commit
+tag test-tag-ed25519
+tagger Test User 1767225600 +0000
+
+Test tag signed with ed25519
+`
+
+ signatureSSHTagFixture = `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgrYSEhPKV/65kzG2JLYU+586anT
+AORbbZ0UW9qzon28EAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQOZe8xDqF1oktx3h9p3EQKSx13zFVHQ2YcpF/HfeuQusKA1tvJY+T+ykPH4+zbva93
+KXi2P5xm89dQni0kTeAAY=
+-----END SSH SIGNATURE-----
+`
+
+ sshAuthorizedKeysFixture = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2EhITylf+uZMxtiS2FPufOmp0wDkW22dFFvas6J9vB test-ed25519@example.com
`
)
@@ -2223,7 +2257,7 @@ func TestGitRepositoryReconciler_verifySignature(t *testing.T) {
},
},
{
- name: "Invalid commit makes SourceVerifiedCondition=False and returns error",
+ name: "No PGP keys in secret makes SourceVerifiedCondition=False and returns error",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "existing",
@@ -2245,7 +2279,7 @@ func TestGitRepositoryReconciler_verifySignature(t *testing.T) {
},
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "signature verification of commit 'shasum' failed: unable to verify Git commit: unable to verify payload with any of the given key rings"),
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "signature verification of commit 'shasum' failed: PGP signature detected but no PGP public keys found in secret (keys with .asc suffix)"),
},
},
{
@@ -2325,7 +2359,7 @@ func TestGitRepositoryReconciler_verifySignature(t *testing.T) {
},
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "VerificationError", "PGP public keys secret error: secrets \"none-existing\" not found"),
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "VerificationError", "public keys secret error: secrets \"none-existing\" not found"),
},
},
{
@@ -2347,6 +2381,372 @@ func TestGitRepositoryReconciler_verifySignature(t *testing.T) {
want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{},
},
+ {
+ name: "Valid SSH-signed commit with mode=HEAD makes SourceVerifiedCondition=True",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ssh-keys",
+ },
+ Data: map[string][]byte{
+ "author1.sshpub": []byte(sshAuthorizedKeysFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHCommitFixture),
+ Signature: signatureSSHCommitFixture,
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "ssh-keys",
+ },
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ wantSourceVerificationMode: ptrToVerificationMode(sourcev1.ModeGitHEAD),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- commit 'shasum' with key 'SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4'"),
+ },
+ },
+ {
+ name: "Valid SSH-signed tag with mode=Tag makes SourceVerifiedCondition=True",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ssh-keys",
+ },
+ Data: map[string][]byte{
+ "author1.sshpub": []byte(sshAuthorizedKeysFixture),
+ },
+ },
+ commit: git.Commit{
+ ReferencingTag: &git.Tag{
+ Name: "v0.1.0",
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHTagFixture),
+ Signature: signatureSSHTagFixture,
+ },
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Reference = &sourcev1.GitRepositoryRef{
+ Tag: "v0.1.0",
+ }
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitTag,
+ SecretRef: meta.LocalObjectReference{
+ Name: "ssh-keys",
+ },
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ wantSourceVerificationMode: ptrToVerificationMode(sourcev1.ModeGitTag),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- tag 'v0.1.0@shasum' with key 'SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4'"),
+ },
+ },
+ {
+ name: "Valid SSH-signed tag and commit with mode=TagAndHEAD makes SourceVerifiedCondition=True",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ssh-keys",
+ },
+ Data: map[string][]byte{
+ "author1.sshpub": []byte(sshAuthorizedKeysFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHCommitFixture),
+ Signature: signatureSSHCommitFixture,
+ ReferencingTag: &git.Tag{
+ Name: "v0.1.0",
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHTagFixture),
+ Signature: signatureSSHTagFixture,
+ },
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Reference = &sourcev1.GitRepositoryRef{
+ Tag: "v0.1.0",
+ }
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitTagAndHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "ssh-keys",
+ },
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ wantSourceVerificationMode: ptrToVerificationMode(sourcev1.ModeGitTagAndHEAD),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- tag 'v0.1.0@shasum' with key 'SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4'\n\t- commit 'shasum' with key 'SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4'"),
+ },
+ },
+ {
+ name: "SSH-signed commit but Secret only has .asc keys makes SourceVerifiedCondition=False",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pgp-keys-only",
+ },
+ Data: map[string][]byte{
+ "author1.asc": []byte(armoredKeyRingFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHCommitFixture),
+ Signature: signatureSSHCommitFixture,
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "pgp-keys-only",
+ },
+ }
+ },
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "SSH signature detected but no SSH public keys found in secret (keys with .sshpub suffix)"),
+ },
+ },
+ {
+ name: "PGP-signed commit but Secret only has .sshpub keys makes SourceVerifiedCondition=False",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ssh-keys-only",
+ },
+ Data: map[string][]byte{
+ "author1.sshpub": []byte(sshAuthorizedKeysFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedCommitFixture),
+ Signature: signatureCommitFixture,
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "ssh-keys-only",
+ },
+ }
+ },
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "PGP signature detected but no PGP public keys found in secret (keys with .asc suffix)"),
+ },
+ },
+ {
+ name: "SSH-signed tag but Secret only has .asc keys makes SourceVerifiedCondition=False",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pgp-keys-only",
+ },
+ Data: map[string][]byte{
+ "author1.asc": []byte(armoredKeyRingFixture),
+ },
+ },
+ commit: git.Commit{
+ ReferencingTag: &git.Tag{
+ Name: "v0.1.0",
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHTagFixture),
+ Signature: signatureSSHTagFixture,
+ },
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Reference = &sourcev1.GitRepositoryRef{
+ Tag: "v0.1.0",
+ }
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitTag,
+ SecretRef: meta.LocalObjectReference{
+ Name: "pgp-keys-only",
+ },
+ }
+ },
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidTagSignature", "signature verification of tag 'v0.1.0@shasum' failed: SSH signature detected but no SSH public keys found in secret (keys with .sshpub suffix)"),
+ },
+ },
+ {
+ name: "Invalid SSH key in .sshpub entry makes SourceVerifiedCondition=False",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "invalid-ssh-key",
+ },
+ Data: map[string][]byte{
+ "author1.sshpub": []byte("ssh-ed25519 INVALIDKEY test@example.com"),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHCommitFixture),
+ Signature: signatureSSHCommitFixture,
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "invalid-ssh-key",
+ },
+ }
+ },
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "signature verification of commit 'shasum' failed: unable to verify Git commit SSH signature"),
+ },
+ },
+ {
+ name: "Mixed: PGP-signed tag and SSH-signed commit with both key types in Secret and mode=TagAndHEAD makes SourceVerifiedCondition=True",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "mixed-keys",
+ },
+ Data: map[string][]byte{
+ "author1.asc": []byte(armoredKeyRingFixture),
+ "author1.sshpub": []byte(sshAuthorizedKeysFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHCommitFixture),
+ Signature: signatureSSHCommitFixture,
+ ReferencingTag: &git.Tag{
+ Name: "v0.1.0",
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedTagFixture),
+ Signature: signatureTagFixture,
+ },
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Reference = &sourcev1.GitRepositoryRef{
+ Tag: "v0.1.0",
+ }
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitTagAndHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "mixed-keys",
+ },
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ wantSourceVerificationMode: ptrToVerificationMode(sourcev1.ModeGitTagAndHEAD),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- tag 'v0.1.0@shasum' with key '5982D0279C227FFD'\n\t- commit 'shasum' with key 'SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4'"),
+ },
+ },
+ {
+ name: "Mixed: SSH-signed tag and PGP-signed commit with both key types in Secret and mode=TagAndHEAD makes SourceVerifiedCondition=True",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "mixed-keys-reverse",
+ },
+ Data: map[string][]byte{
+ "author1.asc": []byte(armoredKeyRingFixture),
+ "author1.sshpub": []byte(sshAuthorizedKeysFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedCommitFixture),
+ Signature: signatureCommitFixture,
+ ReferencingTag: &git.Tag{
+ Name: "v0.1.0",
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedSSHTagFixture),
+ Signature: signatureSSHTagFixture,
+ },
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Reference = &sourcev1.GitRepositoryRef{
+ Tag: "v0.1.0",
+ }
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitTagAndHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "mixed-keys-reverse",
+ },
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ wantSourceVerificationMode: ptrToVerificationMode(sourcev1.ModeGitTagAndHEAD),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- tag 'v0.1.0@shasum' with key 'SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4'\n\t- commit 'shasum' with key '5982D0279C227FFD'"),
+ },
+ },
+ {
+ name: "Unsupported signature type makes SourceVerifiedCondition=False",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "existing",
+ },
+ Data: map[string][]byte{
+ "foo": []byte(armoredKeyRingFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedCommitFixture),
+ Signature: "-----BEGIN SIGNED MESSAGE-----\nsome x509 signature\n-----END SIGNED MESSAGE-----",
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "existing",
+ },
+ }
+ },
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "unsupported signature type: x509"),
+ },
+ },
+ {
+ name: "Valid commit with explicit .asc key suffix makes SourceVerifiedCondition=True",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "explicit-asc",
+ },
+ Data: map[string][]byte{
+ "foo.asc": []byte(armoredKeyRingFixture),
+ },
+ },
+ commit: git.Commit{
+ Hash: []byte("shasum"),
+ Encoded: []byte(encodedCommitFixture),
+ Signature: signatureCommitFixture,
+ },
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Interval = metav1.Duration{Duration: interval}
+ obj.Spec.Verification = &sourcev1.GitRepositoryVerification{
+ Mode: sourcev1.ModeGitHEAD,
+ SecretRef: meta.LocalObjectReference{
+ Name: "explicit-asc",
+ },
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ wantSourceVerificationMode: ptrToVerificationMode(sourcev1.ModeGitHEAD),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- commit 'shasum' with key '5982D0279C227FFD'"),
+ },
+ },
}
for _, tt := range tests {