Skip to content

Commit

Permalink
[TEP-0089] SPIRE for non-falsifiable provenance.
Browse files Browse the repository at this point in the history
This PR is a part of a larger set of PRs to provide non-falsifiable provenance through SPIRE.
In particular this PR makes changes to the pod created to run a taskrun.
This pod needs access to SpireApi which is mounted as a CSI volume into the pod.

Signed-off-by: jagathprakash <[email protected]>
  • Loading branch information
jagathprakash committed Apr 14, 2023
1 parent c147405 commit 102b5bb
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 0 deletions.
41 changes: 41 additions & 0 deletions pkg/pod/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/internal/computeresources/tasklevel"
"github.com/tektoncd/pipeline/pkg/names"
"github.com/tektoncd/pipeline/pkg/spire"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -53,6 +54,9 @@ const (
// deadlineFactor is the factor we multiply the taskrun timeout with to determine the activeDeadlineSeconds of the Pod.
// It has to be higher than the timeout (to not be killed before)
deadlineFactor = 1.5

// csiDriver is the CSI storage plugin needed for injection of SPIFFE workload api.
SpiffeCsiDriver = "csi.spiffe.io"
)

// These are effectively const, but Go doesn't have such an annotation.
Expand Down Expand Up @@ -132,6 +136,10 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec
// Secrets, along with any arguments needed by Step entrypoints to process
// those secrets.
commonExtraEntrypointArgs := []string{}
// Entrypoint arg to enable or disable spire
if config.IsSpireEnabled(ctx) {
commonExtraEntrypointArgs = append(commonExtraEntrypointArgs, "-enable_spire")
}
credEntrypointArgs, credVolumes, credVolumeMounts, err := credsInit(ctx, taskRun.Spec.ServiceAccountName, taskRun.Namespace, b.KubeClient)
if err != nil {
return nil, err
Expand Down Expand Up @@ -322,6 +330,39 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec
return nil, err
}

readonly := true
if config.IsSpireEnabled(ctx) {
// add SPIRE's CSI volume to the explicitly declared use volumes
volumes = append(volumes, corev1.Volume{
Name: spire.WorkloadAPI,
VolumeSource: corev1.VolumeSource{
CSI: &corev1.CSIVolumeSource{
Driver: SpiffeCsiDriver,
ReadOnly: &readonly,
},
},
})

// mount SPIRE's CSI volume to each Step Container
for i := range stepContainers {
c := &stepContainers[i]
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
Name: spire.WorkloadAPI,
MountPath: spire.VolumeMountPath,
ReadOnly: readonly,
})
}
for i := range initContainers {
// mount SPIRE's CSI volume to each Init Container
c := &initContainers[i]
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
Name: spire.WorkloadAPI,
MountPath: spire.VolumeMountPath,
ReadOnly: readonly,
})
}
}

mergedPodContainers := stepContainers

// Merge sidecar containers with step containers.
Expand Down
162 changes: 162 additions & 0 deletions pkg/pod/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/pod"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/spire"
"github.com/tektoncd/pipeline/test/diff"
"github.com/tektoncd/pipeline/test/names"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -2455,6 +2456,167 @@ func TestPodBuild_TaskLevelResourceRequirements(t *testing.T) {
}
}

func TestPodBuildwithSpireEnabled(t *testing.T) {
initContainers := []corev1.Container{entrypointInitContainer(images.EntrypointImage, []v1beta1.Step{{Name: "name"}})}
readonly := true
for i := range initContainers {
c := &initContainers[i]
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
Name: spire.WorkloadAPI,
MountPath: spire.VolumeMountPath,
ReadOnly: true,
})
}

for _, c := range []struct {
desc string
trs v1beta1.TaskRunSpec
trAnnotation map[string]string
ts v1beta1.TaskSpec
want *corev1.PodSpec
wantAnnotations map[string]string
}{{
desc: "simple",
ts: v1beta1.TaskSpec{
Steps: []v1beta1.Step{{
Name: "name",
Image: "image",
Command: []string{"cmd"}, // avoid entrypoint lookup.
}},
},
want: &corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
InitContainers: initContainers,
Containers: []corev1.Container{{
Name: "step-name",
Image: "image",
Command: []string{"/tekton/bin/entrypoint"},
Args: []string{
"-wait_file",
"/tekton/downward/ready",
"-wait_file_content",
"-post_file",
"/tekton/run/0/out",
"-termination_path",
"/tekton/termination",
"-step_metadata_dir",
"/tekton/run/0/status",
"-enable_spire",
"-entrypoint",
"cmd",
"--",
},
VolumeMounts: append([]corev1.VolumeMount{binROMount, runMount(0, false), downwardMount, {
Name: "tekton-creds-init-home-0",
MountPath: "/tekton/creds",
}, {
Name: spire.WorkloadAPI,
MountPath: spire.VolumeMountPath,
ReadOnly: true,
}}, implicitVolumeMounts...),
TerminationMessagePath: "/tekton/termination",
}},
Volumes: append(implicitVolumes, binVolume, runVolume(0), downwardVolume, corev1.Volume{
Name: "tekton-creds-init-home-0",
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}},
}, corev1.Volume{
Name: spire.WorkloadAPI,
VolumeSource: corev1.VolumeSource{
CSI: &corev1.CSIVolumeSource{
Driver: "csi.spiffe.io",
ReadOnly: &readonly,
},
},
}),
ActiveDeadlineSeconds: &defaultActiveDeadlineSeconds,
},
}} {
t.Run(c.desc, func(t *testing.T) {
featureFlags := map[string]string{
"enable-api-fields": "alpha",
"enforce-nonfalsifiability": "spire",
}
names.TestingSeed()
store := config.NewStore(logtesting.TestLogger(t))
store.OnConfigChanged(
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()},
Data: featureFlags,
},
)
kubeclient := fakek8s.NewSimpleClientset(
&corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"}},
&corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "service-account", Namespace: "default"},
Secrets: []corev1.ObjectReference{{
Name: "multi-creds",
}},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "multi-creds",
Namespace: "default",
Annotations: map[string]string{
"tekton.dev/docker-0": "https://us.gcr.io",
"tekton.dev/docker-1": "https://docker.io",
"tekton.dev/git-0": "github.com",
"tekton.dev/git-1": "gitlab.com",
}},
Type: "kubernetes.io/basic-auth",
Data: map[string][]byte{
"username": []byte("foo"),
"password": []byte("BestEver"),
},
},
)
var trAnnotations map[string]string
if c.trAnnotation == nil {
trAnnotations = map[string]string{
ReleaseAnnotation: fakeVersion,
}
} else {
trAnnotations = c.trAnnotation
trAnnotations[ReleaseAnnotation] = fakeVersion
}
tr := &v1beta1.TaskRun{
ObjectMeta: metav1.ObjectMeta{
Name: "taskrun-name",
Namespace: "default",
Annotations: trAnnotations,
},
Spec: c.trs,
}

// No entrypoints should be looked up.
entrypointCache := fakeCache{}
builder := Builder{
Images: images,
KubeClient: kubeclient,
EntrypointCache: entrypointCache,
}

got, err := builder.Build(store.ToContext(context.Background()), tr, c.ts)
if err != nil {
t.Fatalf("builder.Build: %v", err)
}

want := kmeta.ChildName(tr.Name, "-pod")
if d := cmp.Diff(got.Name, want); d != "" {
t.Errorf("got %v; want %v", got.Name, want)
}

if d := cmp.Diff(c.want, &got.Spec, resourceQuantityCmp, volumeSort, volumeMountSort); d != "" {
t.Errorf("Diff %s", diff.PrintWantGot(d))
}

if c.wantAnnotations != nil {
if d := cmp.Diff(c.wantAnnotations, got.ObjectMeta.Annotations, cmpopts.IgnoreMapEntries(ignoreReleaseAnnotation)); d != "" {
t.Errorf("Annotation Diff(-want, +got):\n%s", d)
}
}
})
}
}

// verifyTaskLevelComputeResources verifies that the given TaskRun's containers have the expected compute resources.
func verifyTaskLevelComputeResources(expectedComputeResources []ExpectedComputeResources, containers []corev1.Container) error {
if len(expectedComputeResources) != len(containers) {
Expand Down

0 comments on commit 102b5bb

Please sign in to comment.