Skip to content

Commit bcabd01

Browse files
jagathprakashtekton-robot
authored andcommittedJan 30, 2023
[TEP-0089] Add a config map to support SPIRE initialization.
This PR is one among the set of PRs done to implement TEP-0089. This PR has been derived from the larger PRs PR#5715 and PR#4759 by @pxp928 and @lumjjb. This PR is addressing the problem of non-falisfiable provenance discussed in TEP-0089. SPIRE is a tool which provides cryptographic identities to workloads in a cluster. These identities are also associated with a key pair, which can be used to sign TaskRun results to track if they have been modified by other workloads. Using SPIRE is one of the ways to address non-falsifiable provenance. This PR has the following changes 1. Modify the feature flag to control non-falsifiability from enable-spire to enforce-nonfalsifiability="spire". This is in accordance to the approved TEP. 2. Add a configmap config-spire to initialize SPIRE. Signed-off-by: jagathprakash <31057312+jagathprakash@users.noreply.github.com>
1 parent ba2eb8a commit bcabd01

22 files changed

+535
-23
lines changed
 

‎config/config-feature-flags.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,8 @@ data:
9494
# Acceptable values are "v1beta1" and "v1alpha1".
9595
# The default is "v1beta1".
9696
custom-task-version: "v1beta1"
97+
# Setting this flag will determine how Tekton pipelines will handle non-falsifiable provenance.
98+
# If set to "spire", then SPIRE will be used to ensure non-falsifiable provenance.
99+
# If set to "none", then Tekton will not have non-falsifiable provenance.
100+
# This is an experimental feature and thus should still be considered an alpha feature.
101+
enforce-nonfalsifiablity: "none"

‎config/config-spire.yaml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2022 The Tekton Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
apiVersion: v1
16+
kind: ConfigMap
17+
metadata:
18+
name: config-spire
19+
namespace: tekton-pipelines
20+
labels:
21+
app.kubernetes.io/instance: default
22+
app.kubernetes.io/part-of: tekton-pipelines
23+
data:
24+
_example: |
25+
################################
26+
# #
27+
# EXAMPLE CONFIGURATION #
28+
# #
29+
################################
30+
# This block is not actually functional configuration,
31+
# but serves to illustrate the available configuration
32+
# options and document them in a way that is accessible
33+
# to users that `kubectl edit` this config map.
34+
#
35+
# These sample configuration options may be copied out of
36+
# this example block and unindented to be in the data block
37+
# to actually change the configuration.
38+
#
39+
# spire-trust-domain specifies the SPIRE trust domain to use.
40+
# spire-trust-domain: "example.org"
41+
#
42+
# spire-socket-path specifies the SPIRE agent socket for SPIFFE workload API.
43+
# spire-socket-path: "unix:///spiffe-workload-api/spire-agent.sock"
44+
#
45+
# spire-server-addr specifies the SPIRE server address for workload/node registration.
46+
# spire-server-addr: "spire-server.spire.svc.cluster.local:8081"
47+
#
48+
# spire-node-alias-prefix specifies the SPIRE node alias prefix to use.
49+
# spire-node-alias-prefix: "/tekton-node/"

‎config/controller.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ spec:
116116
value: feature-flags
117117
- name: CONFIG_LEADERELECTION_NAME
118118
value: config-leader-election
119+
- name: CONFIG_SPIRE
120+
value: config-spire
119121
- name: CONFIG_TRUSTED_RESOURCES_NAME
120122
value: config-trusted-resources
121123
- name: SSL_CERT_FILE

‎docs/spire.md

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!--
2+
---
3+
linkTitle: "TaskRun Result Attestation"
4+
weight: 1660
5+
---
6+
-->
7+
⚠️ This is a work in progress: SPIRE support is not yet functional
8+
9+
TaskRun result attestations is currently an alpha experimental feature. Currently all that is implemented is support for configuring Tekton to connect to SPIRE. See TEP-0089 for details on the overall design and feature set.
10+
11+
This being a large feature, this will be implemented in the following phases. This document will be updated as we implement new phases.
12+
1. Add a client for SPIRE (done).
13+
2. Add a configMap which initializes SPIRE (in progress).
14+
3. Modify TaskRun to sign and verify TaskRun Results using SPIRE.
15+
4. Modify Tekton Chains to verify the TaskRun Results.
16+
17+
## Architecture Overview
18+
19+
This feature relies on a SPIRE installation. This is how it integrates into the architecture of Tekton:
20+
21+
```
22+
┌─────────────┐ Register TaskRun Workload Identity ┌──────────┐
23+
│ ├──────────────────────────────────────────────►│ │
24+
│ Tekton │ │ SPIRE │
25+
│ Controller │◄───────────┐ │ Server │
26+
│ │ │ Listen on TaskRun │ │
27+
└────────────┬┘ │ └──────────┘
28+
▲ │ ┌───────┴───────────────────────────────┐ ▲
29+
│ │ │ Tekton TaskRun │ │
30+
│ │ └───────────────────────────────────────┘ │
31+
│ Configure│ ▲ │ Attest
32+
│ Pod & │ │ │ +
33+
│ check │ │ │ Request
34+
│ ready │ ┌───────────┐ │ │ SVIDs
35+
│ └────►│ TaskRun ├────────────────────────┘ │
36+
│ │ Pod │ │
37+
│ └───────────┘ TaskRun Entrypointer │
38+
│ ▲ Sign Result and update │
39+
│ Get │ Get SVID TaskRun status with │
40+
│ SPIRE │ signature + cert │
41+
│ server │ │
42+
│ Credentials │ ▼
43+
┌┴───────────────────┴─────────────────────────────────────────────────────┐
44+
│ │
45+
│ SPIRE Agent ( Runs as ) │
46+
│ + CSI Driver ( Daemonset ) │
47+
│ │
48+
└──────────────────────────────────────────────────────────────────────────┘
49+
```
50+
51+
Initial Setup:
52+
1. As part of the SPIRE deployment, the SPIRE server attests the agents running on each node in the cluster.
53+
1. The Tekton Controller is configured to have workload identity entry creation permissions to the SPIRE server.
54+
1. As part of the Tekton Controller operations, the Tekton Controller will retrieve an identity that it can use to talk to the SPIRE server to register TaskRun workloads.
55+
56+
When a TaskRun is created:
57+
1. The Tekton Controller creates a TaskRun pod and its associated resources
58+
1. When the TaskRun pod is ready, the Tekton Controller registers an identity with the information of the pod to the SPIRE server. This will tell the SPIRE server the identity of the TaskRun to use as well as how to attest the workload/pod.
59+
1. After the TaskRun steps complete, as part of the entrypointer code, it requests an SVID from SPIFFE workload API (via the SPIRE agent socket)
60+
1. The SPIRE agent will attest the workload and request an SVID.
61+
1. The entrypointer receives an x509 SVID, containing the x509 certificate and associated private key.
62+
1. The entrypointer signs the results of the TaskRun and emits the signatures and x509 certificate to the TaskRun results for later verification.
63+
64+
## Enabling TaskRun result attestations
65+
66+
To enable TaskRun attestations:
67+
1. Make sure `enforce-nonfalsifiability` is set to `"spire"` in the `feature-flags` configmap, see [`install.md`](./install.md#customizing-the-pipelines-controller-behavior) for details
68+
1. Create a SPIRE deployment containing a SPIRE server, SPIRE agents and the SPIRE CSI driver, for convenience, [this sample single cluster deployment](https://github.com/spiffe/spiffe-csi/tree/main/example/config) can be used.
69+
1. Register the SPIRE workload entry for Tekton with the "Admin" flag, which will allow the Tekton controller to communicate with the SPIRE server to manage the TaskRun identities dynamically.
70+
```
71+
72+
# This example is assuming use of the above SPIRE deployment
73+
# Example where trust domain is "example.org" and cluster name is "example-cluster"
74+
75+
# Register a node alias for all nodes of which the Tekton Controller may reside
76+
kubectl -n spire exec -it \
77+
deployment/spire-server -- \
78+
/opt/spire/bin/spire-server entry create \
79+
-node \
80+
-spiffeID spiffe://example.org/allnodes \
81+
-selector k8s_psat:cluster:example-cluster
82+
83+
# Register the tekton controller workload to have access to creating entries in the SPIRE server
84+
kubectl -n spire exec -it \
85+
deployment/spire-server -- \
86+
/opt/spire/bin/spire-server entry create \
87+
-admin \
88+
-spiffeID spiffe://example.org/tekton/controller \
89+
-parentID spiffe://example.org/allnode \
90+
-selector k8s:ns:tekton-pipelines \
91+
-selector k8s:pod-label:app:tekton-pipelines-controller \
92+
-selector k8s:sa:tekton-pipelines-controller
93+
94+
```
95+
96+
1. Modify the controller (`config/controller.yaml`) to provide access to the SPIRE agent socket.
97+
```yaml
98+
# Add the following the volumeMounts of the "tekton-pipelines-controller" container
99+
- name: spiffe-workload-api
100+
mountPath: /spiffe-workload-api
101+
readOnly: true
102+
103+
# Add the following to the volumes of the controller pod
104+
- name: spiffe-workload-api
105+
csi:
106+
driver: "csi.spiffe.io"
107+
```
108+
1. (Optional) Modify the configmap (`config/config-spire.yaml`) to configure non-default SPIRE options.
109+
```yaml
110+
apiVersion: v1
111+
kind: ConfigMap
112+
metadata:
113+
name: config-spire
114+
namespace: tekton-pipelines
115+
labels:
116+
app.kubernetes.io/instance: default
117+
app.kubernetes.io/part-of: tekton-pipelines
118+
data:
119+
# More explanation about the fields is at the SPIRE Server Configuration file
120+
# https://spiffe.io/docs/latest/deploying/spire_server/#server-configuration-file
121+
# spire-trust-domain specifies the SPIRE trust domain to use.
122+
spire-trust-domain: "example.org"
123+
# spire-socket-path specifies the SPIRE agent socket for SPIFFE workload API.
124+
spire-socket-path: "unix:///spiffe-workload-api/spire-agent.sock"
125+
# spire-server-addr specifies the SPIRE server address for workload/node registration.
126+
spire-server-addr: "spire-server.spire.svc.cluster.local:8081"
127+
# spire-node-alias-prefix specifies the SPIRE node alias prefix to use.
128+
spire-node-alias-prefix: "/tekton-node/"
129+
```

‎hack/update-codegen.sh

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ ${PREFIX}/deepcopy-gen \
5757
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt \
5858
-i github.com/tektoncd/pipeline/pkg/apis/config
5959

60+
${PREFIX}/deepcopy-gen \
61+
-O zz_generated.deepcopy \
62+
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt \
63+
-i github.com/tektoncd/pipeline/pkg/spire/config
64+
6065
${PREFIX}/deepcopy-gen \
6166
-O zz_generated.deepcopy \
6267
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt \

‎pkg/apis/config/feature_flags.go

+34-7
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,12 @@ const (
7878
DefaultSendCloudEventsForRuns = false
7979
// DefaultEmbeddedStatus is the default value for "embedded-status".
8080
DefaultEmbeddedStatus = MinimalEmbeddedStatus
81-
// DefaultEnableSpire is the default value for "enable-spire".
82-
DefaultEnableSpire = false
81+
// EnforceNonfalsifiabilityWithSpire is the value used for "enable-nonfalsifiability" when SPIRE is used to enable non-falsifiability.
82+
EnforceNonfalsifiabilityWithSpire = "spire"
83+
// EnforceNonfalsifiabilityNone is the value used for "enable-nonfalsifiability" when non-falsifiability is not enabled.
84+
EnforceNonfalsifiabilityNone = ""
85+
// DefaultEnforceNonfalsifiability is the default value for "enforce-nonfalsifiability".
86+
DefaultEnforceNonfalsifiability = EnforceNonfalsifiabilityNone
8387
// DefaultResourceVerificationMode is the default value for "resource-verification-mode".
8488
DefaultResourceVerificationMode = SkipResourceVerificationMode
8589
// DefaultEnableProvenanceInStatus is the default value for "enable-provenance-status".
@@ -100,7 +104,7 @@ const (
100104
enableAPIFields = "enable-api-fields"
101105
sendCloudEventsForRuns = "send-cloudevents-for-runs"
102106
embeddedStatus = "embedded-status"
103-
enableSpire = "enable-spire"
107+
enforceNonfalsifiability = "enforce-nonfalsifiability"
104108
verificationMode = "resource-verification-mode"
105109
enableProvenanceInStatus = "enable-provenance-in-status"
106110
resultExtractionMethod = "results-from"
@@ -121,7 +125,7 @@ type FeatureFlags struct {
121125
SendCloudEventsForRuns bool
122126
AwaitSidecarReadiness bool
123127
EmbeddedStatus string
124-
EnableSpire bool
128+
EnforceNonfalsifiability string
125129
ResourceVerificationMode string
126130
EnableProvenanceInStatus bool
127131
ResultExtractionMethod string
@@ -138,6 +142,22 @@ func GetFeatureFlagsConfigName() string {
138142
return "feature-flags"
139143
}
140144

145+
func getEnforceNonfalsifiabilityFeature(cfgMap map[string]string) (string, error) {
146+
var mapValue struct{}
147+
var acceptedValues = map[string]struct{}{
148+
EnforceNonfalsifiabilityNone: mapValue,
149+
EnforceNonfalsifiabilityWithSpire: mapValue,
150+
}
151+
var value = DefaultEnforceNonfalsifiability
152+
if cfg, ok := cfgMap[enforceNonfalsifiability]; ok {
153+
value = strings.ToLower(cfg)
154+
}
155+
if _, ok := acceptedValues[value]; !ok {
156+
return DefaultEnforceNonfalsifiability, fmt.Errorf("invalid value for feature flag %q: %q", enforceNonfalsifiability, value)
157+
}
158+
return value, nil
159+
}
160+
141161
// NewFeatureFlagsFromMap returns a Config given a map corresponding to a ConfigMap
142162
func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
143163
setFeature := func(key string, defaultValue bool, feature *bool) error {
@@ -202,13 +222,20 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
202222
// defeat the purpose of having a single shared gate for all alpha features.
203223
if tc.EnableAPIFields == AlphaAPIFields {
204224
tc.EnableTektonOCIBundles = true
205-
tc.EnableSpire = true
225+
// Only consider SPIRE if alpha is on.
226+
enforceNonfalsifiabilityValue, err := getEnforceNonfalsifiabilityFeature(cfgMap)
227+
if err != nil {
228+
return nil, err
229+
}
230+
tc.EnforceNonfalsifiability = enforceNonfalsifiabilityValue
206231
} else {
207232
if err := setFeature(enableTektonOCIBundles, DefaultEnableTektonOciBundles, &tc.EnableTektonOCIBundles); err != nil {
208233
return nil, err
209234
}
210-
if err := setFeature(enableSpire, DefaultEnableSpire, &tc.EnableSpire); err != nil {
211-
return nil, err
235+
// Do not enable any form of non-falsifiability enforcement in non-alpha mode.
236+
tc.EnforceNonfalsifiability = EnforceNonfalsifiabilityNone
237+
if enforceNonfalsifiabilityValue, err := getEnforceNonfalsifiabilityFeature(cfgMap); err != nil || enforceNonfalsifiabilityValue != DefaultEnforceNonfalsifiability {
238+
return nil, fmt.Errorf("%q can be set to non-default values (%q) only in alpha", enforceNonfalsifiability, enforceNonfalsifiabilityValue)
212239
}
213240
}
214241
return &tc, nil

‎pkg/apis/config/feature_flags_test.go

+13-9
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
6666
EnableAPIFields: "alpha",
6767
SendCloudEventsForRuns: true,
6868
EmbeddedStatus: "both",
69-
EnableSpire: true,
69+
EnforceNonfalsifiability: "spire",
7070
ResourceVerificationMode: "enforce",
7171
EnableProvenanceInStatus: true,
7272
ResultExtractionMethod: "termination-message",
@@ -80,9 +80,8 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
8080
EnableAPIFields: "alpha",
8181
// These are prescribed as true by enabling "alpha" API fields, even
8282
// if the submitted text value is "false".
83-
EnableTektonOCIBundles: true,
84-
EnableSpire: true,
85-
83+
EnableTektonOCIBundles: true,
84+
EnforceNonfalsifiability: "",
8685
DisableAffinityAssistant: config.DefaultDisableAffinityAssistant,
8786
DisableCredsInit: config.DefaultDisableCredsInit,
8887
RunningInEnvWithInjectedSidecars: config.DefaultRunningInEnvWithInjectedSidecars,
@@ -137,17 +136,18 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
137136
},
138137
{
139138
expectedConfig: &config.FeatureFlags{
140-
EnableAPIFields: "stable",
141-
EmbeddedStatus: config.DefaultEmbeddedStatus,
142-
EnableSpire: true,
139+
EnableAPIFields: "alpha",
140+
EmbeddedStatus: "minimal",
141+
EnforceNonfalsifiability: "spire",
142+
EnableTektonOCIBundles: true,
143143
ResourceVerificationMode: config.DefaultResourceVerificationMode,
144144
RunningInEnvWithInjectedSidecars: config.DefaultRunningInEnvWithInjectedSidecars,
145145
AwaitSidecarReadiness: config.DefaultAwaitSidecarReadiness,
146146
ResultExtractionMethod: config.DefaultResultExtractionMethod,
147147
MaxResultSize: config.DefaultMaxResultSize,
148148
CustomTaskVersion: config.DefaultCustomTaskVersion,
149149
},
150-
fileName: "feature-flags-enable-spire",
150+
fileName: "feature-flags-enforce-nonfalsifiability-spire",
151151
},
152152
{
153153
expectedConfig: &config.FeatureFlags{
@@ -185,7 +185,7 @@ func TestNewFeatureFlagsFromEmptyConfigMap(t *testing.T) {
185185
EnableAPIFields: config.DefaultEnableAPIFields,
186186
SendCloudEventsForRuns: config.DefaultSendCloudEventsForRuns,
187187
EmbeddedStatus: config.DefaultEmbeddedStatus,
188-
EnableSpire: config.DefaultEnableSpire,
188+
EnforceNonfalsifiability: config.DefaultEnforceNonfalsifiability,
189189
ResourceVerificationMode: config.DefaultResourceVerificationMode,
190190
EnableProvenanceInStatus: config.DefaultEnableProvenanceInStatus,
191191
ResultExtractionMethod: config.DefaultResultExtractionMethod,
@@ -241,6 +241,10 @@ func TestNewFeatureFlagsConfigMapErrors(t *testing.T) {
241241
fileName: "feature-flags-invalid-max-result-size-bad-value",
242242
}, {
243243
fileName: "feature-flags-invalid-custom-task-version",
244+
}, {
245+
fileName: "feature-flags-enforce-nonfalsifiability-bad-flag",
246+
}, {
247+
fileName: "feature-flags-spire-with-stable",
244248
}} {
245249
t.Run(tc.fileName, func(t *testing.T) {
246250
cm := test.ConfigMapFromTestFile(t, tc.fileName)

0 commit comments

Comments
 (0)