Skip to content

Commit 03034ff

Browse files
committed
feat(tls): inject centrally managed TLS config into pipelines-as-code deployment and webhook
Extend the OpenShift TLS centralization pattern (introduced for Tekton Pipelines and Triggers webhooks) 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, identical to the approach used for tekton-triggers-webhook. 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 e4eaabd commit 03034ff

6 files changed

Lines changed: 266 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: 45 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,13 @@ import (
3739
)
3840

3941
const (
40-
openshiftNS = "openshift"
42+
openshiftNS = "openshift"
43+
pacControllerDeployment = "pipelines-as-code-controller"
44+
pacControllerContainerName = "pac-controller"
45+
pacWatcherDeployment = "pipelines-as-code-watcher"
46+
pacWatcherContainerName = "pac-watcher"
47+
pacWebhookDeployment = "pipelines-as-code-webhook"
48+
pacWebhookContainerName = "pac-webhook"
4149
)
4250

4351
func OpenShiftExtension(ctx context.Context) common.Extension {
@@ -68,13 +76,14 @@ func OpenShiftExtension(ctx context.Context) common.Extension {
6876
}
6977

7078
tisClient := operatorclient.Get(ctx).OperatorV1alpha1().TektonInstallerSets()
71-
return openshiftExtension{
79+
return &openshiftExtension{
7280
// component version is used for metrics, passing a dummy
7381
// value through extension not going to affect execution
7482
installerSetClient: client.NewInstallerSetClient(tisClient, operatorVer, "pipelines-as-code-ext", v1alpha1.KindOpenShiftPipelinesAsCode, nil),
7583
pacManifest: &pacManifest,
7684
pipelineRunTemplates: prTemplates,
7785
kubeClientSet: kubeclient.Get(ctx),
86+
tektonConfigLister: tektonConfiginformer.Get(ctx).Lister(),
7887
}
7988
}
8089

@@ -83,17 +92,44 @@ type openshiftExtension struct {
8392
pacManifest *mf.Manifest
8493
pipelineRunTemplates *mf.Manifest
8594
kubeClientSet kubernetes.Interface
95+
tektonConfigLister occommon.TektonConfigLister
96+
resolvedTLSConfig *occommon.TLSEnvVars
8697
}
8798

88-
func (oe openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer {
89-
return []mf.Transformer{
99+
func (oe *openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer {
100+
trns := []mf.Transformer{
90101
InjectNamespaceOwnerForPACWebhook(oe.kubeClientSet, comp.GetSpec().GetTargetNamespace()),
91102
}
103+
104+
// Inject APIServer TLS profile env vars into all three PAC deployments so that
105+
// they apply the cluster-wide TLS version and cipher suite policy (PQC readiness).
106+
if oe.resolvedTLSConfig != nil {
107+
trns = append(trns,
108+
occommon.InjectTLSEnvVars(oe.resolvedTLSConfig, "Deployment", pacControllerDeployment, []string{pacControllerContainerName}),
109+
occommon.InjectTLSEnvVars(oe.resolvedTLSConfig, "Deployment", pacWatcherDeployment, []string{pacWatcherContainerName}),
110+
occommon.InjectTLSEnvVars(oe.resolvedTLSConfig, "Deployment", pacWebhookDeployment, []string{pacWebhookContainerName}),
111+
)
112+
}
113+
114+
return trns
92115
}
93-
func (oe openshiftExtension) PreReconcile(context.Context, v1alpha1.TektonComponent) error {
116+
117+
func (oe *openshiftExtension) PreReconcile(ctx context.Context, _ v1alpha1.TektonComponent) error {
118+
logger := logging.FromContext(ctx)
119+
120+
resolvedTLS, err := occommon.ResolveCentralTLSToEnvVars(ctx, oe.tektonConfigLister)
121+
if err != nil {
122+
return err
123+
}
124+
oe.resolvedTLSConfig = resolvedTLS
125+
if oe.resolvedTLSConfig != nil {
126+
logger.Infof("Injecting central TLS config into PAC deployments: MinVersion=%s", oe.resolvedTLSConfig.MinVersion)
127+
}
128+
94129
return nil
95130
}
96-
func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.TektonComponent) error {
131+
132+
func (oe *openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.TektonComponent) error {
97133
logger := logging.FromContext(ctx)
98134

99135
if err := oe.installerSetClient.PostSet(ctx, comp, oe.pipelineRunTemplates, extFilterAndTransform()); err != nil {
@@ -107,11 +143,12 @@ func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.Te
107143
}
108144
return nil
109145
}
110-
func (oe openshiftExtension) Finalize(context.Context, v1alpha1.TektonComponent) error {
146+
147+
func (oe *openshiftExtension) Finalize(context.Context, v1alpha1.TektonComponent) error {
111148
return nil
112149
}
113150

114-
func (oe openshiftExtension) GetPlatformData() string {
151+
func (oe *openshiftExtension) GetPlatformData() string {
115152
return ""
116153
}
117154

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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.TLSMinVersionEnvVar || e.Name == occommon.TLSCipherSuitesEnvVar {
94+
t.Errorf("unexpected TLS env var %s set when resolvedTLSConfig is nil", e.Name)
95+
}
96+
}
97+
}
98+
}
99+
100+
func TestPACTransformers_WithTLSConfig_InjectsEnvVarsIntoWebhook(t *testing.T) {
101+
tlsConfig := &occommon.TLSEnvVars{
102+
MinVersion: "1.2",
103+
CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_128_GCM_SHA256",
104+
}
105+
assertTLSInjected(t, &openshiftExtension{resolvedTLSConfig: tlsConfig}, pacWebhookDeployment, pacWebhookContainerName, tlsConfig)
106+
}
107+
108+
// assertTLSInjected runs the Transformers against a single-resource manifest and
109+
// checks that TLS env vars are present in the named container.
110+
func assertTLSInjected(t *testing.T, ext *openshiftExtension, deploymentName, containerName string, tlsConfig *occommon.TLSEnvVars) {
111+
t.Helper()
112+
113+
u := makePACDeployment(t, deploymentName, containerName)
114+
manifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{u}))
115+
if err != nil {
116+
t.Fatalf("failed to build manifest: %v", err)
117+
}
118+
119+
transformed, err := manifest.Transform(ext.Transformers(&v1alpha1.OpenShiftPipelinesAsCode{})...)
120+
if err != nil {
121+
t.Fatalf("transform failed: %v", err)
122+
}
123+
124+
d := &appsv1.Deployment{}
125+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(transformed.Resources()[0].Object, d); err != nil {
126+
t.Fatalf("failed to convert back: %v", err)
127+
}
128+
129+
envMap := map[string]string{}
130+
for _, c := range d.Spec.Template.Spec.Containers {
131+
if c.Name != containerName {
132+
continue
133+
}
134+
for _, e := range c.Env {
135+
envMap[e.Name] = e.Value
136+
}
137+
}
138+
139+
if got := envMap[occommon.TLSMinVersionEnvVar]; got != tlsConfig.MinVersion {
140+
t.Errorf("[%s/%s] %s = %q, want %q", deploymentName, containerName, occommon.TLSMinVersionEnvVar, got, tlsConfig.MinVersion)
141+
}
142+
if got := envMap[occommon.TLSCipherSuitesEnvVar]; got != tlsConfig.CipherSuites {
143+
t.Errorf("[%s/%s] %s = %q, want %q", deploymentName, containerName, occommon.TLSCipherSuitesEnvVar, got, tlsConfig.CipherSuites)
144+
}
145+
}
146+
147+
func TestPACTransformers_WithTLSConfig_InjectsEnvVarsIntoController(t *testing.T) {
148+
tlsConfig := &occommon.TLSEnvVars{
149+
MinVersion: "1.2",
150+
CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_128_GCM_SHA256",
151+
}
152+
assertTLSInjected(t, &openshiftExtension{resolvedTLSConfig: tlsConfig}, pacControllerDeployment, pacControllerContainerName, tlsConfig)
153+
}
154+
155+
func TestPACTransformers_WithTLSConfig_InjectsEnvVarsIntoWatcher(t *testing.T) {
156+
tlsConfig := &occommon.TLSEnvVars{
157+
MinVersion: "1.2",
158+
CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_128_GCM_SHA256",
159+
}
160+
assertTLSInjected(t, &openshiftExtension{resolvedTLSConfig: tlsConfig}, pacWatcherDeployment, pacWatcherContainerName, 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+
t.Errorf("unexpected TLS env var %s injected into unrelated deployment", e.Name)
190+
}
191+
}
192+
}
193+
}

pkg/reconciler/openshift/tektonconfig/extension.go

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

192192
pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform()
193193
if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable {
194-
if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, oe.operatorVersion); err != nil {
194+
if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, oe.operatorVersion, oe.GetPlatformData()); err != nil {
195195
configInstance.Status.MarkComponentNotReady(fmt.Sprintf("OpenShiftPipelinesAsCode: %s", err.Error()))
196196
return v1alpha1.REQUEUE_EVENT_AFTER
197197
}

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)