Skip to content

Commit ad4e392

Browse files
authored
Merge pull request #3186 from simonbaird/keyless-params-task-redo
The task looks in a ConfigMap containing Konflux cluster config related to keyless signing. The idea is to use the task results as input params to the Conforma task so it can perform keyless signature verification using the correct params. Ref: https://redhat.atlassian.net/browse/EC-1695 Note: This was split out from #3171 which has been blocked on some unrelated acceptance test problems because I was doing too much in the one PR. This PR has the task only, so should be green and ready to merge.
2 parents 7ebac6e + ea67e32 commit ad4e392

12 files changed

Lines changed: 642 additions & 4 deletions

File tree

.github/workflows/release.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,18 @@ jobs:
148148
env:
149149
TASK_REPO: quay.io/conforma/tekton-task
150150
IMAGE_REPO: quay.io/conforma/cli
151-
TASKS: "tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml"
151+
TASKS: "tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml tasks/collect-keyless-params/0.1/collect-keyless-params.yaml"
152152
run: make task-bundle-snapshot TASK_REPO=$TASK_REPO TASK_TAG=$TAG ADD_TASK_TAG="$TAG_TIMESTAMP snapshot" TASKS=<( yq e ".spec.steps[].image? = \"$IMAGE_REPO:$TAG\"" $TASKS | yq 'select(. != null)')
153153

154154
- name: Registry login (quay.io/enterprise-contract)
155155
run: podman login -u ${{ secrets.BUNDLE_PUSH_USER_EC }} -p ${{ secrets.BUNDLE_PUSH_PASS_EC }} quay.io
156156

157+
# This repo is deprecated and probably no one uses it any more. At some point we should stop updating it.
157158
- name: Create and push the tekton bundle (quay.io/enterprise-contract/ec-task-bundle)
158159
env:
159160
TASK_REPO: quay.io/enterprise-contract/ec-task-bundle
160161
IMAGE_REPO: quay.io/enterprise-contract/ec-cli
161-
TASKS: "tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml"
162+
TASKS: "tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml tasks/collect-keyless-params/0.1/collect-keyless-params.yaml"
162163
run: make task-bundle-snapshot TASK_REPO=$TASK_REPO TASK_TAG=$TAG ADD_TASK_TAG="$TAG_TIMESTAMP" TASKS=<( yq e ".spec.steps[].image? = \"$IMAGE_REPO:$TAG\"" $TASKS | yq 'select(. != null)')
163164

164165
- name: Download statistics

.tekton/cli-main-pull-request.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,10 @@ spec:
298298
- name: IMAGE
299299
value: $(params.output-image).bundle
300300
- name: CONTEXT
301-
value: tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
301+
value: >-
302+
tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml
303+
tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
304+
tasks/collect-keyless-params/0.1/collect-keyless-params.yaml
302305
- name: STEPS_IMAGE
303306
value: $(params.bundle-cli-ref-repo)@$(tasks.build-image-index.results.IMAGE_DIGEST)
304307
- name: URL

.tekton/cli-main-push.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,10 @@ spec:
300300
- name: IMAGE
301301
value: $(params.output-image).bundle
302302
- name: CONTEXT
303-
value: tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
303+
value: >-
304+
tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml
305+
tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
306+
tasks/collect-keyless-params/0.1/collect-keyless-params.yaml
304307
- name: STEPS_IMAGE
305308
value: $(params.bundle-cli-ref-repo)@$(tasks.build-image-index.results.IMAGE_DIGEST)
306309
- name: URL

acceptance/kubernetes/kind/kubernetes.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import (
3232
pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
3333
tekton "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1"
3434
v1 "k8s.io/api/core/v1"
35+
rbacv1 "k8s.io/api/rbac/v1"
36+
apierrors "k8s.io/apimachinery/pkg/api/errors"
3537
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3638
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3739
"k8s.io/apimachinery/pkg/runtime"
@@ -189,6 +191,137 @@ func (k *kindCluster) CreateNamedSnapshot(ctx context.Context, name string, spec
189191
return k.createSnapshot(ctx, snapshot)
190192
}
191193

194+
// CreateConfigMap creates a ConfigMap with the given name and namespace with the provided content
195+
// Also creates necessary RBAC permissions for cross-namespace access
196+
func (k *kindCluster) CreateConfigMap(ctx context.Context, name, namespace, content string) error {
197+
var data map[string]string
198+
199+
// Parse JSON content and extract individual fields as ConfigMap data keys
200+
if strings.HasPrefix(strings.TrimSpace(content), "{") {
201+
// Parse JSON content
202+
var jsonData map[string]interface{}
203+
if err := json.Unmarshal([]byte(content), &jsonData); err != nil {
204+
return fmt.Errorf("failed to parse JSON content: %w", err)
205+
}
206+
207+
// Convert to string map for ConfigMap data
208+
data = make(map[string]string)
209+
for key, value := range jsonData {
210+
if value != nil {
211+
data[key] = fmt.Sprintf("%v", value)
212+
}
213+
}
214+
} else {
215+
// For non-JSON content, store as-is under a single key
216+
data = map[string]string{
217+
"content": content,
218+
}
219+
}
220+
221+
configMap := &v1.ConfigMap{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Name: name,
224+
Namespace: namespace,
225+
},
226+
Data: data,
227+
}
228+
229+
// Create the ConfigMap (or update if it already exists)
230+
if _, err := k.client.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
231+
if apierrors.IsAlreadyExists(err) {
232+
// ConfigMap exists, so get the existing one to retrieve its ResourceVersion
233+
existing, err := k.client.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
234+
if err != nil {
235+
return fmt.Errorf("failed to get existing ConfigMap: %w", err)
236+
}
237+
// Set the ResourceVersion from the existing ConfigMap
238+
configMap.ResourceVersion = existing.ResourceVersion
239+
// Now update with the proper ResourceVersion
240+
if _, err := k.client.CoreV1().ConfigMaps(namespace).Update(ctx, configMap, metav1.UpdateOptions{}); err != nil {
241+
return fmt.Errorf("failed to update existing ConfigMap: %w", err)
242+
}
243+
} else {
244+
return err
245+
}
246+
}
247+
248+
// Create RBAC permissions for cross-namespace ConfigMap access
249+
// This allows any service account to read ConfigMaps from any namespace
250+
if err := k.ensureConfigMapRBAC(ctx); err != nil {
251+
return fmt.Errorf("failed to create RBAC permissions: %w", err)
252+
}
253+
254+
return nil
255+
}
256+
257+
// ensureConfigMapRBAC creates necessary RBAC permissions for ConfigMap access across namespaces
258+
func (k *kindCluster) ensureConfigMapRBAC(ctx context.Context) error {
259+
// Create ClusterRole for ConfigMap reading (idempotent)
260+
clusterRole := &rbacv1.ClusterRole{
261+
ObjectMeta: metav1.ObjectMeta{
262+
Name: "acceptance-configmap-reader",
263+
},
264+
Rules: []rbacv1.PolicyRule{
265+
{
266+
APIGroups: []string{""},
267+
Resources: []string{"configmaps"},
268+
Verbs: []string{"get", "list"},
269+
},
270+
},
271+
}
272+
273+
if _, err := k.client.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{}); err != nil {
274+
// Ignore error if ClusterRole already exists
275+
if !strings.Contains(err.Error(), "already exists") {
276+
return fmt.Errorf("failed to create ClusterRole: %w", err)
277+
}
278+
}
279+
280+
// Create ClusterRoleBinding for all service accounts (idempotent)
281+
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
282+
ObjectMeta: metav1.ObjectMeta{
283+
Name: "acceptance-configmap-reader-binding",
284+
},
285+
RoleRef: rbacv1.RoleRef{
286+
APIGroup: "rbac.authorization.k8s.io",
287+
Kind: "ClusterRole",
288+
Name: "acceptance-configmap-reader",
289+
},
290+
Subjects: []rbacv1.Subject{
291+
{
292+
Kind: "Group",
293+
Name: "system:serviceaccounts",
294+
APIGroup: "rbac.authorization.k8s.io",
295+
},
296+
},
297+
}
298+
299+
if _, err := k.client.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{}); err != nil {
300+
// Ignore error if ClusterRoleBinding already exists
301+
if !strings.Contains(err.Error(), "already exists") {
302+
return fmt.Errorf("failed to create ClusterRoleBinding: %w", err)
303+
}
304+
}
305+
306+
return nil
307+
}
308+
309+
// CreateNamedNamespace creates a namespace with the specified name
310+
func (k *kindCluster) CreateNamedNamespace(ctx context.Context, name string) error {
311+
_, err := k.client.CoreV1().Namespaces().Create(ctx, &v1.Namespace{
312+
ObjectMeta: metav1.ObjectMeta{
313+
Name: name,
314+
},
315+
}, metav1.CreateOptions{})
316+
317+
// Ignore error if namespace already exists
318+
if err != nil && strings.Contains(err.Error(), "already exists") {
319+
return nil
320+
}
321+
322+
return err
323+
}
324+
192325
// CreateNamespace creates a randomly-named namespace for the test to execute in
193326
// and stores it in the test context
194327
func (k *kindCluster) CreateNamespace(ctx context.Context) (context.Context, error) {

acceptance/kubernetes/kubernetes.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,26 @@ func createNamedSnapshot(ctx context.Context, name string, specification *godog.
174174
return c.cluster.CreateNamedSnapshot(ctx, name, specification.Content)
175175
}
176176

177+
func createConfigMap(ctx context.Context, name, namespace string, content *godog.DocString) error {
178+
c := testenv.FetchState[ClusterState](ctx)
179+
180+
if err := mustBeUp(ctx, *c); err != nil {
181+
return err
182+
}
183+
184+
return c.cluster.CreateConfigMap(ctx, name, namespace, content.Content)
185+
}
186+
187+
func createNamedNamespace(ctx context.Context, name string) error {
188+
c := testenv.FetchState[ClusterState](ctx)
189+
190+
if err := mustBeUp(ctx, *c); err != nil {
191+
return err
192+
}
193+
194+
return c.cluster.CreateNamedNamespace(ctx, name)
195+
}
196+
177197
func createNamedSnapshotWithManyComponents(ctx context.Context, name string, amount int, key string) (context.Context, error) {
178198
c := testenv.FetchState[ClusterState](ctx)
179199

@@ -493,6 +513,8 @@ func AddStepsTo(sc *godog.ScenarioContext) {
493513
sc.Step(`^the task results should match the snapshot$`, taskResultsShouldMatchTheSnapshot)
494514
sc.Step(`^the task result "([^"]*)" should equal "([^"]*)"$`, taskResultShouldEqual)
495515
sc.Step(`^policy configuration named "([^"]*)" with (\d+) policy sources from "([^"]*)"(?:, patched with)$`, createNamedPolicyWithManySources)
516+
sc.Step(`^a namespace named "([^"]*)" exists$`, createNamedNamespace)
517+
sc.Step(`^a ConfigMap "([^"]*)" in namespace "([^"]*)" with content:$`, createConfigMap)
496518
// stop usage of the cluster once a test is done, godog will call this
497519
// function on failure and on the last step, so more than once if the
498520
// failure is not on the last step and once if there was no failure or the

acceptance/kubernetes/stub/stub.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ func (s stubCluster) CreateNamedSnapshot(ctx context.Context, name string, speci
135135
}))).WithHeaders(map[string]string{"Content-Type": "application/json"}).WithStatus(200)))
136136
}
137137

138+
func (s stubCluster) CreateConfigMap(_ context.Context, _, _, _ string) error {
139+
return errors.New("ConfigMap creation is not supported when using the stub Kubernetes")
140+
}
141+
142+
func (s stubCluster) CreateNamedNamespace(_ context.Context, _ string) error {
143+
return errors.New("Named namespace creation is not supported when using the stub Kubernetes")
144+
}
145+
138146
func (s stubCluster) CreatePolicy(_ context.Context, _ string) error {
139147
return errors.New("use `Given policy configuration named \"<name>\" with specification` when using the stub Kubernetes")
140148
}

acceptance/kubernetes/types/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type Cluster interface {
3131
AwaitUntilTaskIsDone(context.Context) (bool, error)
3232
TaskInfo(context.Context) (*TaskInfo, error)
3333
CreateNamedSnapshot(context.Context, string, string) error
34+
CreateNamedNamespace(context.Context, string) error
35+
CreateConfigMap(context.Context, string, string, string) error
3436
Registry(context.Context) (string, error)
3537
BuildSnapshotArtifact(context.Context, string) (context.Context, error)
3638
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
= collect-keyless-params
2+
3+
Version: 0.1
4+
5+
== Synopsis
6+
7+
Tekton task to collect Konflux configuration parameters related to
8+
keyless signing using cosign. The task attempts to read the "cluster-config"
9+
ConfigMap in the "konflux-info" namespace to extract signing parameters.
10+
11+
In case the ConfigMap is not found, the task will output empty strings for all parameters,
12+
allowing the pipeline to continue without signing parameters.
13+
14+
15+
== Params
16+
[horizontal]
17+
18+
*configMapName* (`string`):: The name of the ConfigMap to read signing parameters from
19+
+
20+
*Default*: `cluster-config`
21+
*configMapNamespace* (`string`):: The namespace where the ConfigMap is located
22+
+
23+
*Default*: `konflux-info`
24+
25+
== Results
26+
27+
[horizontal]
28+
*keylessSigningEnabled*:: A flag indicating whether keyless signing is enabled based on the presence of signing parameters.
29+
30+
*defaultOIDCIssuer*:: A default OIDC issuer URL to be used for signing.
31+
32+
*buildIdentityRegexp*:: A regular expression to extract build identity from the OIDC token claims, if applicable.
33+
34+
*tektonChainsIdentity*:: The Tekton Chains identity from the OIDC token claims, if applicable.
35+
36+
*fulcioUrl*:: The URL of the Fulcio certificate authority.
37+
38+
*rekorUrl*:: The URL of the Rekor transparency log.
39+
40+
*tufUrl*:: The URL of the TUF repository.
41+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
* xref:tasks.adoc[Tekton Tasks]
2+
** xref:collect-keyless-params.adoc[collect-keyless-params]
23
** xref:verify-conforma-konflux-ta.adoc[verify-conforma-konflux-ta]
34
** xref:verify-conforma-vsa-release-ta.adoc[verify-conforma-vsa-release-ta]
45
** xref:verify-enterprise-contract.adoc[verify-enterprise-contract]

features/__snapshots__/task_validate_image.snap

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,83 @@ true
453453
"TEST_OUTPUT": "{\"timestamp\":\"${TIMESTAMP}\",\"namespace\":\"\",\"successes\":5,\"failures\":0,\"warnings\":0,\"result\":\"SUCCESS\"}\n"
454454
}
455455
---
456+
457+
[Collect keyless signing parameters from ConfigMap:collect-signing-params - 1]
458+
Reading ConfigMap konflux-info/cluster-config
459+
ConfigMap found, extracting keyless signing parameters
460+
results.keylessSigningEnabled: true
461+
results.defaultOIDCIssuer: https://kubernetes.default.svc
462+
results.buildIdentityRegexp: ^https://kubernetes.io/namespaces/[a-z0-9-]+-tenant/serviceaccounts/build-pipeline-[a-z0-9-]+$
463+
results.tektonChainsIdentity: https://kubernetes.io/namespaces/openshift-pipelines/serviceaccounts/tekton-chains-controller
464+
results.fulcioUrl: https://fulcio.internal.svc
465+
results.rekorUrl: https://rekor.internal.svc
466+
results.tufUrl: https://tuf.internal.svc
467+
468+
---
469+
470+
[Collect keyless signing parameters from ConfigMap with external url fallback:collect-signing-params - 1]
471+
Reading ConfigMap konflux-info/cluster-config-1
472+
ConfigMap found, extracting keyless signing parameters
473+
results.keylessSigningEnabled: true
474+
results.defaultOIDCIssuer: https://kubernetes.default.svc
475+
results.buildIdentityRegexp: ^https://kubernetes.io/namespaces/[a-z0-9-]+-tenant/serviceaccounts/build-pipeline-[a-z0-9-]+$
476+
results.tektonChainsIdentity: https://kubernetes.io/namespaces/openshift-pipelines/serviceaccounts/tekton-chains-controller
477+
results.fulcioUrl: https://fulcio.example.com
478+
results.rekorUrl: https://rekor.example.com
479+
results.tufUrl: https://tuf.example.com
480+
481+
---
482+
483+
[Collect keyless signing parameters from ConfigMap with keyless signing disabled:collect-signing-params - 1]
484+
Reading ConfigMap konflux-info/cluster-config-2
485+
ConfigMap found, extracting keyless signing parameters
486+
enableKeylessSigning is not set, using default empty values
487+
results.keylessSigningEnabled: false
488+
results.defaultOIDCIssuer:
489+
results.buildIdentityRegexp:
490+
results.tektonChainsIdentity:
491+
results.fulcioUrl:
492+
results.rekorUrl:
493+
results.tufUrl:
494+
495+
---
496+
497+
[Collect keyless signing parameters when there is a malformed ConfigMap:collect-signing-params - 1]
498+
Reading ConfigMap konflux-info/cluster-config-3
499+
ConfigMap found, extracting keyless signing parameters
500+
enableKeylessSigning is not set, using default empty values
501+
results.keylessSigningEnabled: false
502+
results.defaultOIDCIssuer:
503+
results.buildIdentityRegexp:
504+
results.tektonChainsIdentity:
505+
results.fulcioUrl:
506+
results.rekorUrl:
507+
results.tufUrl:
508+
509+
---
510+
511+
[Collect keyless signing parameters when the ConfigMap does not exist:collect-signing-params - 1]
512+
Reading ConfigMap konflux-info/doesnt-exist-config
513+
ConfigMap not found, using default empty values
514+
results.keylessSigningEnabled: false
515+
results.defaultOIDCIssuer:
516+
results.buildIdentityRegexp:
517+
results.tektonChainsIdentity:
518+
results.fulcioUrl:
519+
results.rekorUrl:
520+
results.tufUrl:
521+
522+
---
523+
524+
[Collect keyless signing parameters when the namespace does not exist:collect-signing-params - 1]
525+
Reading ConfigMap doesnt-exist-namespace/whatever
526+
ConfigMap not found, using default empty values
527+
results.keylessSigningEnabled: false
528+
results.defaultOIDCIssuer:
529+
results.buildIdentityRegexp:
530+
results.tektonChainsIdentity:
531+
results.fulcioUrl:
532+
results.rekorUrl:
533+
results.tufUrl:
534+
535+
---

0 commit comments

Comments
 (0)