Skip to content

Commit

Permalink
Refactor internal OCI package
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
  • Loading branch information
stefanprodan committed Sep 20, 2022
1 parent 21af88f commit 3a2c636
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 33 deletions.
23 changes: 13 additions & 10 deletions controllers/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
}
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)
if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
e := serror.NewGeneric(
Expand Down Expand Up @@ -410,21 +410,25 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
}()

// Extract the content of the first artifact layer
if !obj.GetArtifact().HasRevision(revision) {
if !obj.GetArtifact().HasRevision(revision) || obj.Status.ObservedGeneration != obj.Generation {
if obj.Spec.Verify != nil {
provider := obj.Spec.Verify.Provider
err := r.verifyOCISourceSignature(ctx, obj, url, keychain)
if err != nil {
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,
)
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
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)
} else {
// Remove old observations if verification was disabled
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
}

layers, err := img.Layers()
if err != nil {
e := serror.NewGeneric(
Expand Down Expand Up @@ -512,7 +516,6 @@ func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context,
case "cosign":
defaultCosignOciOpts := []soci.Options{
soci.WithAuthnKeychain(keychain),
soci.WithContext(ctxTimeout),
}

ref, err := name.ParseReference(url)
Expand All @@ -536,12 +539,12 @@ func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context,
for k, data := range pubSecret.Data {
// search for public keys in the secret
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 {
return err
}

signatures, _, err := verifier.VerifyImageSignatures(ref)
signatures, _, err := verifier.VerifyImageSignatures(ctxTimeout, ref)
if err != nil {
continue
}
Expand All @@ -562,12 +565,12 @@ func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context,

// 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")
verifier, err := soci.New(defaultCosignOciOpts...)
verifier, err := soci.NewVerifier(ctxTimeout, defaultCosignOciOpts...)
if err != nil {
return err
}

signatures, _, err := verifier.VerifyImageSignatures(ref)
signatures, _, err := verifier.VerifyImageSignatures(ctxTimeout, ref)
if err != nil {
return err
}
Expand Down Expand Up @@ -689,7 +692,7 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC

// if no pullsecrets available return an AnonymousKeychain
if len(pullSecretNames) == 0 {
return util.Anonymous{}, nil
return soci.Anonymous{}, nil
}

// lookup image pull secrets
Expand Down
8 changes: 4 additions & 4 deletions controllers/ocirepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1042,22 +1042,22 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, "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{
Tag: "6.1.5",
},
digest: img5.digest.Hex,
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,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, "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>'"),
},
},
}
Expand Down
12 changes: 10 additions & 2 deletions docs/spec/v1beta2/ocirepositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ data:
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

Expand All @@ -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/).

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

Expand Down Expand Up @@ -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
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
will continue to attempt to produce an Artifact for the resource with an
exponential backoff, until it succeeds and the OCIRepository is marked as
Expand Down
2 changes: 1 addition & 1 deletion internal/util/auth.go → internal/oci/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package util
package oci

import "github.com/google/go-containerregistry/pkg/authn"

Expand Down
23 changes: 7 additions & 16 deletions internal/oci/oci.go → internal/oci/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import (
type options struct {
PublicKey []byte
Keychain authn.Keychain
Context context.Context
}

// Options is a function that configures the options applied to a Verifier.
Expand All @@ -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.
type Verifier struct {
opts *cosign.CheckOpts
context context.Context
opts *cosign.CheckOpts
}

// New initializes a new Verifier.
func New(opts ...Options) (*Verifier, error) {
// NewVerifier initializes a new Verifier.
func NewVerifier(ctx context.Context, opts ...Options) (*Verifier, error) {
o := options{}
for _, opt := range opts {
opt(&o)
Expand All @@ -79,7 +71,7 @@ func New(opts ...Options) (*Verifier, error) {
checkOpts := &cosign.CheckOpts{}

ro := coptions.RegistryOptions{}
co, err := ro.ClientOpts(o.Context)
co, err := ro.ClientOpts(ctx)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -124,12 +116,11 @@ func New(opts ...Options) (*Verifier, error) {
}

return &Verifier{
opts: checkOpts,
context: o.Context,
opts: checkOpts,
}, nil
}

// VerifyImageSignatures verify the authenticity of the given ref OCI image.
func (v *Verifier) VerifyImageSignatures(ref name.Reference) ([]oci.Signature, bool, error) {
return cosign.VerifyImageSignatures(v.context, ref, v.opts)
func (v *Verifier) VerifyImageSignatures(ctx context.Context, ref name.Reference) ([]oci.Signature, bool, error) {
return cosign.VerifyImageSignatures(ctx, ref, v.opts)
}

0 comments on commit 3a2c636

Please sign in to comment.