Refactor internal OCI package
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
		
							parent
							
								
									21af88fbea
								
							
						
					
					
						commit
						082028e115
					
				
							
								
								
									
										8
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										8
									
								
								Makefile
								
								
								
								
							| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
# Image URL to use all building/pushing image targets
 | 
					# Image URL to use all building/pushing image targets
 | 
				
			||||||
IMG ?= fluxcd/source-controller
 | 
					IMG ?= localhost:5050/source-controller
 | 
				
			||||||
TAG ?= latest
 | 
					TAG ?= test1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Base image used to build the Go binary
 | 
					# Base image used to build the Go binary
 | 
				
			||||||
LIBGIT2_IMG ?= ghcr.io/fluxcd/golang-with-libgit2-only
 | 
					LIBGIT2_IMG ?= ghcr.io/fluxcd/golang-with-libgit2-only
 | 
				
			||||||
| 
						 | 
					@ -14,9 +14,9 @@ GO_TEST_PREFIX ?=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Allows for defining additional Docker buildx arguments,
 | 
					# Allows for defining additional Docker buildx arguments,
 | 
				
			||||||
# e.g. '--push'.
 | 
					# e.g. '--push'.
 | 
				
			||||||
BUILD_ARGS ?=
 | 
					BUILD_ARGS ?= --load
 | 
				
			||||||
# Architectures to build images for
 | 
					# Architectures to build images for
 | 
				
			||||||
BUILD_PLATFORMS ?= linux/amd64,linux/arm64,linux/arm/v7
 | 
					BUILD_PLATFORMS ?= linux/arm64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Go additional tag arguments, e.g. 'integration',
 | 
					# Go additional tag arguments, e.g. 'integration',
 | 
				
			||||||
# this is append to the tag arguments required for static builds
 | 
					# this is append to the tag arguments required for static builds
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -311,7 +311,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	options = append(options, crane.WithAuthFromKeychain(keychain))
 | 
						options = append(options, crane.WithAuthFromKeychain(keychain))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, ok := keychain.(util.Anonymous); obj.Spec.Provider != sourcev1.GenericOCIProvider && ok {
 | 
						if _, ok := keychain.(soci.Anonymous); obj.Spec.Provider != sourcev1.GenericOCIProvider && ok {
 | 
				
			||||||
		auth, authErr := oidcAuth(ctxTimeout, obj.Spec.URL, obj.Spec.Provider)
 | 
							auth, authErr := oidcAuth(ctxTimeout, obj.Spec.URL, obj.Spec.Provider)
 | 
				
			||||||
		if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
 | 
							if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
 | 
				
			||||||
			e := serror.NewGeneric(
 | 
								e := serror.NewGeneric(
 | 
				
			||||||
| 
						 | 
					@ -409,22 +409,28 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Extract the content of the first artifact layer
 | 
						// Verify artifact
 | 
				
			||||||
	if !obj.GetArtifact().HasRevision(revision) {
 | 
						if obj.Spec.Verify == nil {
 | 
				
			||||||
		if obj.Spec.Verify != nil {
 | 
							// Remove old observations if verification was disabled
 | 
				
			||||||
 | 
							conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
 | 
				
			||||||
 | 
						} else if !obj.GetArtifact().HasRevision(revision) || conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation {
 | 
				
			||||||
		provider := obj.Spec.Verify.Provider
 | 
							provider := obj.Spec.Verify.Provider
 | 
				
			||||||
		err := r.verifyOCISourceSignature(ctx, obj, url, keychain)
 | 
							err := r.verifyOCISourceSignature(ctx, obj, url, keychain)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			e := serror.NewGeneric(
 | 
								e := serror.NewGeneric(
 | 
				
			||||||
					fmt.Errorf("failed to verify OCI image signature '%s' using provider '%s': %w", url, provider, err),
 | 
									fmt.Errorf("failed to verify the signature using provider '%s': %w", provider, err),
 | 
				
			||||||
				sourcev1.VerificationError,
 | 
									sourcev1.VerificationError,
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
			conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
 | 
								conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
 | 
				
			||||||
 | 
								conditions.MarkFalse(obj, meta.ReconcilingCondition, e.Reason, e.Err.Error())
 | 
				
			||||||
			return sreconcile.ResultEmpty, e
 | 
								return sreconcile.ResultEmpty, e
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "OCI image %s with digest %s verified.", url, revision)
 | 
							conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of digest %s", revision)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Extract the content of the first artifact layer
 | 
				
			||||||
 | 
						if !obj.GetArtifact().HasRevision(revision) {
 | 
				
			||||||
		layers, err := img.Layers()
 | 
							layers, err := img.Layers()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			e := serror.NewGeneric(
 | 
								e := serror.NewGeneric(
 | 
				
			||||||
| 
						 | 
					@ -512,7 +518,6 @@ func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context,
 | 
				
			||||||
	case "cosign":
 | 
						case "cosign":
 | 
				
			||||||
		defaultCosignOciOpts := []soci.Options{
 | 
							defaultCosignOciOpts := []soci.Options{
 | 
				
			||||||
			soci.WithAuthnKeychain(keychain),
 | 
								soci.WithAuthnKeychain(keychain),
 | 
				
			||||||
			soci.WithContext(ctxTimeout),
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ref, err := name.ParseReference(url)
 | 
							ref, err := name.ParseReference(url)
 | 
				
			||||||
| 
						 | 
					@ -536,12 +541,12 @@ func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context,
 | 
				
			||||||
			for k, data := range pubSecret.Data {
 | 
								for k, data := range pubSecret.Data {
 | 
				
			||||||
				// search for public keys in the secret
 | 
									// search for public keys in the secret
 | 
				
			||||||
				if strings.HasSuffix(k, ".pub") {
 | 
									if strings.HasSuffix(k, ".pub") {
 | 
				
			||||||
					verifier, err := soci.New(append(defaultCosignOciOpts, soci.WithPublicKey(data))...)
 | 
										verifier, err := soci.NewVerifier(ctxTimeout, append(defaultCosignOciOpts, soci.WithPublicKey(data))...)
 | 
				
			||||||
					if err != nil {
 | 
										if err != nil {
 | 
				
			||||||
						return err
 | 
											return err
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					signatures, _, err := verifier.VerifyImageSignatures(ref)
 | 
										signatures, _, err := verifier.VerifyImageSignatures(ctxTimeout, ref)
 | 
				
			||||||
					if err != nil {
 | 
										if err != nil {
 | 
				
			||||||
						continue
 | 
											continue
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
| 
						 | 
					@ -562,12 +567,12 @@ func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// if no secret is provided, try keyless verification
 | 
							// if no secret is provided, try keyless verification
 | 
				
			||||||
		ctrl.LoggerFrom(ctx).Info("no secret reference is provided, trying to verify the image using keyless approach")
 | 
							ctrl.LoggerFrom(ctx).Info("no secret reference is provided, trying to verify the image using keyless approach")
 | 
				
			||||||
		verifier, err := soci.New(defaultCosignOciOpts...)
 | 
							verifier, err := soci.NewVerifier(ctxTimeout, defaultCosignOciOpts...)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		signatures, _, err := verifier.VerifyImageSignatures(ref)
 | 
							signatures, _, err := verifier.VerifyImageSignatures(ctxTimeout, ref)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -689,7 +694,7 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// if no pullsecrets available return an AnonymousKeychain
 | 
						// if no pullsecrets available return an AnonymousKeychain
 | 
				
			||||||
	if len(pullSecretNames) == 0 {
 | 
						if len(pullSecretNames) == 0 {
 | 
				
			||||||
		return util.Anonymous{}, nil
 | 
							return soci.Anonymous{}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// lookup image pull secrets
 | 
						// lookup image pull secrets
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1042,22 +1042,22 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
 | 
				
			||||||
			assertConditions: []metav1.Condition{
 | 
								assertConditions: []metav1.Condition{
 | 
				
			||||||
				*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
									*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
				
			||||||
				*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
									*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
				
			||||||
				*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "OCI image <url> with digest <digest> verified."),
 | 
									*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of digest <digest>"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "not signed image should not pass verification",
 | 
								name: "unsigned image should not pass verification",
 | 
				
			||||||
			reference: &sourcev1.OCIRepositoryRef{
 | 
								reference: &sourcev1.OCIRepositoryRef{
 | 
				
			||||||
				Tag: "6.1.5",
 | 
									Tag: "6.1.5",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			digest:     img5.digest.Hex,
 | 
								digest:     img5.digest.Hex,
 | 
				
			||||||
			wantErr:    true,
 | 
								wantErr:    true,
 | 
				
			||||||
			wantErrMsg: "failed to verify OCI image signature '<url>' using provider 'cosign': no matching signatures were found for '<url>",
 | 
								wantErrMsg: "failed to verify the signature using provider 'cosign': no matching signatures were found for '<url>'",
 | 
				
			||||||
			want:       sreconcile.ResultEmpty,
 | 
								want:       sreconcile.ResultEmpty,
 | 
				
			||||||
			assertConditions: []metav1.Condition{
 | 
								assertConditions: []metav1.Condition{
 | 
				
			||||||
				*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
									*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
				
			||||||
				*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
									*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '<digest>' for '<url>'"),
 | 
				
			||||||
				*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "failed to verify OCI image signature '<url>' using provider '<provider>': no matching signatures were found for '<url>'"),
 | 
									*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "failed to verify the signature using provider '<provider>': no matching signatures were found for '<url>'"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -455,7 +455,7 @@ data:
 | 
				
			||||||
  key2.pub: <BASE64>
 | 
					  key2.pub: <BASE64>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that the keys must have the `.pub` extension for Flux to make user of them.
 | 
					Note that the keys must have the `.pub` extension for Flux to make use of them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Keyless verification
 | 
					#### Keyless verification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -482,7 +482,7 @@ The controller verifies the signatures using the Fulcio root CA and the Rekor
 | 
				
			||||||
instance hosted at [rekor.sigstore.dev](https://rekor.sigstore.dev/).
 | 
					instance hosted at [rekor.sigstore.dev](https://rekor.sigstore.dev/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that keyless verification is an **experimental feature**, using
 | 
					Note that keyless verification is an **experimental feature**, using
 | 
				
			||||||
custom root CAs or self-hosted Rekor instances are not currency supported.
 | 
					custom root CAs or self-hosted Rekor instances are not currently supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Suspend
 | 
					### Suspend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -839,6 +839,14 @@ and is only present on the OCIRepository while the status value is `"True"`.
 | 
				
			||||||
There may be more arbitrary values for the `reason` field to provide accurate
 | 
					There may be more arbitrary values for the `reason` field to provide accurate
 | 
				
			||||||
reason for a condition.
 | 
					reason for a condition.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In addition to the above Condition types, when the signature
 | 
				
			||||||
 | 
					[verification](#verification) fails. A condition with
 | 
				
			||||||
 | 
					the following attributes is added to the GitRepository's `.status.conditions`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `type: SourceVerified`
 | 
				
			||||||
 | 
					- `status: "False"`
 | 
				
			||||||
 | 
					- `reason: VerificationError`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
While the OCIRepository has one or more of these Conditions, the controller
 | 
					While the OCIRepository has one or more of these Conditions, the controller
 | 
				
			||||||
will continue to attempt to produce an Artifact for the resource with an
 | 
					will continue to attempt to produce an Artifact for the resource with an
 | 
				
			||||||
exponential backoff, until it succeeds and the OCIRepository is marked as
 | 
					exponential backoff, until it succeeds and the OCIRepository is marked as
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 | 
				
			||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package util
 | 
					package oci
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "github.com/google/go-containerregistry/pkg/authn"
 | 
					import "github.com/google/go-containerregistry/pkg/authn"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,6 @@ import (
 | 
				
			||||||
type options struct {
 | 
					type options struct {
 | 
				
			||||||
	PublicKey []byte
 | 
						PublicKey []byte
 | 
				
			||||||
	Keychain  authn.Keychain
 | 
						Keychain  authn.Keychain
 | 
				
			||||||
	Context   context.Context
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Options is a function that configures the options applied to a Verifier.
 | 
					// Options is a function that configures the options applied to a Verifier.
 | 
				
			||||||
| 
						 | 
					@ -57,20 +56,13 @@ func WithAuthnKeychain(keychain authn.Keychain) Options {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func WithContext(ctx context.Context) Options {
 | 
					 | 
				
			||||||
	return func(opts *options) {
 | 
					 | 
				
			||||||
		opts.Context = ctx
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Verifier is a struct which is responsible for executing verification logic.
 | 
					// Verifier is a struct which is responsible for executing verification logic.
 | 
				
			||||||
type Verifier struct {
 | 
					type Verifier struct {
 | 
				
			||||||
	opts *cosign.CheckOpts
 | 
						opts *cosign.CheckOpts
 | 
				
			||||||
	context context.Context
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// New initializes a new Verifier.
 | 
					// NewVerifier initializes a new Verifier.
 | 
				
			||||||
func New(opts ...Options) (*Verifier, error) {
 | 
					func NewVerifier(ctx context.Context, opts ...Options) (*Verifier, error) {
 | 
				
			||||||
	o := options{}
 | 
						o := options{}
 | 
				
			||||||
	for _, opt := range opts {
 | 
						for _, opt := range opts {
 | 
				
			||||||
		opt(&o)
 | 
							opt(&o)
 | 
				
			||||||
| 
						 | 
					@ -79,7 +71,7 @@ func New(opts ...Options) (*Verifier, error) {
 | 
				
			||||||
	checkOpts := &cosign.CheckOpts{}
 | 
						checkOpts := &cosign.CheckOpts{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ro := coptions.RegistryOptions{}
 | 
						ro := coptions.RegistryOptions{}
 | 
				
			||||||
	co, err := ro.ClientOpts(o.Context)
 | 
						co, err := ro.ClientOpts(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -125,11 +117,10 @@ func New(opts ...Options) (*Verifier, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Verifier{
 | 
						return &Verifier{
 | 
				
			||||||
		opts: checkOpts,
 | 
							opts: checkOpts,
 | 
				
			||||||
		context: o.Context,
 | 
					 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// VerifyImageSignatures verify the authenticity of the given ref OCI image.
 | 
					// VerifyImageSignatures verify the authenticity of the given ref OCI image.
 | 
				
			||||||
func (v *Verifier) VerifyImageSignatures(ref name.Reference) ([]oci.Signature, bool, error) {
 | 
					func (v *Verifier) VerifyImageSignatures(ctx context.Context, ref name.Reference) ([]oci.Signature, bool, error) {
 | 
				
			||||||
	return cosign.VerifyImageSignatures(v.context, ref, v.opts)
 | 
						return cosign.VerifyImageSignatures(ctx, ref, v.opts)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue