Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,17 +148,18 @@ jobs:
env:
TASK_REPO: quay.io/conforma/tekton-task
IMAGE_REPO: quay.io/conforma/cli
TASKS: "tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml"
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"
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)')

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

# This repo is deprecated and probably no one uses it any more. At some point we should stop updating it.
- name: Create and push the tekton bundle (quay.io/enterprise-contract/ec-task-bundle)
env:
TASK_REPO: quay.io/enterprise-contract/ec-task-bundle
IMAGE_REPO: quay.io/enterprise-contract/ec-cli
TASKS: "tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml"
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"
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)')

- name: Download statistics
Expand Down
5 changes: 4 additions & 1 deletion .tekton/cli-main-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,10 @@ spec:
- name: IMAGE
value: $(params.output-image).bundle
- name: CONTEXT
value: tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
value: >-
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
- name: STEPS_IMAGE
value: $(params.bundle-cli-ref-repo)@$(tasks.build-image-index.results.IMAGE_DIGEST)
- name: URL
Expand Down
5 changes: 4 additions & 1 deletion .tekton/cli-main-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,10 @@ spec:
- name: IMAGE
value: $(params.output-image).bundle
- name: CONTEXT
value: tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
value: >-
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
- name: STEPS_IMAGE
value: $(params.bundle-cli-ref-repo)@$(tasks.build-image-index.results.IMAGE_DIGEST)
- name: URL
Expand Down
133 changes: 133 additions & 0 deletions acceptance/kubernetes/kind/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
tekton "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -189,6 +191,137 @@ func (k *kindCluster) CreateNamedSnapshot(ctx context.Context, name string, spec
return k.createSnapshot(ctx, snapshot)
}

// CreateConfigMap creates a ConfigMap with the given name and namespace with the provided content
// Also creates necessary RBAC permissions for cross-namespace access
func (k *kindCluster) CreateConfigMap(ctx context.Context, name, namespace, content string) error {
var data map[string]string

// Parse JSON content and extract individual fields as ConfigMap data keys
if strings.HasPrefix(strings.TrimSpace(content), "{") {
// Parse JSON content
var jsonData map[string]interface{}
if err := json.Unmarshal([]byte(content), &jsonData); err != nil {
return fmt.Errorf("failed to parse JSON content: %w", err)
}

// Convert to string map for ConfigMap data
data = make(map[string]string)
for key, value := range jsonData {
if value != nil {
data[key] = fmt.Sprintf("%v", value)
}
}
} else {
// For non-JSON content, store as-is under a single key
data = map[string]string{
"content": content,
}
}

configMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Data: data,
}

// Create the ConfigMap (or update if it already exists)
if _, err := k.client.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
if apierrors.IsAlreadyExists(err) {
// ConfigMap exists, so get the existing one to retrieve its ResourceVersion
existing, err := k.client.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get existing ConfigMap: %w", err)
}
// Set the ResourceVersion from the existing ConfigMap
configMap.ResourceVersion = existing.ResourceVersion
// Now update with the proper ResourceVersion
if _, err := k.client.CoreV1().ConfigMaps(namespace).Update(ctx, configMap, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("failed to update existing ConfigMap: %w", err)
}
} else {
return err
}
}

// Create RBAC permissions for cross-namespace ConfigMap access
// This allows any service account to read ConfigMaps from any namespace
if err := k.ensureConfigMapRBAC(ctx); err != nil {
return fmt.Errorf("failed to create RBAC permissions: %w", err)
}

return nil
}

// ensureConfigMapRBAC creates necessary RBAC permissions for ConfigMap access across namespaces
func (k *kindCluster) ensureConfigMapRBAC(ctx context.Context) error {
// Create ClusterRole for ConfigMap reading (idempotent)
clusterRole := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "acceptance-configmap-reader",
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"configmaps"},
Verbs: []string{"get", "list"},
},
},
}

if _, err := k.client.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{}); err != nil {
// Ignore error if ClusterRole already exists
if !strings.Contains(err.Error(), "already exists") {
return fmt.Errorf("failed to create ClusterRole: %w", err)
}
}

// Create ClusterRoleBinding for all service accounts (idempotent)
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "acceptance-configmap-reader-binding",
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "acceptance-configmap-reader",
},
Subjects: []rbacv1.Subject{
{
Kind: "Group",
Name: "system:serviceaccounts",
APIGroup: "rbac.authorization.k8s.io",
},
},
}

if _, err := k.client.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{}); err != nil {
// Ignore error if ClusterRoleBinding already exists
if !strings.Contains(err.Error(), "already exists") {
return fmt.Errorf("failed to create ClusterRoleBinding: %w", err)
}
}

return nil
}

// CreateNamedNamespace creates a namespace with the specified name
func (k *kindCluster) CreateNamedNamespace(ctx context.Context, name string) error {
_, err := k.client.CoreV1().Namespaces().Create(ctx, &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}, metav1.CreateOptions{})

// Ignore error if namespace already exists
if err != nil && strings.Contains(err.Error(), "already exists") {
return nil
}

return err
}

// CreateNamespace creates a randomly-named namespace for the test to execute in
// and stores it in the test context
func (k *kindCluster) CreateNamespace(ctx context.Context) (context.Context, error) {
Expand Down
22 changes: 22 additions & 0 deletions acceptance/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,26 @@ func createNamedSnapshot(ctx context.Context, name string, specification *godog.
return c.cluster.CreateNamedSnapshot(ctx, name, specification.Content)
}

func createConfigMap(ctx context.Context, name, namespace string, content *godog.DocString) error {
c := testenv.FetchState[ClusterState](ctx)

if err := mustBeUp(ctx, *c); err != nil {
return err
}

return c.cluster.CreateConfigMap(ctx, name, namespace, content.Content)
}

func createNamedNamespace(ctx context.Context, name string) error {
c := testenv.FetchState[ClusterState](ctx)

if err := mustBeUp(ctx, *c); err != nil {
return err
}

return c.cluster.CreateNamedNamespace(ctx, name)
}

func createNamedSnapshotWithManyComponents(ctx context.Context, name string, amount int, key string) (context.Context, error) {
c := testenv.FetchState[ClusterState](ctx)

Expand Down Expand Up @@ -493,6 +513,8 @@ func AddStepsTo(sc *godog.ScenarioContext) {
sc.Step(`^the task results should match the snapshot$`, taskResultsShouldMatchTheSnapshot)
sc.Step(`^the task result "([^"]*)" should equal "([^"]*)"$`, taskResultShouldEqual)
sc.Step(`^policy configuration named "([^"]*)" with (\d+) policy sources from "([^"]*)"(?:, patched with)$`, createNamedPolicyWithManySources)
sc.Step(`^a namespace named "([^"]*)" exists$`, createNamedNamespace)
sc.Step(`^a ConfigMap "([^"]*)" in namespace "([^"]*)" with content:$`, createConfigMap)
// stop usage of the cluster once a test is done, godog will call this
// function on failure and on the last step, so more than once if the
// failure is not on the last step and once if there was no failure or the
Expand Down
8 changes: 8 additions & 0 deletions acceptance/kubernetes/stub/stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ func (s stubCluster) CreateNamedSnapshot(ctx context.Context, name string, speci
}))).WithHeaders(map[string]string{"Content-Type": "application/json"}).WithStatus(200)))
}

func (s stubCluster) CreateConfigMap(_ context.Context, _, _, _ string) error {
return errors.New("ConfigMap creation is not supported when using the stub Kubernetes")
}

func (s stubCluster) CreateNamedNamespace(_ context.Context, _ string) error {
return errors.New("Named namespace creation is not supported when using the stub Kubernetes")
}

func (s stubCluster) CreatePolicy(_ context.Context, _ string) error {
return errors.New("use `Given policy configuration named \"<name>\" with specification` when using the stub Kubernetes")
}
Expand Down
2 changes: 2 additions & 0 deletions acceptance/kubernetes/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Cluster interface {
AwaitUntilTaskIsDone(context.Context) (bool, error)
TaskInfo(context.Context) (*TaskInfo, error)
CreateNamedSnapshot(context.Context, string, string) error
CreateNamedNamespace(context.Context, string) error
CreateConfigMap(context.Context, string, string, string) error
Registry(context.Context) (string, error)
BuildSnapshotArtifact(context.Context, string) (context.Context, error)
}
Expand Down
41 changes: 41 additions & 0 deletions docs/modules/ROOT/pages/collect-keyless-params.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
= collect-keyless-params

Version: 0.1

== Synopsis

Tekton task to collect Konflux configuration parameters related to
keyless signing using cosign. The task attempts to read the "cluster-config"
ConfigMap in the "konflux-info" namespace to extract signing parameters.

In case the ConfigMap is not found, the task will output empty strings for all parameters,
allowing the pipeline to continue without signing parameters.


== Params
[horizontal]

*configMapName* (`string`):: The name of the ConfigMap to read signing parameters from
+
*Default*: `cluster-config`
*configMapNamespace* (`string`):: The namespace where the ConfigMap is located
+
*Default*: `konflux-info`

== Results

[horizontal]
*defaultOIDCIssuer*:: A default OIDC issuer URL to be used for signing.

*rekorExternalUrl*:: The external URL of the Rekor transparency log.

*fulcioExternalUrl*:: The external URL of the Fulcio certificate authority.

*tufExternalUrl*:: The external URL of the TUF repository.

*buildIdentity*:: The build identity from the OIDC token claims, if applicable.

*buildIdentityRegexp*:: A regular expression to extract build identity from the OIDC token claims, if applicable.

*keylessSigningEnabled*:: A flag indicating whether keyless signing is enabled based on the presence of signing parameters.

1 change: 1 addition & 0 deletions docs/modules/ROOT/partials/tasks_nav.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
* xref:tasks.adoc[Tekton Tasks]
** xref:collect-keyless-params.adoc[collect-keyless-params]
** xref:verify-conforma-konflux-ta.adoc[verify-conforma-konflux-ta]
** xref:verify-conforma-vsa-release-ta.adoc[verify-conforma-vsa-release-ta]
** xref:verify-enterprise-contract.adoc[verify-enterprise-contract]
67 changes: 67 additions & 0 deletions features/__snapshots__/task_validate_image.snap
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,70 @@ true
"TEST_OUTPUT": "{\"timestamp\":\"${TIMESTAMP}\",\"namespace\":\"\",\"successes\":5,\"failures\":0,\"warnings\":0,\"result\":\"SUCCESS\"}\n"
}
---

[Collect keyless signing parameters from ConfigMap:collect-signing-params - 1]
Reading ConfigMap konflux-info/cluster-config
ConfigMap found, extracting keyless signing parameters
results.keylessSigningEnabled: true
results.defaultOIDCIssuer: https://kubernetes.default.svc.cluster.local
results.rekorExternalUrl: https://rekor.example.com
results.fulcioExternalUrl: https://fulcio.example.com
results.tufExternalUrl: https://tuf.example.com
results.buildIdentity: https://kubernetes.io/namespaces/openshift-pipelines/serviceaccounts/tekton-chains-controller
results.buildIdentityRegexp: ^https://konflux-ci.dev/.*$

---

[Collect keyless signing parameters from ConfigMap with keyless signing disabled:collect-signing-params - 1]
Reading ConfigMap konflux-info/cluster-config-2
ConfigMap found, extracting keyless signing parameters
enableKeylessSigning is not set, using default empty values
results.keylessSigningEnabled: false
results.defaultOIDCIssuer:
results.rekorExternalUrl:
results.fulcioExternalUrl:
results.tufExternalUrl:
results.buildIdentity:
results.buildIdentityRegexp:

---

[Collect keyless signing parameters when there is a malformed ConfigMap:collect-signing-params - 1]
Reading ConfigMap konflux-info/cluster-config-3
ConfigMap found, extracting keyless signing parameters
enableKeylessSigning is not set, using default empty values
results.keylessSigningEnabled: false
results.defaultOIDCIssuer:
results.rekorExternalUrl:
results.fulcioExternalUrl:
results.tufExternalUrl:
results.buildIdentity:
results.buildIdentityRegexp:

---

[Collect keyless signing parameters when the ConfigMap does not exist:collect-signing-params - 1]
Reading ConfigMap konflux-info/doesnt-exist-config
ConfigMap not found, using default empty values
results.keylessSigningEnabled: false
results.defaultOIDCIssuer:
results.rekorExternalUrl:
results.fulcioExternalUrl:
results.tufExternalUrl:
results.buildIdentity:
results.buildIdentityRegexp:

---

[Collect keyless signing parameters when the namespace does not exist:collect-signing-params - 1]
Reading ConfigMap doesnt-exist-namespace/whatever
ConfigMap not found, using default empty values
results.keylessSigningEnabled: false
results.defaultOIDCIssuer:
results.rekorExternalUrl:
results.fulcioExternalUrl:
results.tufExternalUrl:
results.buildIdentity:
results.buildIdentityRegexp:

---
Loading
Loading