Skip to content

Commit dd6209d

Browse files
committed
CNTRLPLANE-2014: Enable configurable PKI for managed certificate rotation
Wire up the ConfigurablePKI feature gate to the PKI controller's cert rotation, enabling cluster admins to configure certificate key algorithms via the PKI CRD when the gate is enabled. Changes: - Set CertificateName and PKIProfileProvider on RotatedSigningCASecret and RotatedSelfSignedCertKeySecret to enable PKI profile resolution - Switch from ServingRotation+toClientCert to PeerRotation for target certificates that need both client and server authentication - Create a config informer factory in the PKI reconciler to provide the PKI lister when ConfigurablePKI is enabled - Make the signer controller algorithm-agnostic: remove hardcoded SHA512WithRSA signature algorithm (auto-detected from signing key) and support PKCS#8/EC private key formats alongside legacy PKCS#1 RSA When ConfigurablePKI is disabled (the default), behavior is identical to before — nil PKIProfileProvider preserves legacy RSA-2048 behavior.
1 parent 5b4dc98 commit dd6209d

File tree

2 files changed

+68
-57
lines changed

2 files changed

+68
-57
lines changed

pkg/controller/pki/pki_controller.go

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package pki
88

99
import (
1010
"context"
11-
"crypto/x509"
1211
"fmt"
1312
"log"
1413
"reflect"
@@ -22,15 +21,18 @@ import (
2221
"github.com/openshift/cluster-network-operator/pkg/names"
2322

2423
"github.com/openshift/library-go/pkg/controller/factory"
25-
"github.com/openshift/library-go/pkg/crypto"
2624
"github.com/openshift/library-go/pkg/operator/certrotation"
25+
"github.com/openshift/library-go/pkg/pki"
2726
"github.com/pkg/errors"
2827

2928
features "github.com/openshift/api/features"
29+
configclient "github.com/openshift/client-go/config/clientset/versioned"
30+
configinformers "github.com/openshift/client-go/config/informers/externalversions"
3031
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
3132
apierrors "k8s.io/apimachinery/pkg/api/errors"
3233
"k8s.io/apimachinery/pkg/types"
3334
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
35+
"k8s.io/apiserver/pkg/authentication/user"
3436
"k8s.io/client-go/informers"
3537
"k8s.io/client-go/kubernetes"
3638
crclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -76,11 +78,15 @@ type PKIReconciler struct {
7678
status *statusmanager.StatusManager
7779

7880
// one PKI per CA
79-
pkis map[types.NamespacedName]*pki
81+
pkis map[types.NamespacedName]*operatorPKI
8082
// For computing status
8183
pkiErrs map[types.NamespacedName]error
8284

8385
certDuration time.Duration
86+
87+
// pkiProfileProvider is non-nil when the ConfigurablePKI feature gate is
88+
// enabled. It provides the PKI profile for certificate key configuration.
89+
pkiProfileProvider pki.PKIProfileProvider
8490
}
8591

8692
// The periodic resync interval.
@@ -101,14 +107,29 @@ func newPKIReconciler(mgr manager.Manager, status *statusmanager.StatusManager,
101107
certDuration = 2 * time.Hour
102108
}
103109

110+
var pkiProfileProvider pki.PKIProfileProvider
111+
if featureGates.Enabled(features.FeatureGateConfigurablePKI) {
112+
cfgClient, err := configclient.NewForConfig(mgr.GetConfig())
113+
if err != nil {
114+
return nil, fmt.Errorf("failed to create config client for PKI informer: %w", err)
115+
}
116+
cfgInformers := configinformers.NewSharedInformerFactory(cfgClient, 10*time.Minute)
117+
pkiProfileProvider = pki.NewClusterPKIProfileProvider(cfgInformers.Config().V1alpha1().PKIs().Lister())
118+
119+
stopCh := make(chan struct{})
120+
cfgInformers.Start(stopCh)
121+
cfgInformers.WaitForCacheSync(stopCh)
122+
}
123+
104124
return &PKIReconciler{
105125
mgr: mgr,
106126
status: status,
107127
clientset: clientset,
108128

109-
pkis: map[types.NamespacedName]*pki{},
110-
pkiErrs: map[types.NamespacedName]error{},
111-
certDuration: certDuration,
129+
pkis: map[types.NamespacedName]*operatorPKI{},
130+
pkiErrs: map[types.NamespacedName]error{},
131+
certDuration: certDuration,
132+
pkiProfileProvider: pkiProfileProvider,
112133
}, nil
113134
}
114135

@@ -139,7 +160,7 @@ func (r *PKIReconciler) Reconcile(ctx context.Context, request reconcile.Request
139160
}
140161
}
141162
if existing == nil {
142-
existing, err = newPKI(obj, r.clientset, r.mgr, r.certDuration)
163+
existing, err = newPKI(obj, r.clientset, r.mgr, r.certDuration, r.pkiProfileProvider)
143164
if err != nil {
144165
log.Println(err)
145166
r.pkiErrs[request.NamespacedName] =
@@ -179,15 +200,15 @@ func (r *PKIReconciler) setStatus() {
179200
}
180201
}
181202

182-
// pki is the internal type that represents a single PKI CRD. It manages the
203+
// operatorPKI is the internal type that represents a single PKI CRD. It manages the
183204
// business of reconciling the certificate objects
184-
type pki struct {
205+
type operatorPKI struct {
185206
spec netopv1.OperatorPKISpec
186207
controller factory.Controller
187208
}
188209

189210
// newPKI creates a CertRotationController for the supplied configuration
190-
func newPKI(config *netopv1.OperatorPKI, clientset *kubernetes.Clientset, mgr manager.Manager, certDuration time.Duration) (*pki, error) {
211+
func newPKI(config *netopv1.OperatorPKI, clientset *kubernetes.Clientset, mgr manager.Manager, certDuration time.Duration, pkiProfileProvider pki.PKIProfileProvider) (*operatorPKI, error) {
191212
spec := config.Spec
192213

193214
// Ugly: the existing cache + informers used as part of the controller-manager
@@ -209,12 +230,14 @@ func newPKI(config *netopv1.OperatorPKI, clientset *kubernetes.Clientset, mgr ma
209230
AdditionalAnnotations: certrotation.AdditionalAnnotations{
210231
JiraComponent: names.ClusterNetworkOperatorJiraComponent,
211232
},
212-
Validity: 10 * OneYear,
213-
Refresh: 9 * OneYear,
214-
Informer: inf.Core().V1().Secrets(),
215-
Lister: inf.Core().V1().Secrets().Lister(),
216-
Client: clientset.CoreV1(),
217-
EventRecorder: &eventrecorder.LoggingRecorder{},
233+
CertificateName: fmt.Sprintf("network.%s-signer", config.Name),
234+
PKIProfileProvider: pkiProfileProvider,
235+
Validity: 10 * OneYear,
236+
Refresh: 9 * OneYear,
237+
Informer: inf.Core().V1().Secrets(),
238+
Lister: inf.Core().V1().Secrets().Lister(),
239+
Client: clientset.CoreV1(),
240+
EventRecorder: &eventrecorder.LoggingRecorder{},
218241
},
219242
certrotation.CABundleConfigMap{
220243
Namespace: config.Namespace,
@@ -233,15 +256,13 @@ func newPKI(config *netopv1.OperatorPKI, clientset *kubernetes.Clientset, mgr ma
233256
AdditionalAnnotations: certrotation.AdditionalAnnotations{
234257
JiraComponent: names.ClusterNetworkOperatorJiraComponent,
235258
},
236-
Validity: certDuration,
237-
Refresh: certDuration / 2,
238-
CertCreator: &certrotation.ServingRotation{
259+
CertificateName: fmt.Sprintf("network.%s-peer", config.Name),
260+
PKIProfileProvider: pkiProfileProvider,
261+
Validity: certDuration,
262+
Refresh: certDuration / 2,
263+
CertCreator: &certrotation.PeerRotation{
239264
Hostnames: func() []string { return []string{spec.TargetCert.CommonName} },
240-
241-
// Force the certificate to also be client
242-
CertificateExtensionFn: []crypto.CertificateExtensionFunc{
243-
toClientCert,
244-
},
265+
UserInfo: &user.DefaultInfo{Name: spec.TargetCert.CommonName},
245266
},
246267
Lister: inf.Core().V1().Secrets().Lister(),
247268
Informer: inf.Core().V1().Secrets(),
@@ -252,7 +273,7 @@ func newPKI(config *netopv1.OperatorPKI, clientset *kubernetes.Clientset, mgr ma
252273
nil,
253274
)
254275

255-
out := &pki{
276+
out := &operatorPKI{
256277
controller: cont,
257278
}
258279
config.Spec.DeepCopyInto(&out.spec)
@@ -265,29 +286,7 @@ func newPKI(config *netopv1.OperatorPKI, clientset *kubernetes.Clientset, mgr ma
265286
}
266287

267288
// sync causes the underlying cert controller to try and reconcile
268-
func (p *pki) sync() error {
289+
func (p *operatorPKI) sync() error {
269290
runOnceCtx := context.WithValue(context.Background(), certrotation.RunOnceContextKey, true) //nolint:staticcheck
270291
return p.controller.Sync(runOnceCtx, nil)
271292
}
272-
273-
// toClientCert is a certificate "decorator" that adds ClientAuth to the
274-
// list of ExtendedKeyUsages. This allows the generated certificate to be
275-
// used for both client and server auth.
276-
func toClientCert(cert *x509.Certificate) error {
277-
if len(cert.ExtKeyUsage) == 0 {
278-
return nil
279-
}
280-
281-
found := false
282-
for _, u := range cert.ExtKeyUsage {
283-
if u == x509.ExtKeyUsageClientAuth {
284-
found = true
285-
break
286-
}
287-
}
288-
289-
if !found {
290-
cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
291-
}
292-
return nil
293-
}

pkg/controller/signer/signer.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package signer
33
import (
44
c "crypto"
55
"crypto/rand"
6-
"crypto/rsa"
76
"crypto/x509"
87
"encoding/pem"
98
"errors"
@@ -21,8 +20,6 @@ func newCertificateTemplate(certReq *x509.CertificateRequest, certDuration time.
2120
template := &x509.Certificate{
2221
Subject: certReq.Subject,
2322

24-
SignatureAlgorithm: x509.SHA512WithRSA,
25-
2623
NotBefore: time.Now().Add(-1 * time.Second),
2724
NotAfter: time.Now().Add(certDuration),
2825
SerialNumber: big.NewInt(serialNumber),
@@ -69,13 +66,28 @@ func decodeCertificate(pemBytes []byte) (*x509.Certificate, error) {
6966
return x509.ParseCertificate(block.Bytes)
7067
}
7168

72-
func decodePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
69+
func decodePrivateKey(pemBytes []byte) (c.Signer, error) {
7370
block, _ := pem.Decode(pemBytes)
74-
if block == nil || block.Type != "RSA PRIVATE KEY" {
75-
fmt.Println(block.Type)
76-
err := errors.New("PEM block type must be RSA PRIVATE KEY")
77-
return nil, err
71+
if block == nil {
72+
return nil, errors.New("no PEM block found in private key data")
7873
}
7974

80-
return x509.ParsePKCS1PrivateKey(block.Bytes)
75+
switch block.Type {
76+
case "RSA PRIVATE KEY":
77+
return x509.ParsePKCS1PrivateKey(block.Bytes)
78+
case "PRIVATE KEY":
79+
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to parse PKCS8 private key: %w", err)
82+
}
83+
signer, ok := key.(c.Signer)
84+
if !ok {
85+
return nil, fmt.Errorf("parsed private key does not implement crypto.Signer")
86+
}
87+
return signer, nil
88+
case "EC PRIVATE KEY":
89+
return x509.ParseECPrivateKey(block.Bytes)
90+
default:
91+
return nil, fmt.Errorf("unsupported PEM block type: %s", block.Type)
92+
}
8193
}

0 commit comments

Comments
 (0)