forked from sigstore/policy-controller
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Create an interface for downstream CIP integrations.
🎁 This change factors a new small library `./pkg/policy` which is intended to streamline incorporating CIP validation into downstream tooling. For a (much) more verbose explanation see [here](ko-build/ko#356 (comment)), but the general idea behind this is to allow CIP's to gate consumption of images in other contexts, for example the base images in build tools such as `ko` or `kaniko`. The idea is to enable the tool providers to bake-in default policies for default base images, and optionally expose configuration to let users write policies to authorize base images prior to consumption. For example, I might write the following `.ko.yaml`: ```yaml verification: noMatchPolicy: deny policies: - data: | # inline policy - url: https://github.com/foo/bar/blobs/main/POLICY.yaml ``` With this library, it is likely <100 LoC to add base image policy verification to `ko`, and significantly simplifies our own `policy-tester` which has spaghetti code replicating some of this functionality. /kind feature Signed-off-by: Matt Moore <mattmoor@chainguard.dev>
- Loading branch information
Showing
9 changed files
with
1,369 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright 2023 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package policy | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/sigstore/policy-controller/pkg/apis/policy/v1alpha1" | ||
"github.com/sigstore/policy-controller/pkg/apis/policy/v1beta1" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"knative.dev/pkg/apis" | ||
"sigs.k8s.io/yaml" | ||
) | ||
|
||
// Parse decodes a provided YAML document containing zero or more objects into | ||
// a collection of unstructured.Unstructured objects. | ||
func Parse(ctx context.Context, document string) ([]*unstructured.Unstructured, error) { | ||
docs := strings.Split(document, "\n---\n") | ||
|
||
objs := make([]*unstructured.Unstructured, 0, len(docs)) | ||
for i, doc := range docs { | ||
doc = strings.TrimSpace(doc) | ||
if doc == "" { | ||
continue | ||
} | ||
var obj unstructured.Unstructured | ||
if err := yaml.Unmarshal([]byte(doc), &obj); err != nil { | ||
return nil, fmt.Errorf("decoding object[%d]: %w", i, err) | ||
} | ||
if obj.GetAPIVersion() == "" { | ||
return nil, apis.ErrMissingField("apiVersion").ViaIndex(i) | ||
} | ||
if obj.GetName() == "" { | ||
return nil, apis.ErrMissingField("metadata.name").ViaIndex(i) | ||
} | ||
objs = append(objs, &obj) | ||
} | ||
return objs, nil | ||
} | ||
|
||
// ParseClusterImagePolicies returns ClusterImagePolicy objects found in the | ||
// policy document. | ||
func ParseClusterImagePolicies(ctx context.Context, document string) (cips []*v1alpha1.ClusterImagePolicy, warns error, err error) { | ||
if warns, err = Validate(ctx, document); err != nil { | ||
return nil, warns, err | ||
} | ||
|
||
ol, err := Parse(ctx, document) | ||
if err != nil { | ||
// "Validate" above calls "Parse", so this is unreachable. | ||
return nil, warns, err | ||
} | ||
|
||
cips = make([]*v1alpha1.ClusterImagePolicy, 0, len(ol)) | ||
for _, obj := range ol { | ||
gv, err := schema.ParseGroupVersion(obj.GetAPIVersion()) | ||
if err != nil { | ||
// Practically speaking unstructured.Unstructured won't let this happen. | ||
return nil, warns, fmt.Errorf("error parsing apiVersion of: %w", err) | ||
} | ||
|
||
cip := &v1alpha1.ClusterImagePolicy{} | ||
|
||
switch gv.WithKind(obj.GetKind()) { | ||
case v1beta1.SchemeGroupVersion.WithKind("ClusterImagePolicy"): | ||
v1b1 := &v1beta1.ClusterImagePolicy{} | ||
if err := convert(obj, v1b1); err != nil { | ||
return nil, warns, err | ||
} | ||
if err := cip.ConvertFrom(ctx, v1b1); err != nil { | ||
return nil, warns, err | ||
} | ||
|
||
case v1alpha1.SchemeGroupVersion.WithKind("ClusterImagePolicy"): | ||
// This is allowed, but we should convert things. | ||
if err := convert(obj, cip); err != nil { | ||
return nil, warns, err | ||
} | ||
|
||
default: | ||
continue | ||
} | ||
|
||
cips = append(cips, cip) | ||
} | ||
return cips, warns, nil | ||
} | ||
|
||
func convert(from interface{}, to runtime.Object) error { | ||
bs, err := json.Marshal(from) | ||
if err != nil { | ||
return fmt.Errorf("Marshal() = %w", err) | ||
} | ||
if err := json.Unmarshal(bs, to); err != nil { | ||
return fmt.Errorf("Unmarshal() = %w", err) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.