Skip to content
Merged
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
8 changes: 5 additions & 3 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Supported keys include:
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :-------- |
| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3`, `slsa/v2alpha4` | `in-toto` |
| `artifacts.taskrun.storage` | The storage backend to store `TaskRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `TaskRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas`, `archivista` | `tekton` |
| `artifacts.taskrun.signer` | The signature backend to sign `TaskRun` payloads with. | `x509`, `kms` | `x509` |
| `artifacts.taskrun.signer` | The signature backend to sign `TaskRun` payloads with. Use `none` to disable signing while still storing provenance. | `x509`, `kms`, `none` | `x509` |

> NOTE:
>
Expand All @@ -35,7 +35,7 @@ Supported keys include:
| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------- | :-------- |
| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3`, `slsa/v2alpha4` | `in-toto` |
| `artifacts.pipelinerun.storage` | The storage backend to store `PipelineRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `PipelineRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas`, `archivista` | `tekton` |
| `artifacts.pipelinerun.signer` | The signature backend to sign `PipelineRun` payloads with. | `x509`, `kms` | `x509` |
| `artifacts.pipelinerun.signer` | The signature backend to sign `PipelineRun` payloads with. Use `none` to disable signing while still storing provenance. | `x509`, `kms`, `none` | `x509` |
| `artifacts.pipelinerun.enable-deep-inspection` | This boolean option will configure whether Chains should inspect child taskruns in order to capture inputs/outputs within a pipelinerun. `"false"` means that Chains only checks pipeline level results, whereas `"true"` means Chains inspects both pipeline level and task level results. | `"true"`, `"false"` | `"false"` |

> NOTE:
Expand All @@ -52,7 +52,9 @@ Supported keys include:
| :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :-------------- |
| `artifacts.oci.format` | The format to store `OCI` payloads in. | `simplesigning` | `simplesigning` |
| `artifacts.oci.storage` | The storage backend to store `OCI` signatures in. Multiple backends can be specified with comma-separated list ("oci,tekton"). To disable the `OCI` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `oci` |
| `artifacts.oci.signer` | The signature backend to sign `OCI` payloads with. | `x509`, `kms` | `x509` |
| `artifacts.oci.signer` | The signature backend to sign `OCI` payloads with. Use `none` to skip signing of OCI artifacts while still allowing provenance generation and attestation signing (see note below). | `x509`, `kms`, `none` | `x509` |

> Note: When `artifacts.oci.signer` is set to `none`, only OCI image *signing* is disabled; attestations are still generated and pushed as configured. To push attestations to registries, set `artifacts.taskrun.storage` and/or `artifacts.pipelinerun.storage` to include `oci`. Attestations will still be pushed to the same location determined by type hinting (IMAGE_URL/IMAGE_DIGEST results) or `storage.oci.repository` if configured.

### KMS Configuration

Expand Down
60 changes: 60 additions & 0 deletions pkg/chains/signing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,66 @@ func fakeAllBackends(backends []*mockBackend) map[string]storage.Backend {
return newBackends
}

func TestObjectSigner_Sign_OCIDisabled(t *testing.T) {
tests := []struct {
name string
config config.Config
expectOCIEnabled bool
expectPipelineEnabled bool
}{
{
name: "OCI signing enabled by default",
config: config.Config{
Artifacts: config.ArtifactConfigs{
OCI: config.Artifact{
Format: "simplesigning",
StorageBackend: sets.New[string]("mock"),
Signer: "x509",
},
PipelineRuns: config.Artifact{
Format: "slsa/v1",
StorageBackend: sets.New[string]("mock"),
Signer: "x509",
},
},
},
expectOCIEnabled: true,
expectPipelineEnabled: true,
},
{
name: "OCI signing disabled with none signer",
config: config.Config{
Artifacts: config.ArtifactConfigs{
OCI: config.Artifact{
Format: "simplesigning",
StorageBackend: sets.New[string]("mock"),
Signer: "none",
},
PipelineRuns: config.Artifact{
Format: "slsa/v1",
StorageBackend: sets.New[string]("mock"),
Signer: "x509",
},
},
},
expectOCIEnabled: false,
expectPipelineEnabled: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test direct artifact configuration - this is the core functionality
if tt.config.Artifacts.OCI.Enabled() != tt.expectOCIEnabled {
t.Errorf("Config OCI.Enabled() = %v, want %v", tt.config.Artifacts.OCI.Enabled(), tt.expectOCIEnabled)
}
if tt.config.Artifacts.PipelineRuns.Enabled() != tt.expectPipelineEnabled {
t.Errorf("Config PipelineRuns.Enabled() = %v, want %v", tt.config.Artifacts.PipelineRuns.Enabled(), tt.expectPipelineEnabled)
}
})
}
}

func setupMocks(rekor *mockRekor) func() {
oldRekor := getRekor
getRekor = func(_ string) (rekorClient, error) {
Expand Down
10 changes: 7 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ const (
)

func (artifact *Artifact) Enabled() bool {
// If signer is "none", signing is disabled
if artifact.Signer == "none" {
return false
}
return !(artifact.StorageBackend.Len() == 1 && artifact.StorageBackend.Has(""))
}

Expand Down Expand Up @@ -286,18 +290,18 @@ func NewConfigFromMap(data map[string]string) (*Config, error) {
// TaskRuns
asString(taskrunFormatKey, &cfg.Artifacts.TaskRuns.Format, "in-toto", "slsa/v1", "slsa/v2alpha3", "slsa/v2alpha4"),
asStringSet(taskrunStorageKey, &cfg.Artifacts.TaskRuns.StorageBackend, sets.New[string]("tekton", "oci", "gcs", "docdb", "grafeas", "kafka", "archivista")),
asString(taskrunSignerKey, &cfg.Artifacts.TaskRuns.Signer, "x509", "kms"),
asString(taskrunSignerKey, &cfg.Artifacts.TaskRuns.Signer, "x509", "kms", "none"),

// PipelineRuns
asString(pipelinerunFormatKey, &cfg.Artifacts.PipelineRuns.Format, "in-toto", "slsa/v1", "slsa/v2alpha3", "slsa/v2alpha4"),
asStringSet(pipelinerunStorageKey, &cfg.Artifacts.PipelineRuns.StorageBackend, sets.New[string]("tekton", "oci", "gcs", "docdb", "grafeas", "archivista")),
asString(pipelinerunSignerKey, &cfg.Artifacts.PipelineRuns.Signer, "x509", "kms"),
asString(pipelinerunSignerKey, &cfg.Artifacts.PipelineRuns.Signer, "x509", "kms", "none"),
asBool(pipelinerunEnableDeepInspectionKey, &cfg.Artifacts.PipelineRuns.DeepInspectionEnabled),

// OCI
asString(ociFormatKey, &cfg.Artifacts.OCI.Format, "simplesigning"),
asStringSet(ociStorageKey, &cfg.Artifacts.OCI.StorageBackend, sets.New[string]("tekton", "oci", "gcs", "docdb", "grafeas", "kafka", "archivista")),
asString(ociSignerKey, &cfg.Artifacts.OCI.Signer, "x509", "kms"),
asString(ociSignerKey, &cfg.Artifacts.OCI.Signer, "x509", "kms", "none"),

// PubSub - General
asString(pubsubProvider, &cfg.Storage.PubSub.Provider, "inmemory", "kafka"),
Expand Down
87 changes: 87 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright 2021 The Tekton Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"testing"

"k8s.io/apimachinery/pkg/util/sets"
)

func TestArtifact_Enabled(t *testing.T) {
tests := []struct {
name string
signer string
storageBackend sets.Set[string]
want bool
}{
{
name: "enabled by default with valid storage and x509 signer",
signer: "x509",
storageBackend: sets.New[string]("oci"),
want: true,
},
{
name: "enabled with kms signer",
signer: "kms",
storageBackend: sets.New[string]("oci"),
want: true,
},
{
name: "disabled when signer is none",
signer: "none",
storageBackend: sets.New[string]("oci"),
want: false,
},
{
name: "disabled when no storage backend",
signer: "x509",
storageBackend: sets.New[string](""),
want: false,
},
{
name: "disabled when signer none and no storage",
signer: "none",
storageBackend: sets.New[string](""),
want: false,
},
{
name: "enabled with multiple storage backends",
signer: "x509",
storageBackend: sets.New[string]("oci", "gcs"),
want: true,
},
{
name: "disabled with multiple storage backends but signer none",
signer: "none",
storageBackend: sets.New[string]("oci", "gcs"),
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
artifact := &Artifact{
Signer: tt.signer,
StorageBackend: tt.storageBackend,
}
if got := artifact.Enabled(); got != tt.want {
t.Errorf("Artifact.Enabled() = %v, want %v", got, tt.want)
}
})
}
}
102 changes: 102 additions & 0 deletions pkg/config/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,3 +620,105 @@ func TestParse(t *testing.T) {
})
}
}

func TestNewConfigFromMap_OCISignerNone(t *testing.T) {
tests := []struct {
name string
data map[string]string
wantEnabled bool
wantSigner string
}{
{
name: "OCI signing enabled by default with x509 signer",
data: map[string]string{},
wantEnabled: true,
wantSigner: "x509",
},
{
name: "OCI signing disabled when signer is none",
data: map[string]string{
"artifacts.oci.signer": "none",
},
wantEnabled: false,
wantSigner: "none",
},
{
name: "OCI signing enabled with x509 signer",
data: map[string]string{
"artifacts.oci.signer": "x509",
},
wantEnabled: true,
wantSigner: "x509",
},
{
name: "OCI signing enabled with kms signer",
data: map[string]string{
"artifacts.oci.signer": "kms",
},
wantEnabled: true,
wantSigner: "kms",
},
{
name: "OCI signing disabled with none signer even with other OCI configs",
data: map[string]string{
"artifacts.oci.format": "simplesigning",
"artifacts.oci.storage": "oci",
"artifacts.oci.signer": "none",
},
wantEnabled: false,
wantSigner: "none",
},
{
name: "TaskRun signing disabled with none signer",
data: map[string]string{
"artifacts.taskrun.signer": "none",
},
wantEnabled: false,
wantSigner: "none",
},
{
name: "PipelineRun signing disabled with none signer",
data: map[string]string{
"artifacts.pipelinerun.signer": "none",
},
wantEnabled: false,
wantSigner: "none",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewConfigFromMap(tt.data)
if err != nil {
t.Fatalf("NewConfigFromMap() error = %v", err)
}

// Check the signer based on what was configured
var actualSigner string
var actualEnabled bool

if _, ok := tt.data["artifacts.oci.signer"]; ok {
actualSigner = got.Artifacts.OCI.Signer
actualEnabled = got.Artifacts.OCI.Enabled()
} else if _, ok := tt.data["artifacts.taskrun.signer"]; ok {
actualSigner = got.Artifacts.TaskRuns.Signer
actualEnabled = got.Artifacts.TaskRuns.Enabled()
} else if _, ok := tt.data["artifacts.pipelinerun.signer"]; ok {
actualSigner = got.Artifacts.PipelineRuns.Signer
actualEnabled = got.Artifacts.PipelineRuns.Enabled()
} else {
// Default case - check OCI
actualSigner = got.Artifacts.OCI.Signer
actualEnabled = got.Artifacts.OCI.Enabled()
}

if actualSigner != tt.wantSigner {
t.Errorf("Signer = %v, want %v", actualSigner, tt.wantSigner)
}

if actualEnabled != tt.wantEnabled {
t.Errorf("Enabled() = %v, want %v", actualEnabled, tt.wantEnabled)
}
})
}
}
63 changes: 63 additions & 0 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,3 +931,66 @@ func TestVaultKMSSpire(t *testing.T) {
t.Fatal(err)
}
}

func TestDisableOCISigning(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)
c, ns, cleanup := setup(ctx, t, setupOpts{registry: true})
t.Cleanup(cleanup)

// Configure chains to disable OCI signing using "none" signer but keep attestation storage
resetConfig := setConfigMap(ctx, t, c, map[string]string{
"artifacts.oci.format": "simplesigning",
"artifacts.oci.storage": "oci",
"artifacts.oci.signer": "none", // Use "none" to disable OCI signing
"artifacts.taskrun.format": "slsa/v1",
"artifacts.taskrun.signer": "x509",
"artifacts.taskrun.storage": "oci",
"storage.oci.repository.insecure": "true",
})
t.Cleanup(resetConfig)
time.Sleep(3 * time.Second) // https://github.com/tektoncd/chains/issues/664

// create necessary resources
imageName := "chains-test-disable-signing"
image := fmt.Sprintf("%s/%s", c.internalRegistry, imageName)

task := kanikoTask(t, ns, image)
if _, err := c.PipelineClient.TektonV1().Tasks(ns).Create(ctx, task, metav1.CreateOptions{}); err != nil {
t.Fatalf("error creating task: %s", err)
}

tro := kanikoTaskRun(ns)
createdTro := tekton.CreateObject(t, ctx, c.PipelineClient, tro)

// Give it a minute to complete.
if got := waitForCondition(ctx, t, c.PipelineClient, createdTro, done, time.Minute); got == nil {
t.Fatal("object never done")
}

// Wait for chains to process it
if got := waitForCondition(ctx, t, c.PipelineClient, createdTro, signed, 2*time.Minute); got == nil {
t.Fatal("object never signed")
}

// Verify the taskrun has attestation annotations but OCI image signing should be skipped
// The attestation should still be created and stored in OCI registry
updatedTro, err := tekton.GetObject(t, ctx, c.PipelineClient, createdTro)
if err != nil {
t.Fatalf("error getting updated taskrun: %v", err)
}

// Check that chains processed the taskrun (attestation created)
annotations := updatedTro.GetAnnotations()
if _, ok := annotations["chains.tekton.dev/signed"]; !ok {
t.Error("expected chains.tekton.dev/signed annotation to be present")
}

// Verify signature stored in tekton storage (attestations should still work)
verifySignature(ctx, t, c, updatedTro)

// Test verifies that with artifacts.oci.signer set to "none":
// 1. OCI image signatures are NOT pushed (disabled)
// 2. TaskRun attestations ARE still generated and pushed to OCI registry
// This confirms that disabling OCI signing doesn't affect attestation generation/storage
t.Log("OCI signing disabled test completed - taskrun attestation created and stored while OCI image signing was skipped")
}
Loading