Skip to content

Commit d7b8103

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

11 files changed

Lines changed: 420 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.OpenshiftConfigSecretPullSecret{},
9191
&bootkube.InternalReleaseImageTLSSecret{},
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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
profile := pkidefaults.DefaultPKIProfile()
51+
52+
// Overlay user's signerCertificates if specified in install-config
53+
if installConfig.Config.PKI != nil {
54+
profile.SignerCertificates = installConfig.Config.PKI.SignerCertificates
55+
}
56+
57+
config := &configv1alpha1.PKI{
58+
TypeMeta: metav1.TypeMeta{
59+
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
60+
Kind: "PKI",
61+
},
62+
ObjectMeta: metav1.ObjectMeta{
63+
Name: "cluster",
64+
},
65+
Spec: configv1alpha1.PKISpec{
66+
CertificateManagement: configv1alpha1.PKICertificateManagement{
67+
//TODO(htariq): Should this be Default if PKI is unset in the install config?
68+
Mode: configv1alpha1.PKICertificateManagementModeCustom,
69+
Custom: configv1alpha1.CustomPKIPolicy{
70+
PKIProfile: profile,
71+
},
72+
},
73+
},
74+
}
75+
76+
configData, err := yaml.Marshal(config)
77+
if err != nil {
78+
return errors.Wrapf(err, "failed to marshal PKI config")
79+
}
80+
81+
p.FileList = []*asset.File{
82+
{
83+
Filename: pkiCfgFilename,
84+
Data: configData,
85+
},
86+
}
87+
88+
return nil
89+
}
90+
91+
// Files returns the files generated by the asset.
92+
func (p *PKIConfiguration) Files() []*asset.File {
93+
return p.FileList
94+
}
95+
96+
// Load returns false since this asset is not written to disk by the installer.
97+
func (p *PKIConfiguration) Load(f asset.FileFetcher) (bool, error) {
98+
return false, nil
99+
}

pkg/asset/manifests/pki_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
expectSignerAlgo configv1alpha1.KeyAlgorithm
23+
expectSignerRSA int32
24+
expectSignerCurve configv1alpha1.ECDSACurve
25+
expectDefaultsAlgo configv1alpha1.KeyAlgorithm
26+
expectDefaultsRSA int32
27+
}{
28+
{
29+
name: "feature gate disabled - no manifest generated",
30+
installConfig: &types.InstallConfig{
31+
FeatureSet: configv1.Default,
32+
},
33+
expectEmpty: true,
34+
},
35+
{
36+
name: "feature gate enabled, pki nil - DefaultPKIProfile",
37+
installConfig: &types.InstallConfig{
38+
FeatureSet: configv1.TechPreviewNoUpgrade,
39+
},
40+
expectEmpty: false,
41+
expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA,
42+
expectSignerRSA: 4096,
43+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmRSA,
44+
expectDefaultsRSA: 2048,
45+
},
46+
{
47+
name: "feature gate enabled, pki RSA-4096",
48+
installConfig: &types.InstallConfig{
49+
FeatureSet: configv1.TechPreviewNoUpgrade,
50+
PKI: &types.PKIConfig{
51+
SignerCertificates: configv1alpha1.CertificateConfig{
52+
Key: configv1alpha1.KeyConfig{
53+
Algorithm: configv1alpha1.KeyAlgorithmRSA,
54+
RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096},
55+
},
56+
},
57+
},
58+
},
59+
expectEmpty: false,
60+
expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA,
61+
expectSignerRSA: 4096,
62+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmRSA,
63+
expectDefaultsRSA: 2048,
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: configv1alpha1.CertificateConfig{
71+
Key: configv1alpha1.KeyConfig{
72+
Algorithm: configv1alpha1.KeyAlgorithmECDSA,
73+
ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurveP384},
74+
},
75+
},
76+
},
77+
},
78+
expectEmpty: false,
79+
expectSignerAlgo: configv1alpha1.KeyAlgorithmECDSA,
80+
expectSignerCurve: configv1alpha1.ECDSACurveP384,
81+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmRSA,
82+
expectDefaultsRSA: 2048,
83+
},
84+
{
85+
name: "feature gate enabled, pki RSA-2048 explicit",
86+
installConfig: &types.InstallConfig{
87+
FeatureSet: configv1.TechPreviewNoUpgrade,
88+
PKI: &types.PKIConfig{
89+
SignerCertificates: configv1alpha1.CertificateConfig{
90+
Key: configv1alpha1.KeyConfig{
91+
Algorithm: configv1alpha1.KeyAlgorithmRSA,
92+
RSA: configv1alpha1.RSAKeyConfig{KeySize: 2048},
93+
},
94+
},
95+
},
96+
},
97+
expectEmpty: false,
98+
expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA,
99+
expectSignerRSA: 2048,
100+
expectDefaultsAlgo: configv1alpha1.KeyAlgorithmRSA,
101+
expectDefaultsRSA: 2048,
102+
},
103+
}
104+
105+
for _, tc := range cases {
106+
t.Run(tc.name, func(t *testing.T) {
107+
parents := asset.Parents{}
108+
parents.Add(installconfig.MakeAsset(tc.installConfig))
109+
110+
pkiAsset := &PKIConfiguration{}
111+
err := pkiAsset.Generate(context.Background(), parents)
112+
if !assert.NoError(t, err) {
113+
return
114+
}
115+
116+
if tc.expectEmpty {
117+
assert.Empty(t, pkiAsset.Files())
118+
return
119+
}
120+
121+
if !assert.Len(t, pkiAsset.Files(), 1) {
122+
return
123+
}
124+
assert.Equal(t, "manifests/cluster-pki-02-config.yaml", pkiAsset.Files()[0].Filename)
125+
126+
// Unmarshal and verify the CR structure
127+
var pkiCR configv1alpha1.PKI
128+
err = yaml.Unmarshal(pkiAsset.Files()[0].Data, &pkiCR)
129+
if !assert.NoError(t, err) {
130+
return
131+
}
132+
133+
assert.Equal(t, "config.openshift.io/v1alpha1", pkiCR.APIVersion)
134+
assert.Equal(t, "PKI", pkiCR.Kind)
135+
assert.Equal(t, "cluster", pkiCR.Name)
136+
assert.Equal(t, configv1alpha1.PKICertificateManagementModeCustom, pkiCR.Spec.CertificateManagement.Mode)
137+
138+
profile := pkiCR.Spec.CertificateManagement.Custom.PKIProfile
139+
140+
// Verify defaults
141+
assert.Equal(t, tc.expectDefaultsAlgo, profile.Defaults.Key.Algorithm)
142+
assert.Equal(t, tc.expectDefaultsRSA, profile.Defaults.Key.RSA.KeySize)
143+
144+
// Verify signerCertificates
145+
assert.Equal(t, tc.expectSignerAlgo, profile.SignerCertificates.Key.Algorithm)
146+
if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmRSA {
147+
assert.Equal(t, tc.expectSignerRSA, profile.SignerCertificates.Key.RSA.KeySize)
148+
}
149+
if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmECDSA {
150+
assert.Equal(t, tc.expectSignerCurve, profile.SignerCertificates.Key.ECDSA.Curve)
151+
}
152+
})
153+
}
154+
}

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)