Fix verification condition

Delete a failed verification condition at the beginning of the source
reconciliation and set `SourceVerifiedCondition` to false approprietly.

Set the `BuildOptions.Verify` to true as long as Verify is enabled in the
API fields.

Signed-off-by: Soule BA <soule@weave.works>
This commit is contained in:
Soule BA 2022-10-20 13:48:50 +02:00
parent 25673ac512
commit 06a55590a5
No known key found for this signature in database
GPG Key ID: 4D40965192802994
9 changed files with 36 additions and 31 deletions

View File

@ -90,7 +90,7 @@ type HelmChartSpec struct {
// Verify contains the secret name containing the trusted public keys // Verify contains the secret name containing the trusted public keys
// used to verify the signature and specifies which provider to use to check // used to verify the signature and specifies which provider to use to check
// whether OCI image is authentic. // whether OCI image is authentic.
// This field is only supported for OCI sources. // This field is only supported when using HelmRepository source with spec.type 'oci'.
// Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified. // Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.
// +optional // +optional
Verify *OCIRepositoryVerification `json:"verify,omitempty"` Verify *OCIRepositoryVerification `json:"verify,omitempty"`

View File

@ -407,8 +407,9 @@ spec:
description: Verify contains the secret name containing the trusted description: Verify contains the secret name containing the trusted
public keys used to verify the signature and specifies which provider public keys used to verify the signature and specifies which provider
to use to check whether OCI image is authentic. This field is only to use to check whether OCI image is authentic. This field is only
supported for OCI sources. Chart dependencies, which are not bundled supported when using HelmRepository source with spec.type 'oci'.
in the umbrella chart artifact, are not verified. Chart dependencies, which are not bundled in the umbrella chart
artifact, are not verified.
properties: properties:
provider: provider:
default: cosign default: cosign

View File

@ -372,6 +372,12 @@ func (r *HelmChartReconciler) reconcileStorage(ctx context.Context, obj *sourcev
} }
func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1.HelmChart, build *chart.Build) (_ sreconcile.Result, retErr error) { func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1.HelmChart, build *chart.Build) (_ sreconcile.Result, retErr error) {
// Remove any failed verification condition.
// The reason is that a failing verification should be recalculated.
if conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
}
// Retrieve the source // Retrieve the source
s, err := r.getSource(ctx, obj) s, err := r.getSource(ctx, obj)
if err != nil { if err != nil {
@ -577,10 +583,10 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
if obj.Spec.Verify.SecretRef == nil { if obj.Spec.Verify.SecretRef == nil {
provider = fmt.Sprintf("%s keyless", provider) provider = fmt.Sprintf("%s keyless", provider)
} }
e := serror.NewGeneric( e := &serror.Event{
fmt.Errorf("failed to verify the signature using provider '%s': %w", provider, err), Err: fmt.Errorf("failed to verify the signature using provider '%s': %w", provider, err),
sourcev1.VerificationError, Reason: sourcev1.VerificationError,
) }
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error()) conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e return sreconcile.ResultEmpty, e
} }
@ -650,15 +656,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
Force: obj.Generation != obj.Status.ObservedGeneration, Force: obj.Generation != obj.Status.ObservedGeneration,
// The remote builder will not attempt to download the chart if // The remote builder will not attempt to download the chart if
// an artifact exists with the same name and version and `Force` is false. // an artifact exists with the same name and version and `Force` is false.
// It will try to verify the chart if: // It will however try to verify the chart if `obj.Spec.Verify` is set, at every reconciliation.
// - we are on the first reconciliation Verify: obj.Spec.Verify != nil && obj.Spec.Verify.Provider != "",
// - the HelmChart spec has changed (generation drift)
// - the previous reconciliation resulted in a failed artifact verification
// - there is no artifact in storage
Verify: obj.Spec.Verify != nil && (obj.Generation <= 0 ||
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) ||
obj.GetArtifact() == nil),
} }
if artifact := obj.GetArtifact(); artifact != nil { if artifact := obj.GetArtifact(); artifact != nil {
opts.CachedChart = r.Storage.LocalPath(*artifact) opts.CachedChart = r.Storage.LocalPath(*artifact)
@ -1293,9 +1292,13 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
} }
switch buildErr.Reason { switch buildErr.Reason {
case chart.ErrChartMetadataPatch, chart.ErrValuesFilesMerge, chart.ErrDependencyBuild, chart.ErrChartPackage, chart.ErrChartVerification: case chart.ErrChartMetadataPatch, chart.ErrValuesFilesMerge, chart.ErrDependencyBuild, chart.ErrChartPackage:
conditions.Delete(obj, sourcev1.FetchFailedCondition) conditions.Delete(obj, sourcev1.FetchFailedCondition)
conditions.MarkTrue(obj, sourcev1.BuildFailedCondition, buildErr.Reason.Reason, buildErr.Error()) conditions.MarkTrue(obj, sourcev1.BuildFailedCondition, buildErr.Reason.Reason, buildErr.Error())
case chart.ErrChartVerification:
conditions.Delete(obj, sourcev1.FetchFailedCondition)
conditions.MarkTrue(obj, sourcev1.BuildFailedCondition, buildErr.Reason.Reason, buildErr.Error())
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, buildErr.Error())
default: default:
conditions.Delete(obj, sourcev1.BuildFailedCondition) conditions.Delete(obj, sourcev1.BuildFailedCondition)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, buildErr.Reason.Reason, buildErr.Error()) conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, buildErr.Reason.Reason, buildErr.Error())

View File

@ -2262,11 +2262,11 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T
tests := []struct { tests := []struct {
name string name string
shouldSign bool
beforeFunc func(obj *sourcev1.HelmChart)
want sreconcile.Result want sreconcile.Result
wantErr bool wantErr bool
wantErrMsg string wantErrMsg string
shouldSign bool
beforeFunc func(obj *sourcev1.HelmChart)
assertConditions []metav1.Condition assertConditions []metav1.Condition
cleanFunc func(g *WithT, build *chart.Build) cleanFunc func(g *WithT, build *chart.Build)
}{ }{
@ -2280,11 +2280,12 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T
SecretRef: &meta.LocalObjectReference{Name: "cosign-key"}, SecretRef: &meta.LocalObjectReference{Name: "cosign-key"},
} }
}, },
want: sreconcile.ResultEmpty,
wantErr: true, wantErr: true,
wantErrMsg: "chart verification error: failed to verify <url>: no matching signatures:", wantErrMsg: "chart verification error: failed to verify <url>: no matching signatures:",
want: sreconcile.ResultEmpty,
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.BuildFailedCondition, "ChartVerificationError", "chart verification error: failed to verify <url>: no matching signatures:"), *conditions.TrueCondition(sourcev1.BuildFailedCondition, "ChartVerificationError", "chart verification error: failed to verify <url>: no matching signatures:"),
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "chart verification error: failed to verify <url>: no matching signatures:"),
}, },
}, },
{ {
@ -2296,14 +2297,16 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T
Provider: "cosign", Provider: "cosign",
} }
}, },
wantErr: true,
want: sreconcile.ResultEmpty, want: sreconcile.ResultEmpty,
wantErr: true,
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.BuildFailedCondition, "ChartVerificationError", "chart verification error: failed to verify <url>: no matching signatures:"), *conditions.TrueCondition(sourcev1.BuildFailedCondition, "ChartVerificationError", "chart verification error: failed to verify <url>: no matching signatures:"),
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "chart verification error: failed to verify <url>: no matching signatures:"),
}, },
}, },
{ {
name: "signed charts should pass verification", name: "signed charts should pass verification",
shouldSign: true,
beforeFunc: func(obj *sourcev1.HelmChart) { beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Spec.Chart = metadata.Name obj.Spec.Chart = metadata.Name
obj.Spec.Version = metadata.Version obj.Spec.Version = metadata.Version
@ -2312,7 +2315,6 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T
SecretRef: &meta.LocalObjectReference{Name: "cosign-key"}, SecretRef: &meta.LocalObjectReference{Name: "cosign-key"},
} }
}, },
shouldSign: true,
want: sreconcile.ResultSuccess, want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled '<name>' chart with version '<version>'"), *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled '<name>' chart with version '<version>'"),

View File

@ -684,7 +684,7 @@ OCIRepositoryVerification
<p>Verify contains the secret name containing the trusted public keys <p>Verify contains the secret name containing the trusted public keys
used to verify the signature and specifies which provider to use to check used to verify the signature and specifies which provider to use to check
whether OCI image is authentic. whether OCI image is authentic.
This field is only supported for OCI sources. This field is only supported when using HelmRepository source with spec.type &lsquo;oci&rsquo;.
Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p> Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p>
</td> </td>
</tr> </tr>
@ -2269,7 +2269,7 @@ OCIRepositoryVerification
<p>Verify contains the secret name containing the trusted public keys <p>Verify contains the secret name containing the trusted public keys
used to verify the signature and specifies which provider to use to check used to verify the signature and specifies which provider to use to check
whether OCI image is authentic. whether OCI image is authentic.
This field is only supported for OCI sources. This field is only supported when using HelmRepository source with spec.type &lsquo;oci&rsquo;.
Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p> Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p>
</td> </td>
</tr> </tr>

View File

@ -290,7 +290,7 @@ data:
Note that the keys must have the `.pub` extension for Flux to make use of them. Note that the keys must have the `.pub` extension for Flux to make use of them.
Flux will loop over the public keys and use them verify a HelmChart's signature. Flux will loop over the public keys and use them to verify a HelmChart's signature.
This allows for older HelmCharts to be valid as long as the right key is in the secret. This allows for older HelmCharts to be valid as long as the right key is in the secret.
#### Keyless verification #### Keyless verification
@ -309,6 +309,7 @@ metadata:
name: podinfo name: podinfo
spec: spec:
interval: 5m interval: 5m
chart: podinfo
reconcileStrategy: ChartVersion reconcileStrategy: ChartVersion
sourceRef: sourceRef:
kind: HelmRepository kind: HelmRepository

View File

@ -462,7 +462,7 @@ data:
Note that the keys must have the `.pub` extension for Flux to make use of them. Note that the keys must have the `.pub` extension for Flux to make use of them.
Flux will loop over the public keys and use them verify an artifact's signature. Flux will loop over the public keys and use them to verify an artifact's signature.
This allows for older artifacts to be valid as long as the right key is in the secret. This allows for older artifacts to be valid as long as the right key is in the secret.
#### Keyless verification #### Keyless verification

View File

@ -105,7 +105,7 @@ func KeychainAdaptHelper(keyChain authn.Keychain) func(string) (registry.LoginOp
} }
// AuthAdaptHelper returns an ORAS credentials callback configured with the authorization data // AuthAdaptHelper returns an ORAS credentials callback configured with the authorization data
// from the given authn authenticator This allows for example to make use of credential helpers from // from the given authn authenticator. This allows for example to make use of credential helpers from
// cloud providers. // cloud providers.
// Ref: https://github.com/google/go-containerregistry/tree/main/pkg/authn // Ref: https://github.com/google/go-containerregistry/tree/main/pkg/authn
func AuthAdaptHelper(auth authn.Authenticator) (registry.LoginOption, error) { func AuthAdaptHelper(auth authn.Authenticator) (registry.LoginOption, error) {

View File

@ -523,10 +523,8 @@ func (r *ChartRepository) RemoveCache() error {
} }
// VerifyChart verifies the chart against a signature. // VerifyChart verifies the chart against a signature.
// If no signature is provided, a keyless verification is performed.
// It returns an error on failure. // It returns an error on failure.
func (r *ChartRepository) VerifyChart(_ context.Context, _ *repo.ChartVersion) error { func (r *ChartRepository) VerifyChart(_ context.Context, _ *repo.ChartVersion) error {
// no-op
// this is a no-op because this is not implemented yet. // this is a no-op because this is not implemented yet.
return fmt.Errorf("not implemented") return fmt.Errorf("not implemented")
} }