Skip to content

Commit ba01305

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 bcabd01 commit ba01305

File tree

16 files changed

+1245
-62
lines changed

16 files changed

+1245
-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
@@ -369,6 +369,11 @@ func CheckWarnResourceVerificationMode(ctx context.Context) bool {
369369
return cfg.FeatureFlags.ResourceVerificationMode == WarnResourceVerificationMode
370370
}
371371

372+
// IsSpireEnabled checks if non-falsifiable provenance is enforced through SPIRE
373+
func IsSpireEnabled(ctx context.Context) bool {
374+
return FromContextOrDefaults(ctx).FeatureFlags.EnforceNonfalsifiability == EnforceNonfalsifiabilityWithSpire
375+
}
376+
372377
func setEnableAPIFields(ctx context.Context, want string) context.Context {
373378
featureFlags, _ := NewFeatureFlagsFromMap(map[string]string{
374379
"enable-api-fields": want,

pkg/apis/config/feature_flags_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,28 @@ func TestCheckWarnResourceVerificationMode(t *testing.T) {
297297
}
298298
}
299299

300+
func TestIsSpireEnabled(t *testing.T) {
301+
ctx := context.Background()
302+
if config.IsSpireEnabled(ctx) {
303+
t.Errorf("IsSpireEnabled got true but expected to be false")
304+
}
305+
store := config.NewStore(logging.FromContext(ctx).Named("config-store"))
306+
featureflags := &corev1.ConfigMap{
307+
ObjectMeta: metav1.ObjectMeta{
308+
Name: "feature-flags",
309+
},
310+
Data: map[string]string{
311+
"enable-api-fields": "alpha",
312+
"enforce-nonfalsifiability": config.EnforceNonfalsifiabilityWithSpire,
313+
},
314+
}
315+
store.OnConfigChanged(featureflags)
316+
ctx = store.ToContext(ctx)
317+
if !config.IsSpireEnabled(ctx) {
318+
t.Errorf("IsSpireEnabled got false but expected to be true")
319+
}
320+
}
321+
300322
func verifyConfigFileWithExpectedFeatureFlagsConfig(t *testing.T, fileName string, expectedConfig *config.FeatureFlags) {
301323
t.Helper()
302324
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.FromContextOrDefaults(ctx).FeatureFlags.EnforceNonfalsifiability == config.EnforceNonfalsifiabilityWithSpire {
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.FromContextOrDefaults(ctx).FeatureFlags.EnforceNonfalsifiability == config.EnforceNonfalsifiabilityWithSpire {
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)