Skip to content

Commit

Permalink
Introduce Initial OCIRepository Source Verification
Browse files Browse the repository at this point in the history
Fixes #863

Signed-off-by: Furkan <furkan.turkal@trendyol.com>
Co-authored-by: Batuhan <batuhan.apaydin@trendyol.com>
Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>
  • Loading branch information
Dentrax and developer-guy committed Aug 27, 2022
1 parent 430f507 commit 0577461
Show file tree
Hide file tree
Showing 10 changed files with 1,148 additions and 73 deletions.
1 change: 0 additions & 1 deletion api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions api/v1beta2/condition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ const (
// required fields, or the provided credentials do not match.
AuthenticationFailedReason string = "AuthenticationFailed"

// SourceVerifiedFailedReason signals that the Source's verification
// check failed.
SourceVerifiedFailedReason string = "SourceVerificationFailed"

// DirCreationFailedReason signals a failure caused by a directory creation
// operation.
DirCreationFailedReason string = "DirectoryCreationFailed"
Expand Down
7 changes: 7 additions & 0 deletions api/v1beta2/ocirepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ type OCIRepositorySpec struct {
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`

// Verify specifies which provider to use to check whether OCI image is authentic.
// Implements RFC-0003.
// +kubebuilder:validation:Enum=cosign
// +kubebuilder:default:=cosign
// +optional
Verify *OCIRepositoryVerification `json:"verify,omitempty"`

// ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate
// the image pull if the service account has attached pull secrets. For more information:
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account
Expand Down
6 changes: 5 additions & 1 deletion api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,33 @@ spec:
on a remote container registry.
pattern: ^oci://.*$
type: string
verify:
default: cosign
description: Verify specifies which provider to use to check whether
OCI image is authentic. Implements RFC-0003.
enum:
- cosign
properties:
provider:
description: Provider specifies the technology used to sign the
OCI Artifact.
enum:
- cosign
type: string
secretRef:
description: SecretRef specifies the Kubernetes Secret containing
the trusted public keys.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
required:
- provider
- secretRef
type: object
required:
- interval
- url
Expand Down
78 changes: 73 additions & 5 deletions controllers/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"net/http"
Expand All @@ -28,6 +29,8 @@ import (
"strings"
"time"

"github.com/fluxcd/source-controller/internal/verify"

"github.com/Masterminds/semver/v3"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/k8schain"
Expand Down Expand Up @@ -362,6 +365,18 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultEmpty, e
}

// Verify the image
if obj.Spec.Verify != nil {
if _, err := r.verify(ctx, obj, url); err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to verify '%s' using provider '%s': %w", url, obj.Spec.Verify.Provider, err),
sourcev1.SourceVerifiedFailedReason,
)
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
}

// Pull artifact from the remote container registry
img, err := crane.Pull(url, options...)
if err != nil {
Expand Down Expand Up @@ -658,7 +673,6 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
tlsConfig.RootCAs = syscerts
}
return transport, nil

}

// oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
Expand Down Expand Up @@ -705,7 +719,8 @@ func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Opti
// The hostname of any URL in the Status of the object are updated, to ensure
// they match the Storage server hostname of current runtime.
func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context,
obj *sourcev1.OCIRepository, _ *sourcev1.Artifact, _ string) (sreconcile.Result, error) {
obj *sourcev1.OCIRepository, _ *sourcev1.Artifact, _ string,
) (sreconcile.Result, error) {
// Garbage collect previous advertised artifact(s) from storage
_ = r.garbageCollect(ctx, obj)

Expand Down Expand Up @@ -741,7 +756,8 @@ func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context,
// On a successful archive, the Artifact in the Status of the object is set,
// and the symlink in the Storage is updated to its path.
func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context,
obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) {
obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string,
) (sreconcile.Result, error) {
// Calculate revision
revision := metadata.Revision

Expand Down Expand Up @@ -885,7 +901,8 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc
// that this is a simple log. While the debug log contains complete details
// about the event.
func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,
obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
obj runtime.Object, eventType, reason, messageFmt string, args ...interface{},
) {
msg := fmt.Sprintf(messageFmt, args...)
// Log and emit event.
if eventType == corev1.EventTypeWarning {
Expand All @@ -898,7 +915,8 @@ func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,

// notify emits notification related to the reconciliation.
func (r *OCIRepositoryReconciler) notify(ctx context.Context,
oldObj, newObj *sourcev1.OCIRepository, res sreconcile.Result, resErr error) {
oldObj, newObj *sourcev1.OCIRepository, res sreconcile.Result, resErr error,
) {
// Notify successful reconciliation for new artifact and recovery from any
// failure.
if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
Expand Down Expand Up @@ -942,3 +960,53 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context,
}
}
}

// notify emits notification related to the reconciliation.
func (r *OCIRepositoryReconciler) verify(ctx context.Context, obj *sourcev1.OCIRepository, url string) (bool, error) {
// get the public keys from the given secret
certSecretName := types.NamespacedName{
Namespace: obj.Namespace,
Name: obj.Spec.Verify.SecretRef.Name,
}

var pubSecret corev1.Secret
if err := r.Get(ctx, certSecretName, &pubSecret); err != nil {
return false, err
}

ref, err := name.ParseReference(url)
if err != nil {
return false, err
}

// traverse all public keys and try to verify the signature
// this is brute-force approach, but it is ok for now
verified := false
for _, data := range pubSecret.Data {
pubRaw, err := base64.StdEncoding.DecodeString(string(data))
if err != nil {
return false, err
}

opts := []verify.Options{
verify.WithPublicKey(pubRaw),
}

verifier, err := verify.New(obj.Spec.Verify.Provider, opts...)
if err != nil {
return false, err
}

signatures, _, err := verifier.VerifyImageSignatures(ctx, ref)
if err != nil {
return false, err
}

if len(signatures) > 0 {
verified = true
break
}
}

return verified, nil
}
Loading

0 comments on commit 0577461

Please sign in to comment.