gitrepo: add support for verifying tags
Add support for verifying tags and optionally the commit object it points to. Modify the reconciler to trigger a full reconciliation if the object contains a verification configuration that implies that we need to verify one (or more) Git objects that we haven't previosuly verified. Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
parent
6002ef51a6
commit
59898cd86b
8
go.mod
8
go.mod
|
@ -27,16 +27,16 @@ require (
|
|||
github.com/docker/go-units v0.5.0
|
||||
github.com/fluxcd/pkg/apis/event v0.5.2
|
||||
github.com/fluxcd/pkg/apis/meta v1.1.2
|
||||
github.com/fluxcd/pkg/git v0.12.4
|
||||
github.com/fluxcd/pkg/git/gogit v0.12.1
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.5
|
||||
github.com/fluxcd/pkg/git v0.13.0
|
||||
github.com/fluxcd/pkg/git/gogit v0.13.0
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.6
|
||||
github.com/fluxcd/pkg/helmtestserver v0.13.2
|
||||
github.com/fluxcd/pkg/lockedfile v0.1.0
|
||||
github.com/fluxcd/pkg/masktoken v0.2.0
|
||||
github.com/fluxcd/pkg/oci v0.30.1
|
||||
github.com/fluxcd/pkg/runtime v0.42.0
|
||||
github.com/fluxcd/pkg/sourceignore v0.3.5
|
||||
github.com/fluxcd/pkg/ssh v0.8.1
|
||||
github.com/fluxcd/pkg/ssh v0.8.2
|
||||
github.com/fluxcd/pkg/tar v0.2.0
|
||||
github.com/fluxcd/pkg/testserver v0.4.0
|
||||
github.com/fluxcd/pkg/version v0.2.2
|
||||
|
|
18
go.sum
18
go.sum
|
@ -354,7 +354,7 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNE
|
|||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||
github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE=
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE=
|
||||
github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
|
@ -393,12 +393,12 @@ github.com/fluxcd/pkg/apis/event v0.5.2 h1:WtnCOeWglf7wR3dpyiWxb1JtYkw1G5OXcERb1
|
|||
github.com/fluxcd/pkg/apis/event v0.5.2/go.mod h1:5l6SSxVTkqrXrYjgEqAajOOHkl4x0TPocAuSdu+3AEs=
|
||||
github.com/fluxcd/pkg/apis/meta v1.1.2 h1:Unjo7hxadtB2dvGpeFqZZUdsjpRA08YYSBb7dF2WIAM=
|
||||
github.com/fluxcd/pkg/apis/meta v1.1.2/go.mod h1:BHQyRHCskGMEDf6kDGbgQ+cyiNpUHbLsCOsaMYM2maI=
|
||||
github.com/fluxcd/pkg/git v0.12.4 h1:COuVYUL+gqMOYAm6oD32Vwcmy/8WVsT/nMk8ps0lpJI=
|
||||
github.com/fluxcd/pkg/git v0.12.4/go.mod h1:rKB1puk7sbC4AYF1oZDBrkvu3cr0aibkd4I5yNbxSQg=
|
||||
github.com/fluxcd/pkg/git/gogit v0.12.1 h1:06jzHOTntYN5xCSQvyFXtLXdqoP8crLh7VYgtXS9+wo=
|
||||
github.com/fluxcd/pkg/git/gogit v0.12.1/go.mod h1:Z4Ysp8VifKTvWpjJMKncJsgb2iBqHuIeK80VGjlU41Y=
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.5 h1:EGqDF4240xPRgW1FFrQAs0Du7fZb8OGXC5qKDIqyXD8=
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.5/go.mod h1:SyGEh+OBzFpdlTWWqv3XBkiLB42Iu+mijfIQ4hPlEZQ=
|
||||
github.com/fluxcd/pkg/git v0.13.0 h1:GcJfldYqw6ELf0vbTCV+iFZgSpK6HZBKx3yAvn1Dqfg=
|
||||
github.com/fluxcd/pkg/git v0.13.0/go.mod h1:rKB1puk7sbC4AYF1oZDBrkvu3cr0aibkd4I5yNbxSQg=
|
||||
github.com/fluxcd/pkg/git/gogit v0.13.0 h1:XCwfiB5qbz08djUgo0TII09zibH97Hn56v098pkFpns=
|
||||
github.com/fluxcd/pkg/git/gogit v0.13.0/go.mod h1:V3g+UyIDSAOysg5KCpHhS+HXBUmNmmbNlVruWkpCJgY=
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.6 h1:YM8prVKB3LC9LBBe+a2p7l1BlfV9erXCgC1em9sbqW4=
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.6/go.mod h1:3abUQFRNlfBhn+BD+TI2lfXI/JkdntdQ99spSnItFk4=
|
||||
github.com/fluxcd/pkg/helmtestserver v0.13.2 h1:Wypmc8kr9UrUwB32v2InK8oRDb9tGaixATAXqaZFurI=
|
||||
github.com/fluxcd/pkg/helmtestserver v0.13.2/go.mod h1:Em5iCJ0FU7TgSS1jfOy2rwc0NnsFgz9BHB4QOo186wM=
|
||||
github.com/fluxcd/pkg/lockedfile v0.1.0 h1:YsYFAkd6wawMCcD74ikadAKXA4s2sukdxrn7w8RB5eo=
|
||||
|
@ -411,8 +411,8 @@ github.com/fluxcd/pkg/runtime v0.42.0 h1:a5DQ/f90YjoHBmiXZUpnp4bDSLORjInbmqP7K11
|
|||
github.com/fluxcd/pkg/runtime v0.42.0/go.mod h1:p6A3xWVV8cKLLQW0N90GehKgGMMmbNYv+OSJ/0qB0vg=
|
||||
github.com/fluxcd/pkg/sourceignore v0.3.5 h1:omcHTH5X5tlPr9w1b9T7WuJTOP+o/KdVdarYb4kgkCU=
|
||||
github.com/fluxcd/pkg/sourceignore v0.3.5/go.mod h1:6Xz3jErz8RsidsdrjUBBUGKes24rbdp/F38MnTGibEw=
|
||||
github.com/fluxcd/pkg/ssh v0.8.1 h1:v35y7Ks/+ABWce8RcnrC7psVIhf3EdCUNFJi5+tYOps=
|
||||
github.com/fluxcd/pkg/ssh v0.8.1/go.mod h1:M1ouDXuDG+QuhGB4JYEjCNCykNytLJGDhwKn9y4DEOE=
|
||||
github.com/fluxcd/pkg/ssh v0.8.2 h1:WNfvTmnLnOUyXQDb8luSfmn1X0RIuhJBcKMFtKm6YsQ=
|
||||
github.com/fluxcd/pkg/ssh v0.8.2/go.mod h1:ewbU9vakYYdGSX92qXhx6Kqi5tVQ3ppmGQakCX1R6Gw=
|
||||
github.com/fluxcd/pkg/tar v0.2.0 h1:HEUHgONQYsJGeZZ4x6h5nQU9Aox1I4T3bOp1faWTqf8=
|
||||
github.com/fluxcd/pkg/tar v0.2.0/go.mod h1:w0/TOC7kwBJhnSJn7TCABkc/I7ib1f2Yz6vOsbLBnhw=
|
||||
github.com/fluxcd/pkg/testserver v0.4.0 h1:pDZ3gistqYhwlf3sAjn1Q8NzN4Qe6I1BEmHMHi46lMg=
|
||||
|
|
|
@ -587,7 +587,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
|
|||
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
||||
|
||||
// Verify commit signature
|
||||
if result, err := r.verifyCommitSignature(ctx, obj, *commit); err != nil || result == sreconcile.ResultEmpty {
|
||||
if result, err := r.verifySignature(ctx, obj, *commit); err != nil || result == sreconcile.ResultEmpty {
|
||||
return result, err
|
||||
}
|
||||
|
||||
|
@ -924,17 +924,18 @@ func (r *GitRepositoryReconciler) fetchIncludes(ctx context.Context, obj *source
|
|||
return &artifacts, nil
|
||||
}
|
||||
|
||||
// verifyCommitSignature verifies the signature of the given Git commit, if a
|
||||
// verification mode is specified on the object.
|
||||
// verifySignature verifies the signature of the given Git commit and/or its referencing tag
|
||||
// depending on the verification mode specified on the object.
|
||||
// If the signature can not be verified or the verification fails, it records
|
||||
// v1beta2.SourceVerifiedCondition=False and returns.
|
||||
// When successful, it records v1beta2.SourceVerifiedCondition=True.
|
||||
// If no verification mode is specified on the object, the
|
||||
// v1beta2.SourceVerifiedCondition Condition is removed.
|
||||
func (r *GitRepositoryReconciler) verifyCommitSignature(ctx context.Context, obj *sourcev1.GitRepository, commit git.Commit) (sreconcile.Result, error) {
|
||||
func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sourcev1.GitRepository, commit git.Commit) (sreconcile.Result, error) {
|
||||
// Check if there is a commit verification is configured and remove any old
|
||||
// observations if there is none
|
||||
if obj.Spec.Verification == nil || obj.Spec.Verification.Mode == "" {
|
||||
obj.Status.SourceVerificationMode = nil
|
||||
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
|
||||
return sreconcile.ResultSuccess, nil
|
||||
}
|
||||
|
@ -958,8 +959,52 @@ func (r *GitRepositoryReconciler) verifyCommitSignature(ctx context.Context, obj
|
|||
for _, v := range secret.Data {
|
||||
keyRings = append(keyRings, string(v))
|
||||
}
|
||||
|
||||
var message strings.Builder
|
||||
if obj.Spec.Verification.VerifyTag() {
|
||||
// If we need to verify a tag object, then the commit must have a tag
|
||||
// that points to it. If it does not, then its safe to asssume that
|
||||
// the checkout didn't happen via a tag reference, thus the object can
|
||||
// be marked as stalled.
|
||||
tag := commit.ReferencingTag
|
||||
if tag == nil {
|
||||
err := serror.NewStalling(
|
||||
errors.New("cannot verify tag object's signature if a tag reference is not specified"),
|
||||
"InvalidVerificationMode",
|
||||
)
|
||||
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, err.Reason, err.Err.Error())
|
||||
return sreconcile.ResultEmpty, err
|
||||
}
|
||||
if !git.IsSignedTag(*tag) {
|
||||
// If the tag was not signed then we can't verify its signature
|
||||
// but since the upstream tag object can change at any time, we can't
|
||||
// mark the object as stalled.
|
||||
err := serror.NewGeneric(
|
||||
fmt.Errorf("cannot verify signature of tag '%s' since it is not signed", commit.ReferencingTag.String()),
|
||||
"InvalidGitObject",
|
||||
)
|
||||
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, err.Reason, err.Err.Error())
|
||||
return sreconcile.ResultEmpty, err
|
||||
}
|
||||
|
||||
// Verify tag with GPG data from secret
|
||||
tagEntity, err := tag.Verify(keyRings...)
|
||||
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, e.Err.Error())
|
||||
// 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))
|
||||
}
|
||||
|
||||
if obj.Spec.Verification.VerifyHEAD() {
|
||||
// Verify commit with GPG data from secret
|
||||
entity, err := commit.Verify(keyRings...)
|
||||
headEntity, err := commit.Verify(keyRings...)
|
||||
if err != nil {
|
||||
e := serror.NewGeneric(
|
||||
fmt.Errorf("signature verification of commit '%s' failed: %w", commit.Hash.String(), err),
|
||||
|
@ -969,11 +1014,19 @@ func (r *GitRepositoryReconciler) verifyCommitSignature(ctx context.Context, obj
|
|||
// 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))
|
||||
} else {
|
||||
message.WriteString(fmt.Sprintf("verified signature of\n\t- commit '%s' with key '%s'", commit.Hash.String(), headEntity))
|
||||
}
|
||||
}
|
||||
|
||||
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason,
|
||||
"verified signature of commit '%s' with key '%s'", commit.Hash.String(), entity)
|
||||
r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "VerifiedCommit",
|
||||
"verified signature of commit '%s' with key '%s'", commit.Hash.String(), entity)
|
||||
reason := meta.SucceededReason
|
||||
mode := obj.Spec.Verification.GetMode()
|
||||
obj.Status.SourceVerificationMode = &mode
|
||||
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, reason, message.String())
|
||||
r.eventLogf(ctx, obj, eventv1.EventTypeTrace, reason, message.String())
|
||||
return sreconcile.ResultSuccess, nil
|
||||
}
|
||||
|
||||
|
@ -1048,7 +1101,8 @@ func (r *GitRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Obj
|
|||
|
||||
// gitContentConfigChanged evaluates the current spec with the observations of
|
||||
// the artifact in the status to determine if artifact content configuration has
|
||||
// changed and requires rebuilding the artifact.
|
||||
// changed and requires rebuilding the artifact. Rebuilding the artifact is also
|
||||
// required if the object needs to be (re)verified.
|
||||
func gitContentConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet) bool {
|
||||
if !pointer.StringEqual(obj.Spec.Ignore, obj.Status.ObservedIgnore) {
|
||||
return true
|
||||
|
@ -1059,6 +1113,9 @@ func gitContentConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet)
|
|||
if len(obj.Spec.Include) != len(obj.Status.ObservedInclude) {
|
||||
return true
|
||||
}
|
||||
if requiresVerification(obj) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Convert artifactSet to index addressable artifacts and ensure that it and
|
||||
// the included artifacts include all the include from the spec.
|
||||
|
@ -1113,3 +1170,28 @@ func commitReference(obj *sourcev1.GitRepository, commit *git.Commit) string {
|
|||
}
|
||||
return commit.String()
|
||||
}
|
||||
|
||||
// requiresVerification inspects a GitRepository's verification spec and its status
|
||||
// to determine whether the Git repository needs to be verified again. It does so by
|
||||
// first checking if the GitRepository has a verification spec. If it does, then
|
||||
// it returns true based on the following three conditions:
|
||||
//
|
||||
// - If the object does not have a observed verification mode in its status.
|
||||
// - If the observed verification mode indicates that only the tag had been
|
||||
// verified earlier and the HEAD also needs to be verified now.
|
||||
// - If the observed verification mode indicates that only the HEAD had been
|
||||
// verified earlier and the tag also needs to be verified now.
|
||||
func requiresVerification(obj *sourcev1.GitRepository) bool {
|
||||
if obj.Spec.Verification != nil {
|
||||
observedMode := obj.Status.SourceVerificationMode
|
||||
mode := obj.Spec.Verification.GetMode()
|
||||
if observedMode == nil {
|
||||
return true
|
||||
}
|
||||
if (*observedMode == sourcev1.ModeGitTag && (mode == sourcev1.ModeGitHEAD || mode == sourcev1.ModeGitTagAndHEAD)) ||
|
||||
(*observedMode == sourcev1.ModeGitHEAD && (mode == sourcev1.ModeGitTag || mode == sourcev1.ModeGitTagAndHEAD)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1515,7 +1515,7 @@ func TestGitRepositoryReconciler_reconcileDelete(t *testing.T) {
|
|||
g.Expect(obj.Status.Artifact).To(BeNil())
|
||||
}
|
||||
|
||||
func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
|
||||
func TestGitRepositoryReconciler_verifySignature(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
secret *corev1.Secret
|
||||
|
@ -1551,7 +1551,7 @@ func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
|
|||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of commit 'shasum' with key '3299AEB0E4085BAF'"),
|
||||
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of\n\t- commit 'shasum' with key '3299AEB0E4085BAF'"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1577,7 +1577,7 @@ func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "InvalidCommitSignature", "signature verification of commit 'shasum' failed: unable to verify commit with any of the given key rings"),
|
||||
*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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1648,7 +1648,7 @@ func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
|
|||
tt.beforeFunc(obj)
|
||||
}
|
||||
|
||||
got, err := r.verifyCommitSignature(context.TODO(), obj, tt.commit)
|
||||
got, err := r.verifySignature(context.TODO(), obj, tt.commit)
|
||||
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
|
||||
g.Expect(err != nil).To(Equal(tt.wantErr))
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
|
|
Loading…
Reference in New Issue