Skip to content

Commit 057f2a7

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 ba75879 commit 057f2a7

11 files changed

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

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)