Merge pull request #23 from fluxcd/signature-verification
gitrepository: Implement PGP signature verification
This commit is contained in:
commit
1e3bc471d5
|
@ -65,4 +65,8 @@ const (
|
|||
// AuthenticationFailedReason represents the fact that a given secret does not
|
||||
// have the required fields or the provided credentials do not match.
|
||||
AuthenticationFailedReason string = "AuthenticationFailed"
|
||||
|
||||
// VerificationFailedReason represents the fact that the cryptographic provenance
|
||||
// verification for the source failed.
|
||||
VerificationFailedReason string = "VerificationFailed"
|
||||
)
|
||||
|
|
|
@ -44,6 +44,10 @@ type GitRepositorySpec struct {
|
|||
// master branch.
|
||||
// +optional
|
||||
Reference *GitRepositoryRef `json:"ref,omitempty"`
|
||||
|
||||
// Verify OpenPGP signature for the commit that HEAD points to.
|
||||
// +optional
|
||||
Verification *GitRepositoryVerification `json:"verify,omitempty"`
|
||||
}
|
||||
|
||||
// GitRepositoryRef defines the git ref used for pull and checkout operations.
|
||||
|
@ -66,7 +70,17 @@ type GitRepositoryRef struct {
|
|||
Commit string `json:"commit"`
|
||||
}
|
||||
|
||||
// GitRepositoryStatus defines the observed state of the GitRepository.
|
||||
// GitRepositoryVerification defines the OpenPGP signature verification process.
|
||||
type GitRepositoryVerification struct {
|
||||
// Mode describes what git object should be verified, currently ('head').
|
||||
// +kubebuilder:validation:Enum=head
|
||||
Mode string `json:"mode"`
|
||||
|
||||
// The secret name containing the public keys of all trusted git authors.
|
||||
SecretRef corev1.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
}
|
||||
|
||||
// GitRepositoryStatus defines the observed state of a Git repository.
|
||||
type GitRepositoryStatus struct {
|
||||
// +optional
|
||||
Conditions []SourceCondition `json:"conditions,omitempty"`
|
||||
|
|
|
@ -129,6 +129,11 @@ func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) {
|
|||
*out = new(GitRepositoryRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.Verification != nil {
|
||||
in, out := &in.Verification, &out.Verification
|
||||
*out = new(GitRepositoryVerification)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec.
|
||||
|
@ -168,6 +173,22 @@ func (in *GitRepositoryStatus) DeepCopy() *GitRepositoryStatus {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitRepositoryVerification) DeepCopyInto(out *GitRepositoryVerification) {
|
||||
*out = *in
|
||||
out.SecretRef = in.SecretRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryVerification.
|
||||
func (in *GitRepositoryVerification) DeepCopy() *GitRepositoryVerification {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GitRepositoryVerification)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HelmChart) DeepCopyInto(out *HelmChart) {
|
||||
*out = *in
|
||||
|
|
|
@ -86,12 +86,34 @@ spec:
|
|||
description: The repository URL, can be a HTTP or SSH address.
|
||||
pattern: ^(http|https|ssh)://
|
||||
type: string
|
||||
verify:
|
||||
description: Verify OpenPGP signature for the commit that HEAD points
|
||||
to.
|
||||
properties:
|
||||
mode:
|
||||
description: Mode describes what git object should be verified,
|
||||
currently ('head').
|
||||
enum:
|
||||
- head
|
||||
type: string
|
||||
secretRef:
|
||||
description: The secret name containing the public keys of all trusted
|
||||
git authors.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- mode
|
||||
type: object
|
||||
required:
|
||||
- interval
|
||||
- url
|
||||
type: object
|
||||
status:
|
||||
description: GitRepositoryStatus defines the observed state of the GitRepository.
|
||||
description: GitRepositoryStatus defines the observed state of a Git repository.
|
||||
properties:
|
||||
artifact:
|
||||
description: Artifact represents the output of the last successful repository
|
||||
|
|
|
@ -36,7 +36,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||
internalgit "github.com/fluxcd/source-controller/internal/git"
|
||||
intgit "github.com/fluxcd/source-controller/internal/git"
|
||||
)
|
||||
|
||||
// GitRepositoryReconciler reconciles a GitRepository object
|
||||
|
@ -76,10 +76,11 @@ func (r *GitRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
|
|||
log.Error(err, "artifacts GC failed")
|
||||
}
|
||||
|
||||
// try git clone
|
||||
// try git sync
|
||||
syncedRepo, err := r.sync(ctx, *repo.DeepCopy())
|
||||
if err != nil {
|
||||
log.Error(err, "Git repository sync failed")
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// update status
|
||||
|
@ -128,6 +129,7 @@ func (r *GitRepositoryReconciler) sync(ctx context.Context, repository sourcev1.
|
|||
}
|
||||
}
|
||||
|
||||
// determine auth method
|
||||
var auth transport.AuthMethod
|
||||
if repository.Spec.SecretRef != nil {
|
||||
name := types.NamespacedName{
|
||||
|
@ -142,7 +144,7 @@ func (r *GitRepositoryReconciler) sync(ctx context.Context, repository sourcev1.
|
|||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||
}
|
||||
|
||||
method, cleanup, err := internalgit.AuthMethodFromSecret(repository.Spec.URL, secret)
|
||||
method, cleanup, err := intgit.AuthMethodFromSecret(repository.Spec.URL, secret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth error: %w", err)
|
||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||
|
@ -259,6 +261,45 @@ func (r *GitRepositoryReconciler) sync(ctx context.Context, repository sourcev1.
|
|||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||
}
|
||||
|
||||
// verify PGP signature
|
||||
if repository.Spec.Verification != nil {
|
||||
commit, err := repo.CommitObject(ref.Hash())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("git resolve HEAD error: %w", err)
|
||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||
}
|
||||
|
||||
if commit.PGPSignature == "" {
|
||||
err = fmt.Errorf("PGP signature not found for commit '%s'", ref.Hash())
|
||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
|
||||
}
|
||||
|
||||
name := types.NamespacedName{
|
||||
Namespace: repository.GetNamespace(),
|
||||
Name: repository.Spec.Verification.SecretRef.Name,
|
||||
}
|
||||
|
||||
var secret corev1.Secret
|
||||
err = r.Client.Get(ctx, name, &secret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("PGP public keys secret error: %w", err)
|
||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
|
||||
}
|
||||
|
||||
var verified bool
|
||||
for _, bytes := range secret.Data {
|
||||
if _, err := commit.Verify(string(bytes)); err == nil {
|
||||
verified = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !verified {
|
||||
err = fmt.Errorf("PGP signature of '%s' can't be verified", commit.Author)
|
||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
|
||||
}
|
||||
}
|
||||
|
||||
if revision == "" {
|
||||
revision = fmt.Sprintf("%s/%s", branch, ref.Hash().String())
|
||||
}
|
||||
|
@ -307,7 +348,6 @@ func (r *GitRepositoryReconciler) shouldResetStatus(repository sourcev1.GitRepos
|
|||
}
|
||||
}
|
||||
|
||||
// set initial status
|
||||
if len(repository.Status.Conditions) == 0 || resetStatus {
|
||||
resetStatus = true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue