Skip to content

Commit ebacca8

Browse files
Merge pull request #10593 from hasbro17/pki-1-types-and-manifest
CNTRLPLANE-2012: Add PKI config types, validation, and CR manifest generation
2 parents a4e07b0 + 85252ce commit ebacca8

15 files changed

Lines changed: 1086 additions & 1 deletion

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5084,6 +5084,82 @@ spec:
50845084
- rhel-9
50855085
- rhel-10
50865086
type: string
5087+
pki:
5088+
description: |-
5089+
PKI configures cryptographic parameters for installer-generated
5090+
signer certificates. When specified, all signer certificates use the
5091+
algorithm and parameters from signerCertificates.
5092+
Feature gated by ConfigurablePKI.
5093+
properties:
5094+
signerCertificates:
5095+
description: |-
5096+
signerCertificates specifies key parameters for all installer-generated
5097+
certificate authority (CA) certificates.
5098+
When set, all signer certificates use the specified algorithm and parameters.
5099+
minProperties: 1
5100+
properties:
5101+
key:
5102+
description: key specifies the cryptographic parameters for the
5103+
certificate's key pair.
5104+
properties:
5105+
algorithm:
5106+
description: |-
5107+
algorithm specifies the key generation algorithm.
5108+
Valid values are "RSA" and "ECDSA".
5109+
enum:
5110+
- RSA
5111+
- ECDSA
5112+
type: string
5113+
ecdsa:
5114+
description: |-
5115+
ecdsa specifies ECDSA key parameters.
5116+
Required when algorithm is ECDSA, and forbidden otherwise.
5117+
properties:
5118+
curve:
5119+
description: |-
5120+
curve specifies the NIST elliptic curve for ECDSA keys.
5121+
Valid values are "P256", "P384", and "P521".
5122+
enum:
5123+
- P256
5124+
- P384
5125+
- P521
5126+
type: string
5127+
required:
5128+
- curve
5129+
type: object
5130+
rsa:
5131+
description: |-
5132+
rsa specifies RSA key parameters.
5133+
Required when algorithm is RSA, and forbidden otherwise.
5134+
properties:
5135+
keySize:
5136+
description: |-
5137+
keySize specifies the size of RSA keys in bits.
5138+
Valid values are multiples of 1024 from 2048 to 8192.
5139+
format: int32
5140+
maximum: 8192
5141+
minimum: 2048
5142+
multipleOf: 1024
5143+
type: integer
5144+
required:
5145+
- keySize
5146+
type: object
5147+
required:
5148+
- algorithm
5149+
type: object
5150+
x-kubernetes-validations:
5151+
- message: rsa is required when algorithm is RSA, and forbidden
5152+
otherwise
5153+
rule: 'has(self.algorithm) && self.algorithm == ''RSA'' ? has(self.rsa)
5154+
: !has(self.rsa)'
5155+
- message: ecdsa is required when algorithm is ECDSA, and forbidden
5156+
otherwise
5157+
rule: 'has(self.algorithm) && self.algorithm == ''ECDSA'' ? has(self.ecdsa)
5158+
: !has(self.ecdsa)'
5159+
type: object
5160+
required:
5161+
- signerCertificates
5162+
type: object
50875163
platform:
50885164
description: |-
50895165
Platform is the configuration for the specific platform upon which to

pkg/asset/manifests/operators.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func (m *Manifests) Dependencies() []asset.Asset {
9292
&bootkube.InternalReleaseImageTLSSecret{},
9393
&bootkube.InternalReleaseImageRegistryAuthSecret{},
9494
&BMCVerifyCAConfigMap{},
95+
&PKIConfiguration{},
9596
}
9697
}
9798

@@ -109,8 +110,9 @@ func (m *Manifests) Generate(_ context.Context, dependencies asset.Parents) erro
109110
imageDigestMirrorSet := &ImageDigestMirrorSet{}
110111
mcoCfgTemplate := &manifests.MCO{}
111112
bmcVerifyCAConfigMap := &BMCVerifyCAConfigMap{}
113+
pkiConfig := &PKIConfiguration{}
112114

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

115117
redactedConfig, err := redactedInstallConfig(*installConfig.Config)
116118
if err != nil {
@@ -149,6 +151,7 @@ func (m *Manifests) Generate(_ context.Context, dependencies asset.Parents) erro
149151
m.FileList = append(m.FileList, clusterCSIDriverConfig.Files()...)
150152
m.FileList = append(m.FileList, imageDigestMirrorSet.Files()...)
151153
m.FileList = append(m.FileList, bmcVerifyCAConfigMap.Files()...)
154+
m.FileList = append(m.FileList, pkiConfig.Files()...)
152155

153156
asset.SortFiles(m.FileList)
154157

pkg/asset/manifests/pki.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package manifests
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"path"
7+
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+
"github.com/openshift/installer/pkg/types"
16+
pkidefaults "github.com/openshift/installer/pkg/types/pki"
17+
)
18+
19+
var pkiCfgFilename = path.Join(manifestDir, "cluster-pki-02-config.yaml")
20+
21+
// PKIConfiguration generates the PKI custom resource manifest.
22+
type PKIConfiguration struct {
23+
FileList []*asset.File
24+
}
25+
26+
var _ asset.WritableAsset = (*PKIConfiguration)(nil)
27+
28+
// Name returns a human friendly name for the asset.
29+
func (*PKIConfiguration) Name() string {
30+
return "PKI Config"
31+
}
32+
33+
// Dependencies returns all of the dependencies directly needed to generate
34+
// the asset.
35+
func (*PKIConfiguration) Dependencies() []asset.Asset {
36+
return []asset.Asset{
37+
&installconfig.InstallConfig{},
38+
}
39+
}
40+
41+
// Generate generates the PKI custom resource manifest.
42+
// The manifest is only generated when the ConfigurablePKI feature gate is enabled.
43+
func (p *PKIConfiguration) Generate(_ context.Context, dependencies asset.Parents) error {
44+
installConfig := &installconfig.InstallConfig{}
45+
dependencies.Get(installConfig)
46+
47+
if !installConfig.Config.Enabled(features.FeatureGateConfigurablePKI) {
48+
return nil
49+
}
50+
51+
certMgmt := configv1alpha1.PKICertificateManagement{
52+
Mode: configv1alpha1.PKICertificateManagementModeDefault,
53+
}
54+
55+
if installConfig.Config.PKI != nil {
56+
profile := pkidefaults.DefaultPKIProfile()
57+
profile.SignerCertificates = convertToAPICertConfig(installConfig.Config.PKI.SignerCertificates)
58+
59+
certMgmt = configv1alpha1.PKICertificateManagement{
60+
Mode: configv1alpha1.PKICertificateManagementModeCustom,
61+
Custom: configv1alpha1.CustomPKIPolicy{
62+
PKIProfile: profile,
63+
},
64+
}
65+
}
66+
67+
config := &configv1alpha1.PKI{
68+
TypeMeta: metav1.TypeMeta{
69+
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
70+
Kind: "PKI",
71+
},
72+
ObjectMeta: metav1.ObjectMeta{
73+
Name: "cluster",
74+
},
75+
Spec: configv1alpha1.PKISpec{
76+
CertificateManagement: certMgmt,
77+
},
78+
}
79+
80+
configData, err := yaml.Marshal(config)
81+
if err != nil {
82+
return fmt.Errorf("failed to marshal PKI config: %w", err)
83+
}
84+
85+
p.FileList = []*asset.File{
86+
{
87+
Filename: pkiCfgFilename,
88+
Data: configData,
89+
},
90+
}
91+
92+
return nil
93+
}
94+
95+
// Files returns the files generated by the asset.
96+
func (p *PKIConfiguration) Files() []*asset.File {
97+
return p.FileList
98+
}
99+
100+
// Load returns false since this asset is not written to disk by the installer.
101+
func (p *PKIConfiguration) Load(f asset.FileFetcher) (bool, error) {
102+
return false, nil
103+
}
104+
105+
// convertToAPICertConfig converts the installer CertificateConfig
106+
// to the openshift/api configv1alpha1.CertificateConfig for use in the PKI CR manifest.
107+
func convertToAPICertConfig(certConf types.CertificateConfig) configv1alpha1.CertificateConfig {
108+
out := configv1alpha1.CertificateConfig{
109+
Key: configv1alpha1.KeyConfig{
110+
Algorithm: configv1alpha1.KeyAlgorithm(certConf.Key.Algorithm),
111+
},
112+
}
113+
if certConf.Key.RSA != nil {
114+
out.Key.RSA = configv1alpha1.RSAKeyConfig{KeySize: certConf.Key.RSA.KeySize}
115+
}
116+
if certConf.Key.ECDSA != nil {
117+
out.Key.ECDSA = configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurve(certConf.Key.ECDSA.Curve)}
118+
}
119+
return out
120+
}

pkg/asset/manifests/pki_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.KeyAlgorithmRSA,
63+
expectDefaultsRSA: 4096,
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.KeyAlgorithmRSA,
83+
expectDefaultsRSA: 4096,
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.KeyAlgorithmRSA,
103+
expectDefaultsRSA: 4096,
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.KeyAlgorithmRSA {
149+
assert.Equal(t, tc.expectDefaultsRSA, profile.Defaults.Key.RSA.KeySize)
150+
}
151+
if tc.expectDefaultsAlgo == configv1alpha1.KeyAlgorithmECDSA {
152+
assert.Equal(t, tc.expectDefaultsCurve, profile.Defaults.Key.ECDSA.Curve)
153+
}
154+
155+
// Verify signerCertificates
156+
assert.Equal(t, tc.expectSignerAlgo, profile.SignerCertificates.Key.Algorithm)
157+
if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmRSA {
158+
assert.Equal(t, tc.expectSignerRSA, profile.SignerCertificates.Key.RSA.KeySize)
159+
}
160+
if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmECDSA {
161+
assert.Equal(t, tc.expectSignerCurve, profile.SignerCertificates.Key.ECDSA.Curve)
162+
}
163+
})
164+
}
165+
}

0 commit comments

Comments
 (0)