Skip to content

Commit a8699d8

Browse files
jkheliltekton-robot
authored andcommitted
feat(tls): inject centrally managed TLS config into pipelines-as-code deployment and webhook
Extend the OpenShift TLS centralization pattern to the Pipelines-as-Code webhook. The openshiftpipelinesascode extension now resolves the cluster-wide APIServer TLS security profile in PreReconcile and injects the resulting TLS_MIN_VERSION and TLS_CIPHER_SUITES environment variables into the pipelines-as-code-webhook deployment (pac-webhook container) via the Transformers step. PlatformDataHashKey propagation is wired through EnsureOpenShiftPipelinesAsCodeExists / createOPAC / updateOPAC so that any change to the cluster APIServer TLS profile automatically re-reconciles the OpenShiftPipelinesAsCode CR and redeploys the webhook with the updated settings. Resolves: SRVKP-9616 Made-with: Cursor
1 parent 1c1be97 commit a8699d8

6 files changed

Lines changed: 269 additions & 21 deletions

File tree

pkg/reconciler/kubernetes/tektonconfig/extension.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (oe kubernetesExtension) PostReconcile(ctx context.Context, comp v1alpha1.T
6161

6262
pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform()
6363
if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable {
64-
if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, configInstance.Status.Version); err != nil {
64+
if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, configInstance.Status.Version, ""); err != nil {
6565
configInstance.Status.MarkComponentNotReady(fmt.Sprintf("OpenShiftPipelinesAsCode: %s", err.Error()))
6666
return v1alpha1.REQUEUE_EVENT_AFTER
6767
}

pkg/reconciler/openshift/openshiftpipelinesascode/extension.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import (
2525
mf "github.com/manifestival/manifestival"
2626
"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
2727
operatorclient "github.com/tektoncd/operator/pkg/client/injection/client"
28+
tektonConfiginformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonconfig"
2829
"github.com/tektoncd/operator/pkg/reconciler/common"
2930
"github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektoninstallerset/client"
31+
occommon "github.com/tektoncd/operator/pkg/reconciler/openshift/common"
3032
"go.uber.org/zap"
3133
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3234
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -37,7 +39,11 @@ import (
3739
)
3840

3941
const (
40-
openshiftNS = "openshift"
42+
openshiftNS = "openshift"
43+
pacControllerDeployment = "pipelines-as-code-controller"
44+
pacControllerContainerName = "pac-controller"
45+
pacWebhookDeployment = "pipelines-as-code-webhook"
46+
pacWebhookContainerName = "pac-webhook"
4147
)
4248

4349
func OpenShiftExtension(ctx context.Context) common.Extension {
@@ -68,13 +74,14 @@ func OpenShiftExtension(ctx context.Context) common.Extension {
6874
}
6975

7076
tisClient := operatorclient.Get(ctx).OperatorV1alpha1().TektonInstallerSets()
71-
return openshiftExtension{
77+
return &openshiftExtension{
7278
// component version is used for metrics, passing a dummy
7379
// value through extension not going to affect execution
7480
installerSetClient: client.NewInstallerSetClient(tisClient, operatorVer, "pipelines-as-code-ext", v1alpha1.KindOpenShiftPipelinesAsCode, nil),
7581
pacManifest: &pacManifest,
7682
pipelineRunTemplates: prTemplates,
7783
kubeClientSet: kubeclient.Get(ctx),
84+
tektonConfigLister: tektonConfiginformer.Get(ctx).Lister(),
7885
}
7986
}
8087

@@ -83,17 +90,47 @@ type openshiftExtension struct {
8390
pacManifest *mf.Manifest
8491
pipelineRunTemplates *mf.Manifest
8592
kubeClientSet kubernetes.Interface
93+
tektonConfigLister occommon.TektonConfigLister
94+
resolvedTLSConfig *occommon.TLSEnvVars
8695
}
8796

88-
func (oe openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer {
89-
return []mf.Transformer{
97+
func (oe *openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer {
98+
trns := []mf.Transformer{
9099
InjectNamespaceOwnerForPACWebhook(oe.kubeClientSet, comp.GetSpec().GetTargetNamespace()),
91100
}
101+
102+
// Inject APIServer TLS profile env vars into all three PAC deployments so that
103+
// they apply the cluster-wide TLS version and cipher suite policy (PQC readiness).
104+
if oe.resolvedTLSConfig != nil {
105+
trns = append(trns,
106+
// pac-webhook uses sharedmain.WebhookMainWithConfig (Knative webhook framework) which
107+
// calls knativetls.DefaultConfigFromEnv("WEBHOOK_") → reads WEBHOOK_TLS_* env vars.
108+
occommon.InjectTLSEnvVars(oe.resolvedTLSConfig, "Deployment", pacWebhookDeployment, []string{pacWebhookContainerName}, occommon.WebhookEnvVarPrefix),
109+
// pac-controller and pac-watcher do not serve a TLS endpoint that reads these env vars;
110+
// injecting with no prefix is harmless and keeps them consistent for future use.
111+
occommon.InjectTLSEnvVars(oe.resolvedTLSConfig, "Deployment", pacControllerDeployment, []string{pacControllerContainerName}, ""),
112+
)
113+
}
114+
115+
return trns
92116
}
93-
func (oe openshiftExtension) PreReconcile(context.Context, v1alpha1.TektonComponent) error {
117+
118+
func (oe *openshiftExtension) PreReconcile(ctx context.Context, _ v1alpha1.TektonComponent) error {
119+
logger := logging.FromContext(ctx)
120+
121+
resolvedTLS, err := occommon.ResolveCentralTLSToEnvVars(ctx, oe.tektonConfigLister)
122+
if err != nil {
123+
return err
124+
}
125+
oe.resolvedTLSConfig = resolvedTLS
126+
if oe.resolvedTLSConfig != nil {
127+
logger.Infof("Injecting central TLS config into PAC deployments: MinVersion=%s", oe.resolvedTLSConfig.MinVersion)
128+
}
129+
94130
return nil
95131
}
96-
func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.TektonComponent) error {
132+
133+
func (oe *openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.TektonComponent) error {
97134
logger := logging.FromContext(ctx)
98135

99136
if err := oe.installerSetClient.PostSet(ctx, comp, oe.pipelineRunTemplates, extFilterAndTransform()); err != nil {
@@ -107,11 +144,12 @@ func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.Te
107144
}
108145
return nil
109146
}
110-
func (oe openshiftExtension) Finalize(context.Context, v1alpha1.TektonComponent) error {
147+
148+
func (oe *openshiftExtension) Finalize(context.Context, v1alpha1.TektonComponent) error {
111149
return nil
112150
}
113151

114-
func (oe openshiftExtension) GetPlatformData() string {
152+
func (oe *openshiftExtension) GetPlatformData() string {
115153
return ""
116154
}
117155

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
Copyright 2026 The Tekton Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package openshiftpipelinesascode
18+
19+
import (
20+
"testing"
21+
22+
mf "github.com/manifestival/manifestival"
23+
"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
24+
occommon "github.com/tektoncd/operator/pkg/reconciler/openshift/common"
25+
appsv1 "k8s.io/api/apps/v1"
26+
corev1 "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
)
31+
32+
// makePACDeployment returns an unstructured PAC Deployment for transformer tests.
33+
func makePACDeployment(t *testing.T, deploymentName, containerName string) unstructured.Unstructured {
34+
t.Helper()
35+
36+
d := &appsv1.Deployment{
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: deploymentName,
39+
Namespace: "openshift-pipelines",
40+
},
41+
Spec: appsv1.DeploymentSpec{
42+
Template: corev1.PodTemplateSpec{
43+
Spec: corev1.PodSpec{
44+
Containers: []corev1.Container{
45+
{Name: containerName},
46+
},
47+
},
48+
},
49+
},
50+
}
51+
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(d)
52+
if err != nil {
53+
t.Fatalf("failed to convert deployment to unstructured: %v", err)
54+
}
55+
u := unstructured.Unstructured{Object: obj}
56+
u.SetKind("Deployment")
57+
u.SetAPIVersion("apps/v1")
58+
return u
59+
}
60+
61+
func makePACWebhookDeployment(t *testing.T) unstructured.Unstructured {
62+
t.Helper()
63+
return makePACDeployment(t, pacWebhookDeployment, pacWebhookContainerName)
64+
}
65+
66+
func TestPACTransformers_NoTLSConfig(t *testing.T) {
67+
ext := &openshiftExtension{
68+
resolvedTLSConfig: nil,
69+
}
70+
71+
transformers := ext.Transformers(&v1alpha1.OpenShiftPipelinesAsCode{})
72+
73+
u := makePACWebhookDeployment(t)
74+
manifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{u}))
75+
if err != nil {
76+
t.Fatalf("failed to build manifest: %v", err)
77+
}
78+
79+
transformed, err := manifest.Transform(transformers...)
80+
if err != nil {
81+
t.Fatalf("transform failed: %v", err)
82+
}
83+
84+
d := &appsv1.Deployment{}
85+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(transformed.Resources()[0].Object, d); err != nil {
86+
t.Fatalf("failed to convert back: %v", err)
87+
}
88+
for _, c := range d.Spec.Template.Spec.Containers {
89+
if c.Name != pacWebhookContainerName {
90+
continue
91+
}
92+
for _, e := range c.Env {
93+
if e.Name == occommon.WebhookEnvVarPrefix+occommon.TLSMinVersionEnvVar ||
94+
e.Name == occommon.WebhookEnvVarPrefix+occommon.TLSCipherSuitesEnvVar {
95+
t.Errorf("unexpected TLS env var %s set when resolvedTLSConfig is nil", e.Name)
96+
}
97+
}
98+
}
99+
}
100+
101+
func TestPACTransformers_WithTLSConfig_InjectsEnvVarsIntoWebhook(t *testing.T) {
102+
tlsConfig := &occommon.TLSEnvVars{
103+
MinVersion: "1.2",
104+
CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_128_GCM_SHA256",
105+
}
106+
// pac-webhook uses the Knative webhook framework → reads WEBHOOK_TLS_* env vars.
107+
assertTLSInjected(t, &openshiftExtension{resolvedTLSConfig: tlsConfig}, pacWebhookDeployment, pacWebhookContainerName, occommon.WebhookEnvVarPrefix, tlsConfig)
108+
}
109+
110+
// assertTLSInjected runs the Transformers against a single-resource manifest and
111+
// checks that TLS env vars are present in the named container.
112+
// envVarPrefix is prepended to the standard env var names (e.g. occommon.WebhookEnvVarPrefix
113+
// for deployments using the Knative webhook framework, "" for everything else).
114+
func assertTLSInjected(t *testing.T, ext *openshiftExtension, deploymentName, containerName, envVarPrefix string, tlsConfig *occommon.TLSEnvVars) {
115+
t.Helper()
116+
117+
u := makePACDeployment(t, deploymentName, containerName)
118+
manifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{u}))
119+
if err != nil {
120+
t.Fatalf("failed to build manifest: %v", err)
121+
}
122+
123+
transformed, err := manifest.Transform(ext.Transformers(&v1alpha1.OpenShiftPipelinesAsCode{})...)
124+
if err != nil {
125+
t.Fatalf("transform failed: %v", err)
126+
}
127+
128+
d := &appsv1.Deployment{}
129+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(transformed.Resources()[0].Object, d); err != nil {
130+
t.Fatalf("failed to convert back: %v", err)
131+
}
132+
133+
envMap := map[string]string{}
134+
for _, c := range d.Spec.Template.Spec.Containers {
135+
if c.Name != containerName {
136+
continue
137+
}
138+
for _, e := range c.Env {
139+
envMap[e.Name] = e.Value
140+
}
141+
}
142+
143+
minVersionKey := envVarPrefix + occommon.TLSMinVersionEnvVar
144+
cipherSuitesKey := envVarPrefix + occommon.TLSCipherSuitesEnvVar
145+
146+
if got := envMap[minVersionKey]; got != tlsConfig.MinVersion {
147+
t.Errorf("[%s/%s] %s = %q, want %q", deploymentName, containerName, minVersionKey, got, tlsConfig.MinVersion)
148+
}
149+
if got := envMap[cipherSuitesKey]; got != tlsConfig.CipherSuites {
150+
t.Errorf("[%s/%s] %s = %q, want %q", deploymentName, containerName, cipherSuitesKey, got, tlsConfig.CipherSuites)
151+
}
152+
}
153+
154+
func TestPACTransformers_WithTLSConfig_InjectsEnvVarsIntoController(t *testing.T) {
155+
tlsConfig := &occommon.TLSEnvVars{
156+
MinVersion: "1.2",
157+
CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_128_GCM_SHA256",
158+
}
159+
// pac-controller uses the Knative eventing adapter — no TLS version env var support; no prefix.
160+
assertTLSInjected(t, &openshiftExtension{resolvedTLSConfig: tlsConfig}, pacControllerDeployment, pacControllerContainerName, "", tlsConfig)
161+
}
162+
163+
func TestPACTransformers_WithTLSConfig_DoesNotInjectIntoUnknownDeployment(t *testing.T) {
164+
tlsConfig := &occommon.TLSEnvVars{
165+
MinVersion: "1.3",
166+
CipherSuites: "TLS_AES_128_GCM_SHA256",
167+
}
168+
ext := &openshiftExtension{resolvedTLSConfig: tlsConfig}
169+
transformers := ext.Transformers(&v1alpha1.OpenShiftPipelinesAsCode{})
170+
171+
// An unrelated deployment must not receive TLS env vars.
172+
u := makePACDeployment(t, "some-other-deployment", "some-container")
173+
manifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{u}))
174+
if err != nil {
175+
t.Fatalf("failed to build manifest: %v", err)
176+
}
177+
transformed, err := manifest.Transform(transformers...)
178+
if err != nil {
179+
t.Fatalf("transform failed: %v", err)
180+
}
181+
182+
result := &appsv1.Deployment{}
183+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(transformed.Resources()[0].Object, result); err != nil {
184+
t.Fatalf("failed to convert back: %v", err)
185+
}
186+
for _, c := range result.Spec.Template.Spec.Containers {
187+
for _, e := range c.Env {
188+
if e.Name == occommon.TLSMinVersionEnvVar || e.Name == occommon.TLSCipherSuitesEnvVar ||
189+
e.Name == occommon.WebhookEnvVarPrefix+occommon.TLSMinVersionEnvVar ||
190+
e.Name == occommon.WebhookEnvVarPrefix+occommon.TLSCipherSuitesEnvVar {
191+
t.Errorf("unexpected TLS env var %s injected into unrelated deployment", e.Name)
192+
}
193+
}
194+
}
195+
}

pkg/reconciler/openshift/tektonconfig/extension.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.Te
207207

208208
pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform()
209209
if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable {
210-
if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, oe.operatorVersion); err != nil {
210+
if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, oe.operatorVersion, oe.GetPlatformData()); err != nil {
211211
configInstance.Status.MarkComponentNotReady(fmt.Sprintf("OpenShiftPipelinesAsCode: %s", err.Error()))
212212
return v1alpha1.REQUEUE_EVENT_AFTER
213213
}

pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@ func pacSettingsFromTektonPAC(p *v1alpha1.PipelinesAsCode) v1alpha1.PACSettings
4040
}
4141
}
4242

43-
func EnsureOpenShiftPipelinesAsCodeExists(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, config *v1alpha1.TektonConfig, operatorVersion string) (*v1alpha1.OpenShiftPipelinesAsCode, error) {
43+
func EnsureOpenShiftPipelinesAsCodeExists(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, config *v1alpha1.TektonConfig, operatorVersion string, platformData string) (*v1alpha1.OpenShiftPipelinesAsCode, error) {
4444
opacCR, err := GetPAC(ctx, clients, v1alpha1.OpenShiftPipelinesAsCodeName)
4545
if err != nil {
4646
if !apierrs.IsNotFound(err) {
4747
return nil, err
4848
}
49-
if _, err = createOPAC(ctx, clients, config, operatorVersion); err != nil {
49+
if _, err = createOPAC(ctx, clients, config, operatorVersion, platformData); err != nil {
5050
return nil, err
5151
}
5252
return nil, v1alpha1.RECONCILE_AGAIN_ERR
5353
}
5454

55-
opacCR, err = updateOPAC(ctx, opacCR, config, clients, operatorVersion)
55+
opacCR, err = updateOPAC(ctx, opacCR, config, clients, operatorVersion, platformData)
5656
if err != nil {
5757
return nil, err
5858
}
@@ -68,18 +68,24 @@ func EnsureOpenShiftPipelinesAsCodeExists(ctx context.Context, clients op.OpenSh
6868
return opacCR, err
6969
}
7070

71-
func createOPAC(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, config *v1alpha1.TektonConfig, operatorVersion string) (*v1alpha1.OpenShiftPipelinesAsCode, error) {
71+
func createOPAC(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, config *v1alpha1.TektonConfig, operatorVersion string, platformData string) (*v1alpha1.OpenShiftPipelinesAsCode, error) {
7272
ownerRef := *metav1.NewControllerRef(config, config.GroupVersionKind())
7373

7474
pacSettings := pacSettingsFromTektonPAC(config.Spec.PipelinesAsCodeForCurrentPlatform())
7575

76+
annotations := map[string]string{}
77+
if platformData != "" {
78+
annotations[v1alpha1.PlatformDataHashKey] = platformData
79+
}
80+
7681
opacCR := &v1alpha1.OpenShiftPipelinesAsCode{
7782
ObjectMeta: metav1.ObjectMeta{
7883
Name: v1alpha1.OpenShiftPipelinesAsCodeName,
7984
OwnerReferences: []metav1.OwnerReference{ownerRef},
8085
Labels: map[string]string{
8186
v1alpha1.ReleaseVersionKey: operatorVersion,
8287
},
88+
Annotations: annotations,
8389
},
8490
Spec: v1alpha1.OpenShiftPipelinesAsCodeSpec{
8591
CommonSpec: v1alpha1.CommonSpec{
@@ -101,7 +107,7 @@ func GetPAC(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, n
101107
}
102108

103109
func updateOPAC(ctx context.Context, opacCR *v1alpha1.OpenShiftPipelinesAsCode, config *v1alpha1.TektonConfig,
104-
clients op.OpenShiftPipelinesAsCodeInterface, operatorVersion string,
110+
clients op.OpenShiftPipelinesAsCodeInterface, operatorVersion string, platformData string,
105111
) (*v1alpha1.OpenShiftPipelinesAsCode, error) {
106112
updated := false
107113

@@ -148,6 +154,15 @@ func updateOPAC(ctx context.Context, opacCR *v1alpha1.OpenShiftPipelinesAsCode,
148154
updated = true
149155
}
150156

157+
oldPlatformData := opacCR.ObjectMeta.Annotations[v1alpha1.PlatformDataHashKey]
158+
if oldPlatformData != platformData {
159+
if opacCR.ObjectMeta.Annotations == nil {
160+
opacCR.ObjectMeta.Annotations = map[string]string{}
161+
}
162+
opacCR.ObjectMeta.Annotations[v1alpha1.PlatformDataHashKey] = platformData
163+
updated = true
164+
}
165+
151166
if updated {
152167
_, err := clients.Update(ctx, opacCR, metav1.UpdateOptions{})
153168
if err != nil {

0 commit comments

Comments
 (0)