Skip to content

Conversation

@jkhelil
Copy link
Member

@jkhelil jkhelil commented Jan 15, 2026

Changes

  • Add central TLS configuration support for OpenShift components
  • Implement dynamic TLS configuration inheritance from OpenShift's
  • APIServer TLS security profile to meet PQC readiness requirements for OpenShift 4.22.

Details:

  • Observe APIServer TLS profile using library-go configobserver
  • Inject TLS_MIN_VERSION, TLS_CIPHER_SUITES, TLS_CURVE_PREFERENCES as environment variables into all Tekton component containers
  • Add hash annotation for automatic pod restarts on TLS changes
  • Apply to Pipeline, Triggers, Chains, PAC, and Results components
  • Add RBAC permissions for config.openshift.io/apiservers
  • Add ENABLE_CENTRAL_TLS_CONFIG feature flag (default: true)

Coauthored with Claude

Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

See the contribution guide for more details.

Release Notes

OpenShift Pipelines components now dynamically inherit TLS configuration (minimum version, cipher suites, and curve preferences) from the cluster's central TLS security profile, enabling PQC-readiness and allowing administrators to enforce custom TLS settings across all Tekton components through the APIServer configuration.

@tekton-robot tekton-robot added release-note-none Denotes a PR that doesnt merit a release note. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Jan 15, 2026
@tekton-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
To complete the pull request process, please ask for approval from jkhelil after the PR has been reviewed.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tekton-robot tekton-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Jan 15, 2026
@jkhelil jkhelil mentioned this pull request Jan 15, 2026
4 tasks
@tekton-robot tekton-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed release-note-none Denotes a PR that doesnt merit a release note. labels Jan 15, 2026
@jkhelil jkhelil force-pushed the tls_2 branch 2 times, most recently from 8382b81 to 443fb6f Compare January 15, 2026 12:22
@tekton-robot tekton-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 15, 2026
@jkhelil jkhelil force-pushed the tls_2 branch 4 times, most recently from 4ef860e to c4efc17 Compare January 16, 2026 09:39
@jkhelil
Copy link
Member Author

jkhelil commented Jan 16, 2026

Example for pipeline-controller, On cluster-bot cluster default tls profile is Intermediate
these env ars get injected

 - name: TLS_CIPHER_SUITES
          value: 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256'
        - name: TLS_CURVE_PREFERENCES
          value: 'P-256,P-384,P-521'
        - name: TLS_MIN_VERSION
          value: TLSv1.2
    
kind: Pod
apiVersion: v1
metadata:
  generateName: tekton-pipelines-controller-7fcc84984-
  annotations:
    k8s.ovn.org/pod-networks: '{"default":{"ip_addresses":["10.128.0.42/23"],"mac_address":"0a:58:0a:80:00:2a","gateway_ips":["10.128.0.1"],"routes":[{"dest":"10.128.0.0/14","nextHop":"10.128.0.1"},{"dest":"172.30.0.0/16","nextHop":"10.128.0.1"},{"dest":"169.254.0.5/32","nextHop":"10.128.0.1"},{"dest":"100.64.0.0/16","nextHop":"10.128.0.1"}],"ip_address":"10.128.0.42/23","gateway_ip":"10.128.0.1","role":"primary"}}'
    k8s.v1.cni.cncf.io/network-status: |-
      [{
          "name": "ovn-kubernetes",
          "interface": "eth0",
          "ips": [
              "10.128.0.42"
          ],
          "mac": "0a:58:0a:80:00:2a",
          "default": true,
          "dns": {}
      }]
    openshift.io/scc: restricted-v2
    operator.tekton.dev/tls-config-hash: eabdc95e25bc9c59
    seccomp.security.alpha.kubernetes.io/pod: runtime/default
  resourceVersion: '25485'
  name: tekton-pipelines-controller-7fcc84984-zl65f
  uid: cbd63066-9815-4273-b273-be7fc972a047
  creationTimestamp: '2026-01-16T09:28:52Z'
  
  namespace: openshift-pipelines
  ownerReferences:
    - apiVersion: apps/v1
      kind: ReplicaSet
      name: tekton-pipelines-controller-7fcc84984
      uid: 54569c2c-8e22-4177-b129-8a6aab3f3041
      controller: true
      blockOwnerDeletion: true
  labels:
    app: tekton-pipelines-controller
    app.kubernetes.io/part-of: tekton-pipelines
    app.kubernetes.io/instance: default
    pipeline.tekton.dev/release: v1.7.0
    deployment.spec.replicas: '1'
    app.kubernetes.io/version: v1.7.0
    operator.tekton.dev/deployment-spec-applied-hash: b148ba0c628754fdc4c657cc4bb9feaa
    app.kubernetes.io/component: controller
    version: v1.7.0
    app.kubernetes.io/name: controller
    pod-template-hash: 7fcc84984
spec:
  restartPolicy: Always
  serviceAccountName: tekton-pipelines-controller
  imagePullSecrets:
    - name: tekton-pipelines-controller-dockercfg-7kzcz
  priority: 0
  schedulerName: default-scheduler
  enableServiceLinks: true
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/os
                operator: NotIn
                values:
                  - windows
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchLabels:
                app.kubernetes.io/component: controller
                app.kubernetes.io/instance: default
                app.kubernetes.io/name: controller
                app.kubernetes.io/part-of: tekton-pipelines
            topologyKey: kubernetes.io/hostname
  terminationGracePeriodSeconds: 30
  preemptionPolicy: PreemptLowerPriority
  nodeName: ip-10-0-1-26.ec2.internal
  securityContext:
    seLinuxOptions:
      level: 's0:c28,c22'
    runAsNonRoot: true
    fsGroup: 1000800000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - resources: {}
      readinessProbe:
        httpGet:
          path: /readiness
          port: probes
          scheme: HTTP
        initialDelaySeconds: 5
        timeoutSeconds: 5
        periodSeconds: 10
        successThreshold: 1
        failureThreshold: 3
      terminationMessagePath: /dev/termination-log
      name: tekton-pipelines-controller
      livenessProbe:
        httpGet:
          path: /health
          port: probes
          scheme: HTTP
        initialDelaySeconds: 5
        timeoutSeconds: 5
        periodSeconds: 10
        successThreshold: 1
        failureThreshold: 3
      env:
        - name: CONFIG_DEFAULTS_NAME
          value: config-defaults
        - name: CONFIG_FEATURE_FLAGS_NAME
          value: feature-flags
        - name: CONFIG_LEADERELECTION_NAME
          value: config-leader-election-controller
        - name: CONFIG_LOGGING_NAME
          value: config-logging
        - name: CONFIG_OBSERVABILITY_NAME
          value: config-observability
        - name: CONFIG_SPIRE
          value: config-spire
        - name: KUBERNETES_MIN_VERSION
          value: v1.0.0
        - name: METRICS_DOMAIN
          value: tekton.dev/pipeline
        - name: SSL_CERT_DIR
          value: /etc/ssl/certs
        - name: SSL_CERT_FILE
          value: /etc/config-registry-cert/cert
        - name: SYSTEM_NAMESPACE
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        - name: TLS_CIPHER_SUITES
          value: 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256'
        - name: TLS_CURVE_PREFERENCES
          value: 'P-256,P-384,P-521'
        - name: TLS_MIN_VERSION
          value: TLSv1.2
      securityContext:
        capabilities:
          drop:
            - ALL
        runAsUser: 1000800000
        runAsGroup: 65532
        runAsNonRoot: true
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        seccompProfile:
          type: RuntimeDefault
      ports:
        - name: metrics
          containerPort: 9090
          protocol: TCP
        - name: profiling
          containerPort: 8008
          protocol: TCP
        - name: probes
          containerPort: 8080
          protocol: TCP
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - name: config-logging
          mountPath: /etc/config-logging
        - name: config-registry-cert
          mountPath: /etc/config-registry-cert
        - name: config-trusted-cabundle-volume
          readOnly: true
          mountPath: /etc/ssl/certs/ca-bundle.crt
          subPath: ca-bundle.crt
        - name: config-service-cabundle-volume
          readOnly: true
          mountPath: /etc/ssl/certs/service-ca.crt
          subPath: service-ca.crt
        - name: config-trusted-system-cabundle-volume
          readOnly: true
          mountPath: /etc/pki/ca-trust/extracted/pem
        - name: kube-api-access-rzg7k
          readOnly: true
          mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      terminationMessagePolicy: File
      image: 'ghcr.io/tektoncd/pipeline/controller-10a3e32792f33651396d02b6855a6e36:v1.7.0@sha256:999e7f4216f527763781b63796ac6ad79e0c0d1b69527d0879dcac4cac1b9f81'
      args:
        - '-entrypoint-image'
        - 'ghcr.io/tektoncd/pipeline/entrypoint-bff0a22da108bc2f16c818c97641a296:v1.7.0@sha256:98b8248e53fb8d5c54ec397f862a48ba78a963c9d49153d288c06b01049ee934'
        - '-nop-image'
        - 'ghcr.io/tektoncd/pipeline/nop-8eac7c133edad5df719dc37b36b62482:v1.7.0@sha256:3125feab8f67479690415e69a6cc318c6ec38b13165fc330c0be336c43548f45'
        - '-sidecarlogresults-image'
        - 'ghcr.io/tektoncd/pipeline/sidecarlogresults-7501c6a20d741631510a448b48ab098f:v1.7.0@sha256:3cb3bf8b774b68f71a0bafc1238094f382d2bb67e89b2127b901ad91f52a545a'
        - '-workingdirinit-image'
        - 'ghcr.io/tektoncd/pipeline/workingdirinit-0c558922ec6a1b739e550e349f2d5fc1:v1.7.0@sha256:ec3483230b3a5e56f42a1e171c6915369d1f2c31b2467a7cd16383b680f85971'
        - '-shell-image'
        - 'cgr.dev/chainguard/busybox@sha256:19f02276bf8dbdd62f069b922f10c65262cc34b710eea26ff928129a736be791'
        - '-shell-image-win'
        - 'mcr.microsoft.com/powershell:nanoserver@sha256:b6d5ff841b78bdf2dfed7550000fd4f3437385b8fa686ec0f010be24777654d6'
        - '-disable-ha=false'
  serviceAccount: tekton-pipelines-controller
  volumes:
    - name: config-logging
      configMap:
        name: config-logging
        defaultMode: 420
    - name: config-registry-cert
      configMap:
        name: config-registry-cert
        defaultMode: 420
    - name: config-trusted-cabundle-volume
      configMap:
        name: config-trusted-cabundle
        items:
          - key: ca-bundle.crt
            path: ca-bundle.crt
        defaultMode: 420
    - name: config-service-cabundle-volume
      configMap:
        name: config-service-cabundle
        items:
          - key: service-ca.crt
            path: service-ca.crt
        defaultMode: 420
    - name: config-trusted-system-cabundle-volume
      configMap:
        name: config-trusted-cabundle
        items:
          - key: ca-bundle.crt
            path: tls-ca-bundle.pem
        defaultMode: 420
    - name: kube-api-access-rzg7k
      projected:
        sources:
          - serviceAccountToken:
              expirationSeconds: 3607
              path: token
          - configMap:
              name: kube-root-ca.crt
              items:
                - key: ca.crt
                  path: ca.crt
          - downwardAPI:
              items:
                - path: namespace
                  fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.namespace
          - configMap:
              name: openshift-service-ca.crt
              items:
                - key: service-ca.crt
                  path: service-ca.crt
        defaultMode: 420
  dnsPolicy: ClusterFirst
  tolerations:
    - key: node.kubernetes.io/not-ready
      operator: Exists
      effect: NoExecute
      tolerationSeconds: 300
    - key: node.kubernetes.io/unreachable
      operator: Exists
      effect: NoExecute
      tolerationSeconds: 300
status:
  containerStatuses:
    - restartCount: 0
      started: true
      ready: true
      name: tekton-pipelines-controller
      state:
        running:
          startedAt: '2026-01-16T09:28:56Z'
      volumeMounts:
        - name: config-logging
          mountPath: /etc/config-logging
        - name: config-registry-cert
          mountPath: /etc/config-registry-cert
        - name: config-trusted-cabundle-volume
          mountPath: /etc/ssl/certs/ca-bundle.crt
          readOnly: true
          recursiveReadOnly: Disabled
        - name: config-service-cabundle-volume
          mountPath: /etc/ssl/certs/service-ca.crt
          readOnly: true
          recursiveReadOnly: Disabled
        - name: config-trusted-system-cabundle-volume
          mountPath: /etc/pki/ca-trust/extracted/pem
          readOnly: true
          recursiveReadOnly: Disabled
        - name: kube-api-access-rzg7k
          mountPath: /var/run/secrets/kubernetes.io/serviceaccount
          readOnly: true
          recursiveReadOnly: Disabled
      imageID: 'ghcr.io/tektoncd/pipeline/controller-10a3e32792f33651396d02b6855a6e36@sha256:88d3f9ac305a250a027cb88d7066fbc02fefcd0d86bf63a7999adf91d1ac1c2e'
      image: 'ghcr.io/tektoncd/pipeline/controller-10a3e32792f33651396d02b6855a6e36@sha256:999e7f4216f527763781b63796ac6ad79e0c0d1b69527d0879dcac4cac1b9f81'
      lastState: {}
      containerID: 'cri-o://90fa77fc15561c0dec7942f60ca0f99334f96975a982027ec68f919d685274cd'
  qosClass: BestEffort
  hostIPs:
    - ip: 10.0.1.26
  podIPs:
    - ip: 10.128.0.42

  phase: Running

@jkhelil
Copy link
Member Author

jkhelil commented Jan 16, 2026

@anithapriyanatarajan @mbpavan @enarha
ping. PTAL

@enarha
Copy link
Contributor

enarha commented Jan 18, 2026

Few high level notes on the implementation without getting down to the code level:
One notable difference with #3151 is that the policy if set just once, at operator startup. Once the TLS policy changes, it might take days/weeks until the policy is actually propagated to the services. Also using the context to propagate that configuration is not that flexible, as once set they are immutable and can't be changed. To keep the discussion in one place (not comment on both PRs), the advantage in my implementation (#3151) is that the configuration is updated dynamically the moment the cluster admit set it. Another advantage IMO is that each service is self sufficient, they do not rely on the operator to set that policy for them, so no single point of failure in case when the operator pod gets stuck for whatever reason. The disadvantage is more resources (each component sets an extra informer) and the way it's implemented now, using direct API calls to Kubernetes. Given that the APIServer is a singleton (just one instance "cluster") exists and that it'll change very rarely makes me think the number of API calls is not that big of a problem. I can optimize those, by creating a single informer and pass it to each component, which will reduce the number of API calls, but requires changes of the function signatures to add that extra parameter.
You choose to use quite older version of library-go. I also faced the same library dependencies issue, but choose to copy the newer versions of the functions we need until our code base is updated to the library versions required by library-go. Copying the functions from the library even though temporary is obviously bad, but it has one advantage. In the older version you use, openSSLToIANACiphersMap has the TLS 1.3 cipher commented out, so when you call OpenSSLToIANACipherSuites(), it checks each cipher from the TLS configuration against the ciphers in the map and silently drops the unknown ones (check vendor/github.com/openshift/library-go/pkg/crypto/crypto.go lines 306-311). The actual effect will be that TLS_CIPHER_SUITES will be empty or incomplete and inconsistent with the TLS configuration set by the cluster admin.
You added a config option to disable the central TLS management, with the default being "enabled", which totally makes sense and it's a good thing to have if an admin wants a quick fix for a broken deployment. ‎So I agree it is a good thing to have. As a side note, config/openshift/base/operator.yaml sets that to false, why?
You also hardcoded the the TLS curves. I understand that library-go still does not support those and there is an open PR which will add them, probably before the OCP release which mandates the policy enforcement, so IMO in my opinion it makes sense to keep those empty instead of hard coding them, unless you had a good reason to do so?

[EDIT] I suggest we meet and discuss both approaches instead of going back and forth on both PRs. Once we agree on a path forward, we can proceed with just one PR.

@jkhelil
Copy link
Member Author

jkhelil commented Jan 19, 2026

@enarha the context is not immutable, ieach time we do context.WithValue, returns new context.
my pr is here just to show an alternative for the implmentation using librayr-go, the thing we can use library-go just for the observer pattern, avoiding us the api watcher and lister implementation, once the tlsprofile is retreived, we use own function to convert to go ciphers (to avoid that function commented for 1.3), I experienced and it is working

I will go ahead, close my PR, I will comment your PR on what needs to be reworked/refined

@jkhelil jkhelil closed this Jan 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants