Skip to content

Commit fc066cf

Browse files
committed
[TEP-0089] SPIRE for non-falsifiable provenance.
This PR is a part of a larger set of PRs to provide non-falsifiable provenance through SPIRE. In particular this PR uses the SPIRE infrastructure which has already been merged to sign TaskRunStatus. It also has support to verify if TaskRunStatus has been modified by another workload between reconciles.
1 parent f3e9fc1 commit fc066cf

35 files changed

+2157
-62
lines changed

cmd/imagedigestexporter/main.go

+21
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"context"
2021
"encoding/json"
2122
"flag"
2223

24+
"github.com/tektoncd/pipeline/pkg/spire"
25+
"github.com/tektoncd/pipeline/pkg/spire/config"
2326
"github.com/tektoncd/pipeline/pkg/termination"
2427
"knative.dev/pkg/logging"
2528

@@ -31,6 +34,8 @@ import (
3134
var (
3235
images = flag.String("images", "", "List of images resources built by task in json format")
3336
terminationMessagePath = flag.String("terminationMessagePath", "/tekton/termination", "Location of file containing termination message")
37+
enableSpire = flag.Bool("enable_spire", false, "If specified by configmap, this enables spire signing and verification")
38+
socketPath = flag.String("spire_socket_path", "unix:///spiffe-workload-api/spire-agent.sock", "Experimental: The SPIRE agent socket for SPIFFE workload API.")
3439
)
3540

3641
/*
@@ -75,6 +80,22 @@ func main() {
7580
Value: imageResource.URL,
7681
ResourceName: imageResource.Name,
7782
})
83+
84+
}
85+
86+
if enableSpire != nil && *enableSpire && socketPath != nil && *socketPath != "" {
87+
ctx := context.Background()
88+
spireConfig := config.SpireConfig{
89+
SocketPath: *socketPath,
90+
}
91+
92+
spireWorkloadAPI := spire.NewEntrypointerAPIClient(&spireConfig)
93+
signed, err := spireWorkloadAPI.Sign(ctx, output)
94+
if err != nil {
95+
logger.Fatal(err)
96+
}
97+
98+
output = append(output, signed...)
7899
}
79100

80101
if err := termination.WriteMessage(*terminationMessagePath, output); err != nil {

examples/v1beta1/pipelineruns/4808-regression.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,4 @@ spec:
9292
name: result-test
9393
params:
9494
- name: RESULT_STRING_LENGTH
95-
value: "3000"
95+
value: "2000"

pkg/apis/config/feature_flags.go

+5
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ func CheckAlphaOrBetaAPIFields(ctx context.Context) bool {
348348
return cfg.FeatureFlags.EnableAPIFields == AlphaAPIFields || cfg.FeatureFlags.EnableAPIFields == BetaAPIFields
349349
}
350350

351+
// IsSpireEnabled checks if non-falsifiable provenance is enforced through SPIRE
352+
func IsSpireEnabled(ctx context.Context) bool {
353+
return FromContextOrDefaults(ctx).FeatureFlags.EnforceNonfalsifiability == EnforceNonfalsifiabilityWithSpire
354+
}
355+
351356
func setEnableAPIFields(ctx context.Context, want string) context.Context {
352357
featureFlags, _ := NewFeatureFlagsFromMap(map[string]string{
353358
"enable-api-fields": want,

pkg/apis/config/feature_flags_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,28 @@ func TestCheckAlphaOrBetaAPIFields(t *testing.T) {
318318
}
319319
}
320320

321+
func TestIsSpireEnabled(t *testing.T) {
322+
ctx := context.Background()
323+
if config.IsSpireEnabled(ctx) {
324+
t.Errorf("IsSpireEnabled got true but expected to be false")
325+
}
326+
store := config.NewStore(logging.FromContext(ctx).Named("config-store"))
327+
featureflags := &corev1.ConfigMap{
328+
ObjectMeta: metav1.ObjectMeta{
329+
Name: "feature-flags",
330+
},
331+
Data: map[string]string{
332+
"enable-api-fields": "alpha",
333+
"enforce-nonfalsifiability": config.EnforceNonfalsifiabilityWithSpire,
334+
},
335+
}
336+
store.OnConfigChanged(featureflags)
337+
ctx = store.ToContext(ctx)
338+
if !config.IsSpireEnabled(ctx) {
339+
t.Errorf("IsSpireEnabled got false but expected to be true")
340+
}
341+
}
342+
321343
func verifyConfigFileWithExpectedFeatureFlagsConfig(t *testing.T, fileName string, expectedConfig *config.FeatureFlags) {
322344
t.Helper()
323345
cm := test.ConfigMapFromTestFile(t, fileName)

pkg/pod/pod.go

+35
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
3232
"github.com/tektoncd/pipeline/pkg/internal/computeresources/tasklevel"
3333
"github.com/tektoncd/pipeline/pkg/names"
34+
"github.com/tektoncd/pipeline/pkg/spire"
3435
corev1 "k8s.io/api/core/v1"
3536
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3637
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -132,6 +133,10 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec
132133
// Secrets, along with any arguments needed by Step entrypoints to process
133134
// those secrets.
134135
commonExtraEntrypointArgs := []string{}
136+
// Entrypoint arg to enable or disable spire
137+
if config.IsSpireEnabled(ctx) {
138+
commonExtraEntrypointArgs = append(commonExtraEntrypointArgs, "-enable_spire")
139+
}
135140
credEntrypointArgs, credVolumes, credVolumeMounts, err := credsInit(ctx, taskRun.Spec.ServiceAccountName, taskRun.Namespace, b.KubeClient)
136141
if err != nil {
137142
return nil, err
@@ -322,6 +327,36 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec
322327
return nil, err
323328
}
324329

330+
readonly := true
331+
if config.IsSpireEnabled(ctx) {
332+
volumes = append(volumes, corev1.Volume{
333+
Name: spire.WorkloadAPI,
334+
VolumeSource: corev1.VolumeSource{
335+
CSI: &corev1.CSIVolumeSource{
336+
Driver: "csi.spiffe.io",
337+
ReadOnly: &readonly,
338+
},
339+
},
340+
})
341+
342+
for i := range stepContainers {
343+
c := &stepContainers[i]
344+
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
345+
Name: spire.WorkloadAPI,
346+
MountPath: spire.VolumeMountPath,
347+
ReadOnly: true,
348+
})
349+
}
350+
for i := range initContainers {
351+
c := &initContainers[i]
352+
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
353+
Name: spire.WorkloadAPI,
354+
MountPath: spire.VolumeMountPath,
355+
ReadOnly: true,
356+
})
357+
}
358+
}
359+
325360
mergedPodContainers := stepContainers
326361

327362
// Merge sidecar containers with step containers.

pkg/pod/pod_test.go

+162
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
3636
"github.com/tektoncd/pipeline/pkg/apis/pipeline/pod"
3737
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
38+
"github.com/tektoncd/pipeline/pkg/spire"
3839
"github.com/tektoncd/pipeline/test/diff"
3940
"github.com/tektoncd/pipeline/test/names"
4041
corev1 "k8s.io/api/core/v1"
@@ -2458,6 +2459,167 @@ func TestPodBuild_TaskLevelResourceRequirements(t *testing.T) {
24582459
}
24592460
}
24602461

2462+
func TestPodBuildwithSpireEnabled(t *testing.T) {
2463+
initContainers := []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}
2464+
readonly := true
2465+
for i := range initContainers {
2466+
c := &initContainers[i]
2467+
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
2468+
Name: spire.WorkloadAPI,
2469+
MountPath: spire.VolumeMountPath,
2470+
ReadOnly: true,
2471+
})
2472+
}
2473+
2474+
for _, c := range []struct {
2475+
desc string
2476+
trs v1beta1.TaskRunSpec
2477+
trAnnotation map[string]string
2478+
ts v1beta1.TaskSpec
2479+
want *corev1.PodSpec
2480+
wantAnnotations map[string]string
2481+
}{{
2482+
desc: "simple",
2483+
ts: v1beta1.TaskSpec{
2484+
Steps: []v1beta1.Step{{
2485+
Name: "name",
2486+
Image: "image",
2487+
Command: []string{"cmd"}, // avoid entrypoint lookup.
2488+
}},
2489+
},
2490+
want: &corev1.PodSpec{
2491+
RestartPolicy: corev1.RestartPolicyNever,
2492+
InitContainers: initContainers,
2493+
Containers: []corev1.Container{{
2494+
Name: "step-name",
2495+
Image: "image",
2496+
Command: []string{"/tekton/bin/entrypoint"},
2497+
Args: []string{
2498+
"-wait_file",
2499+
"/tekton/downward/ready",
2500+
"-wait_file_content",
2501+
"-post_file",
2502+
"/tekton/run/0/out",
2503+
"-termination_path",
2504+
"/tekton/termination",
2505+
"-step_metadata_dir",
2506+
"/tekton/run/0/status",
2507+
"-enable_spire",
2508+
"-entrypoint",
2509+
"cmd",
2510+
"--",
2511+
},
2512+
VolumeMounts: append([]corev1.VolumeMount{binROMount, runMount(0, false), downwardMount, {
2513+
Name: "tekton-creds-init-home-0",
2514+
MountPath: "/tekton/creds",
2515+
}, {
2516+
Name: spire.WorkloadAPI,
2517+
MountPath: spire.VolumeMountPath,
2518+
ReadOnly: true,
2519+
}}, implicitVolumeMounts...),
2520+
TerminationMessagePath: "/tekton/termination",
2521+
}},
2522+
Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{
2523+
Name: "tekton-creds-init-home-0",
2524+
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}},
2525+
}, corev1.Volume{
2526+
Name: spire.WorkloadAPI,
2527+
VolumeSource: corev1.VolumeSource{
2528+
CSI: &corev1.CSIVolumeSource{
2529+
Driver: "csi.spiffe.io",
2530+
ReadOnly: &readonly,
2531+
},
2532+
},
2533+
}),
2534+
ActiveDeadlineSeconds: &defaultActiveDeadlineSeconds,
2535+
},
2536+
}} {
2537+
t.Run(c.desc, func(t *testing.T) {
2538+
featureFlags := map[string]string{
2539+
"enable-api-fields": "alpha",
2540+
"enforce-nonfalsifiability": "spire",
2541+
}
2542+
names.TestingSeed()
2543+
store := config.NewStore(logtesting.TestLogger(t))
2544+
store.OnConfigChanged(
2545+
&corev1.ConfigMap{
2546+
ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()},
2547+
Data: featureFlags,
2548+
},
2549+
)
2550+
kubeclient := fakek8s.NewSimpleClientset(
2551+
&corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"}},
2552+
&corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "service-account", Namespace: "default"},
2553+
Secrets: []corev1.ObjectReference{{
2554+
Name: "multi-creds",
2555+
}},
2556+
},
2557+
&corev1.Secret{
2558+
ObjectMeta: metav1.ObjectMeta{
2559+
Name: "multi-creds",
2560+
Namespace: "default",
2561+
Annotations: map[string]string{
2562+
"tekton.dev/docker-0": "https://us.gcr.io",
2563+
"tekton.dev/docker-1": "https://docker.io",
2564+
"tekton.dev/git-0": "github.com",
2565+
"tekton.dev/git-1": "gitlab.com",
2566+
}},
2567+
Type: "kubernetes.io/basic-auth",
2568+
Data: map[string][]byte{
2569+
"username": []byte("foo"),
2570+
"password": []byte("BestEver"),
2571+
},
2572+
},
2573+
)
2574+
var trAnnotations map[string]string
2575+
if c.trAnnotation == nil {
2576+
trAnnotations = map[string]string{
2577+
ReleaseAnnotation: fakeVersion,
2578+
}
2579+
} else {
2580+
trAnnotations = c.trAnnotation
2581+
trAnnotations[ReleaseAnnotation] = fakeVersion
2582+
}
2583+
tr := &v1beta1.TaskRun{
2584+
ObjectMeta: metav1.ObjectMeta{
2585+
Name: "taskrun-name",
2586+
Namespace: "default",
2587+
Annotations: trAnnotations,
2588+
},
2589+
Spec: c.trs,
2590+
}
2591+
2592+
// No entrypoints should be looked up.
2593+
entrypointCache := fakeCache{}
2594+
builder := Builder{
2595+
Images: images,
2596+
KubeClient: kubeclient,
2597+
EntrypointCache: entrypointCache,
2598+
}
2599+
2600+
got, err := builder.Build(store.ToContext(context.Background()), tr, c.ts)
2601+
if err != nil {
2602+
t.Fatalf("builder.Build: %v", err)
2603+
}
2604+
2605+
want := kmeta.ChildName(tr.Name, "-pod")
2606+
if d := cmp.Diff(got.Name, want); d != "" {
2607+
t.Errorf("got %v; want %v", got.Name, want)
2608+
}
2609+
2610+
if d := cmp.Diff(c.want, &got.Spec, resourceQuantityCmp, volumeSort, volumeMountSort); d != "" {
2611+
t.Errorf("Diff %s", diff.PrintWantGot(d))
2612+
}
2613+
2614+
if c.wantAnnotations != nil {
2615+
if d := cmp.Diff(c.wantAnnotations, got.ObjectMeta.Annotations, cmpopts.IgnoreMapEntries(ignoreReleaseAnnotation)); d != "" {
2616+
t.Errorf("Annotation Diff(-want, +got):\n%s", d)
2617+
}
2618+
}
2619+
})
2620+
}
2621+
}
2622+
24612623
// verifyTaskLevelComputeResources verifies that the given TaskRun's containers have the expected compute resources.
24622624
func verifyTaskLevelComputeResources(expectedComputeResources []ExpectedComputeResources, containers []corev1.Container) error {
24632625
if len(expectedComputeResources) != len(containers) {

0 commit comments

Comments
 (0)