Skip to content

Commit 68341be

Browse files
committed
pki: generate PKI CR manifest when ConfigurablePKI feature gate is enabled
When the ConfigurablePKI feature gate is active, the installer generates a config.openshift.io/v1alpha1 PKI custom resource manifest that is applied to the cluster during bootstrap. This CR provides day-2 operators with the certificate parameters to use when rotating certificates. When pki is not specified in install-config, the PKI CR uses mode: Default. When pki is specified, the PKI CR uses mode: Custom with DefaultPKIProfile as the base and user overrides from pki.signerCertificates layered on top. When the feature gate is enabled but pki is not specified, the installer also aligns its own signer cert generation to ECDSA P-384 (matching DefaultPKIProfile) instead of the legacy RSA-2048 default. Assisted-by: Claude Code (Opus 4.6)
1 parent 1503bab commit 68341be

12 files changed

Lines changed: 462 additions & 12 deletions

File tree

pkg/asset/manifests/operators.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func (m *Manifests) Dependencies() []asset.Asset {
9090
&bootkube.InternalReleaseImageTLSSecret{},
9191
&bootkube.InternalReleaseImageRegistryAuthSecret{},
9292
&BMCVerifyCAConfigMap{},
93+
&PKIConfiguration{},
9394
}
9495
}
9596

@@ -107,8 +108,9 @@ func (m *Manifests) Generate(_ context.Context, dependencies asset.Parents) erro
107108
imageDigestMirrorSet := &ImageDigestMirrorSet{}
108109
mcoCfgTemplate := &manifests.MCO{}
109110
bmcVerifyCAConfigMap := &BMCVerifyCAConfigMap{}
111+
pkiConfig := &PKIConfiguration{}
110112

111-
dependencies.Get(installConfig, ingress, dns, network, infra, proxy, scheduler, imageContentSourcePolicy, imageDigestMirrorSet, clusterCSIDriverConfig, mcoCfgTemplate, bmcVerifyCAConfigMap)
113+
dependencies.Get(installConfig, ingress, dns, network, infra, proxy, scheduler, imageContentSourcePolicy, imageDigestMirrorSet, clusterCSIDriverConfig, mcoCfgTemplate, bmcVerifyCAConfigMap, pkiConfig)
112114

113115
redactedConfig, err := redactedInstallConfig(*installConfig.Config)
114116
if err != nil {
@@ -147,6 +149,7 @@ func (m *Manifests) Generate(_ context.Context, dependencies asset.Parents) erro
147149
m.FileList = append(m.FileList, clusterCSIDriverConfig.Files()...)
148150
m.FileList = append(m.FileList, imageDigestMirrorSet.Files()...)
149151
m.FileList = append(m.FileList, bmcVerifyCAConfigMap.Files()...)
152+
m.FileList = append(m.FileList, pkiConfig.Files()...)
150153

151154
asset.SortFiles(m.FileList)
152155

pkg/asset/manifests/pki.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package manifests
2+
3+
import (
4+
"context"
5+
"path"
6+
7+
"github.com/pkg/errors"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"sigs.k8s.io/yaml"
10+
11+
configv1alpha1 "github.com/openshift/api/config/v1alpha1"
12+
features "github.com/openshift/api/features"
13+
"github.com/openshift/installer/pkg/asset"
14+
"github.com/openshift/installer/pkg/asset/installconfig"
15+
pkidefaults "github.com/openshift/installer/pkg/types/pki"
16+
)
17+
18+
var pkiCfgFilename = path.Join(manifestDir, "cluster-pki-02-config.yaml")
19+
20+
// PKIConfiguration generates the PKI custom resource manifest.
21+
type PKIConfiguration struct {
22+
FileList []*asset.File
23+
}
24+
25+
var _ asset.WritableAsset = (*PKIConfiguration)(nil)
26+
27+
// Name returns a human friendly name for the asset.
28+
func (*PKIConfiguration) Name() string {
29+
return "PKI Config"
30+
}
31+
32+
// Dependencies returns all of the dependencies directly needed to generate
33+
// the asset.
34+
func (*PKIConfiguration) Dependencies() []asset.Asset {
35+
return []asset.Asset{
36+
&installconfig.InstallConfig{},
37+
}
38+
}
39+
40+
// Generate generates the PKI custom resource manifest.
41+
// The manifest is only generated when the ConfigurablePKI feature gate is enabled.
42+
func (p *PKIConfiguration) Generate(_ context.Context, dependencies asset.Parents) error {
43+
installConfig := &installconfig.InstallConfig{}
44+
dependencies.Get(installConfig)
45+
46+
if !installConfig.Config.Enabled(features.FeatureGateConfigurablePKI) {
47+
return nil
48+
}
49+
50+
certMgmt := configv1alpha1.PKICertificateManagement{
51+
Mode: configv1alpha1.PKICertificateManagementModeDefault,
52+
}
53+
54+
if installConfig.Config.PKI != nil {
55+
profile := pkidefaults.DefaultPKIProfile()
56+
profile.SignerCertificates = pkidefaults.CertificateConfigToUpstream(installConfig.Config.PKI.SignerCertificates)
57+
58+
certMgmt = configv1alpha1.PKICertificateManagement{
59+
Mode: configv1alpha1.PKICertificateManagementModeCustom,
60+
Custom: configv1alpha1.CustomPKIPolicy{
61+
PKIProfile: profile,
62+
},
63+
}
64+
}
65+
66+
config := &configv1alpha1.PKI{
67+
TypeMeta: metav1.TypeMeta{
68+
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
69+
Kind: "PKI",
70+
},
71+
ObjectMeta: metav1.ObjectMeta{
72+
Name: "cluster",
73+
},
74+
Spec: configv1alpha1.PKISpec{
75+
CertificateManagement: certMgmt,
76+
},
77+
}
78+
79+
configData, err := yaml.Marshal(config)
80+
if err != nil {
81+
return errors.Wrapf(err, "failed to marshal PKI config")
82+
}
83+
84+
p.FileList = []*asset.File{
85+
{
86+
Filename: pkiCfgFilename,
87+
Data: configData,
88+
},
89+
}
90+
91+
return nil
92+
}
93+
94+
// Files returns the files generated by the asset.
95+
func (p *PKIConfiguration) Files() []*asset.File {
96+
return p.FileList
97+
}
98+
99+
// Load returns false since this asset is not written to disk by the installer.
100+
func (p *PKIConfiguration) Load(f asset.FileFetcher) (bool, error) {
101+
return false, nil
102+
}

pkg/asset/manifests/pki_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package manifests
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"sigs.k8s.io/yaml"
9+
10+
configv1 "github.com/openshift/api/config/v1"
11+
configv1alpha1 "github.com/openshift/api/config/v1alpha1"
12+
"github.com/openshift/installer/pkg/asset"
13+
"github.com/openshift/installer/pkg/asset/installconfig"
14+
"github.com/openshift/installer/pkg/types"
15+
)
16+
17+
func TestPKIConfigurationGenerate(t *testing.T) {
18+
cases := []struct {
19+
name string
20+
installConfig *types.InstallConfig
21+
expectEmpty bool
22+
expectMode configv1alpha1.PKICertificateManagementMode
23+
expectSignerAlgo configv1alpha1.KeyAlgorithm
24+
expectSignerRSA int32
25+
expectSignerCurve configv1alpha1.ECDSACurve
26+
expectDefaultsAlgo configv1alpha1.KeyAlgorithm
27+
expectDefaultsRSA int32
28+
expectDefaultsCurve configv1alpha1.ECDSACurve
29+
}{
30+
{
31+
name: "feature gate disabled - no manifest generated",
32+
installConfig: &types.InstallConfig{
33+
FeatureSet: configv1.Default,
34+
},
35+
expectEmpty: true,
36+
},
37+
{
38+
name: "feature gate enabled, pki nil - mode Default",
39+
installConfig: &types.InstallConfig{
40+
FeatureSet: configv1.TechPreviewNoUpgrade,
41+
},
42+
expectEmpty: false,
43+
expectMode: configv1alpha1.PKICertificateManagementModeDefault,
44+
},
45+
{
46+
name: "feature gate enabled, pki RSA-4096",
47+
installConfig: &types.InstallConfig{
48+
FeatureSet: configv1.TechPreviewNoUpgrade,
49+
PKI: &types.PKIConfig{
50+
SignerCertificates: types.CertificateConfig{
51+
Key: types.KeyConfig{
52+
Algorithm: types.KeyAlgorithmRSA,
53+
RSA: types.RSAKeyConfig{KeySize: 4096},
54+
},
55+
},
56+
},
57+
},
58+
expectEmpty: false,
59+
expectMode: configv1alpha1.PKICertificateManagementModeCustom,
60+
expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA,
61+
expectSignerRSA: 4096,
62+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmECDSA,
63+
expectDefaultsCurve: configv1alpha1.ECDSACurveP256,
64+
},
65+
{
66+
name: "feature gate enabled, pki ECDSA P-384",
67+
installConfig: &types.InstallConfig{
68+
FeatureSet: configv1.TechPreviewNoUpgrade,
69+
PKI: &types.PKIConfig{
70+
SignerCertificates: types.CertificateConfig{
71+
Key: types.KeyConfig{
72+
Algorithm: types.KeyAlgorithmECDSA,
73+
ECDSA: types.ECDSAKeyConfig{Curve: types.ECDSACurveP384},
74+
},
75+
},
76+
},
77+
},
78+
expectEmpty: false,
79+
expectMode: configv1alpha1.PKICertificateManagementModeCustom,
80+
expectSignerAlgo: configv1alpha1.KeyAlgorithmECDSA,
81+
expectSignerCurve: configv1alpha1.ECDSACurveP384,
82+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmECDSA,
83+
expectDefaultsCurve: configv1alpha1.ECDSACurveP256,
84+
},
85+
{
86+
name: "feature gate enabled, pki RSA-2048 explicit",
87+
installConfig: &types.InstallConfig{
88+
FeatureSet: configv1.TechPreviewNoUpgrade,
89+
PKI: &types.PKIConfig{
90+
SignerCertificates: types.CertificateConfig{
91+
Key: types.KeyConfig{
92+
Algorithm: types.KeyAlgorithmRSA,
93+
RSA: types.RSAKeyConfig{KeySize: 2048},
94+
},
95+
},
96+
},
97+
},
98+
expectEmpty: false,
99+
expectMode: configv1alpha1.PKICertificateManagementModeCustom,
100+
expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA,
101+
expectSignerRSA: 2048,
102+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmECDSA,
103+
expectDefaultsCurve: configv1alpha1.ECDSACurveP256,
104+
},
105+
}
106+
107+
for _, tc := range cases {
108+
t.Run(tc.name, func(t *testing.T) {
109+
parents := asset.Parents{}
110+
parents.Add(installconfig.MakeAsset(tc.installConfig))
111+
112+
pkiAsset := &PKIConfiguration{}
113+
err := pkiAsset.Generate(context.Background(), parents)
114+
if !assert.NoError(t, err) {
115+
return
116+
}
117+
118+
if tc.expectEmpty {
119+
assert.Empty(t, pkiAsset.Files())
120+
return
121+
}
122+
123+
if !assert.Len(t, pkiAsset.Files(), 1) {
124+
return
125+
}
126+
assert.Equal(t, "manifests/cluster-pki-02-config.yaml", pkiAsset.Files()[0].Filename)
127+
128+
// Unmarshal and verify the CR structure
129+
var pkiCR configv1alpha1.PKI
130+
err = yaml.Unmarshal(pkiAsset.Files()[0].Data, &pkiCR)
131+
if !assert.NoError(t, err) {
132+
return
133+
}
134+
135+
assert.Equal(t, "config.openshift.io/v1alpha1", pkiCR.APIVersion)
136+
assert.Equal(t, "PKI", pkiCR.Kind)
137+
assert.Equal(t, "cluster", pkiCR.Name)
138+
assert.Equal(t, tc.expectMode, pkiCR.Spec.CertificateManagement.Mode)
139+
140+
if tc.expectMode != configv1alpha1.PKICertificateManagementModeCustom {
141+
return
142+
}
143+
144+
profile := pkiCR.Spec.CertificateManagement.Custom.PKIProfile
145+
146+
// Verify defaults
147+
assert.Equal(t, tc.expectDefaultsAlgo, profile.Defaults.Key.Algorithm)
148+
if tc.expectDefaultsAlgo == configv1alpha1.KeyAlgorithmECDSA {
149+
assert.Equal(t, tc.expectDefaultsCurve, profile.Defaults.Key.ECDSA.Curve)
150+
} else {
151+
assert.Equal(t, tc.expectDefaultsRSA, profile.Defaults.Key.RSA.KeySize)
152+
}
153+
154+
// Verify signerCertificates
155+
assert.Equal(t, tc.expectSignerAlgo, profile.SignerCertificates.Key.Algorithm)
156+
if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmRSA {
157+
assert.Equal(t, tc.expectSignerRSA, profile.SignerCertificates.Key.RSA.KeySize)
158+
}
159+
if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmECDSA {
160+
assert.Equal(t, tc.expectSignerCurve, profile.SignerCertificates.Key.ECDSA.Curve)
161+
}
162+
})
163+
}
164+
}

pkg/asset/tls/adminkubeconfig.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/openshift/installer/pkg/asset"
99
"github.com/openshift/installer/pkg/asset/installconfig"
10+
pkidefaults "github.com/openshift/installer/pkg/types/pki"
1011
)
1112

1213
// AdminKubeConfigSignerCertKey is a key/cert pair that signs the admin kubeconfig client certs.
@@ -32,7 +33,7 @@ func (c *AdminKubeConfigSignerCertKey) Generate(ctx context.Context, parents ass
3233
IsCA: true,
3334
}
3435

35-
return c.SelfSignedCertKey.Generate(ctx, cfg, "admin-kubeconfig-signer", installConfig.Config.PKI)
36+
return c.SelfSignedCertKey.Generate(ctx, cfg, "admin-kubeconfig-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
3637
}
3738

3839
// Load reads the asset files from disk.

pkg/asset/tls/aggregator.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/openshift/installer/pkg/asset"
99
"github.com/openshift/installer/pkg/asset/installconfig"
10+
pkidefaults "github.com/openshift/installer/pkg/types/pki"
1011
)
1112

1213
// AggregatorCA is the asset that generates the aggregator-ca key/cert pair.
@@ -38,7 +39,7 @@ func (a *AggregatorCA) Generate(ctx context.Context, dependencies asset.Parents)
3839
IsCA: true,
3940
}
4041

41-
return a.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-ca", installConfig.Config.PKI)
42+
return a.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-ca", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
4243
}
4344

4445
// Name returns the human-friendly name of the asset.
@@ -108,7 +109,7 @@ func (c *AggregatorSignerCertKey) Generate(ctx context.Context, parents asset.Pa
108109
IsCA: true,
109110
}
110111

111-
return c.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-signer", installConfig.Config.PKI)
112+
return c.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
112113
}
113114

114115
// Name returns the human-friendly name of the asset.

pkg/asset/tls/apiserver.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/openshift/installer/pkg/asset"
1212
"github.com/openshift/installer/pkg/asset/installconfig"
13+
pkidefaults "github.com/openshift/installer/pkg/types/pki"
1314
)
1415

1516
// KubeAPIServerToKubeletSignerCertKey is a key/cert pair that signs the kube-apiserver to kubelet client certs.
@@ -35,7 +36,7 @@ func (c *KubeAPIServerToKubeletSignerCertKey) Generate(ctx context.Context, pare
3536
IsCA: true,
3637
}
3738

38-
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-to-kubelet-signer", installConfig.Config.PKI)
39+
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-to-kubelet-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
3940
}
4041

4142
// Name returns the human-friendly name of the asset.
@@ -132,7 +133,7 @@ func (c *KubeAPIServerLocalhostSignerCertKey) Generate(ctx context.Context, pare
132133
IsCA: true,
133134
}
134135

135-
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-localhost-signer", installConfig.Config.PKI)
136+
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-localhost-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
136137
}
137138

138139
// Load reads the asset files from disk.
@@ -238,7 +239,7 @@ func (c *KubeAPIServerServiceNetworkSignerCertKey) Generate(ctx context.Context,
238239
IsCA: true,
239240
}
240241

241-
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-service-network-signer", installConfig.Config.PKI)
242+
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-service-network-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
242243
}
243244

244245
// Load reads the asset files from disk.
@@ -353,7 +354,7 @@ func (c *KubeAPIServerLBSignerCertKey) Generate(ctx context.Context, parents ass
353354
IsCA: true,
354355
}
355356

356-
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-lb-signer", installConfig.Config.PKI)
357+
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-lb-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
357358
}
358359

359360
// Load reads the asset files from disk.

pkg/asset/tls/kubecontrolplane.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/openshift/installer/pkg/asset"
99
"github.com/openshift/installer/pkg/asset/installconfig"
10+
pkidefaults "github.com/openshift/installer/pkg/types/pki"
1011
)
1112

1213
// KubeControlPlaneSignerCertKey is a key/cert pair that signs the kube control-plane client certs.
@@ -32,7 +33,7 @@ func (c *KubeControlPlaneSignerCertKey) Generate(ctx context.Context, parents as
3233
IsCA: true,
3334
}
3435

35-
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-control-plane-signer", installConfig.Config.PKI)
36+
return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-control-plane-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config))
3637
}
3738

3839
// Name returns the human-friendly name of the asset.

0 commit comments

Comments
 (0)