diff --git a/go.mod b/go.mod index bdbf940af6e..f018adcdede 100644 --- a/go.mod +++ b/go.mod @@ -343,7 +343,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.24.5 // indirect - k8s.io/apiserver v0.36.0 // indirect + k8s.io/apiserver v0.36.0 k8s.io/cli-runtime v0.36.0 // indirect k8s.io/cluster-bootstrap v0.35.5 // indirect k8s.io/component-base v0.36.0 // indirect diff --git a/pkg/asset/imagebased/configimage/ingressoperatorsigner.go b/pkg/asset/imagebased/configimage/ingressoperatorsigner.go index 48184aa37fa..8018e246cc0 100644 --- a/pkg/asset/imagebased/configimage/ingressoperatorsigner.go +++ b/pkg/asset/imagebased/configimage/ingressoperatorsigner.go @@ -20,6 +20,8 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" + libcrypto "github.com/openshift/library-go/pkg/crypto" + libpki "github.com/openshift/library-go/pkg/pki" ) // Name returns the human-friendly name of the asset. @@ -37,7 +39,10 @@ var _ asset.Asset = (*IngressOperatorSignerCertKey)(nil) // Dependencies returns the dependency of the the cert/key pair. func (a *IngressOperatorSignerCertKey) Dependencies() []asset.Asset { - return []asset.Asset{&installconfig.InstallConfig{}} + return []asset.Asset{ + &installconfig.InstallConfig{}, + &tls.SignerKeyParams{}, + } } // Generate generates the cert/key pair based on its dependencies. @@ -45,27 +50,38 @@ func (a *IngressOperatorSignerCertKey) Generate(ctx context.Context, dependencie signerName := fmt.Sprintf("%s@%d", "ingress-operator", time.Now().Unix()) installConfig := &installconfig.InstallConfig{} - dependencies.Get(installConfig) - - cfg := &tls.CertCfg{ - Subject: pkix.Name{CommonName: signerName}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: tls.ValidityOneYear(installConfig) * 2, - IsCA: true, + pkiCfg := &tls.SignerKeyParams{} + dependencies.Get(installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &tls.CertCfg{ + Subject: pkix.Name{CommonName: signerName}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: tls.ValidityOneYear(installConfig) * 2, + IsCA: true, + } + key, crt, err := generateSelfSignedCertificate(cfg) + if err != nil { + return err + } + a.KeyRaw, err = tls.PrivateKeyToPem(key) + if err != nil { + return fmt.Errorf("failed to encode private key to PEM: %w", err) + } + a.CertRaw = tls.CertToPem(crt) + return nil } - key, crt, err := generateSelfSignedCertificate(cfg) + keyGen, err := resolveSignerKeyGen(pkiCfg, "installer.ingress-operator-signer") if err != nil { return err } - - a.KeyRaw, err = tls.PrivateKeyToPem(key) - if err != nil { - return fmt.Errorf("failed to encode private key to PEM: %w", err) + cfg := &tls.CertCfg{ + Subject: pkix.Name{CommonName: signerName}, + Validity: tls.ValidityOneYear(installConfig) * 2, + IsCA: true, } - a.CertRaw = tls.CertToPem(crt) - - return nil + return a.SelfSignedCertKey.Generate(ctx, cfg, "ingress-operator-signer", keyGen) } // IngressOperatorCABundle is the asset the generates the ingress-operator-signer-ca-bundle, @@ -175,3 +191,14 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { hash := sha1.Sum(publicKeyBytes) //nolint: gosec return hash[:], nil } + +// resolveSignerKeyGen resolves the KeyPairGenerator for a signer certificate +// from the SignerKeyParams's profile. +func resolveSignerKeyGen(pkiCfg *tls.SignerKeyParams, certName string) (libcrypto.KeyPairGenerator, error) { + provider := libpki.NewStaticPKIProfileProvider(&pkiCfg.Profile) + resolved, err := libpki.ResolveCertificateConfig(provider, libpki.CertificateTypeSigner, certName) + if err != nil { + return nil, fmt.Errorf("failed to resolve PKI config for signer certificate %q: %w", certName, err) + } + return resolved.Key, nil +} diff --git a/pkg/asset/manifests/pki.go b/pkg/asset/manifests/pki.go index f0d6433cd2d..5009b9f0a48 100644 --- a/pkg/asset/manifests/pki.go +++ b/pkg/asset/manifests/pki.go @@ -9,11 +9,9 @@ import ( "sigs.k8s.io/yaml" configv1alpha1 "github.com/openshift/api/config/v1alpha1" - features "github.com/openshift/api/features" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/types" - pkidefaults "github.com/openshift/installer/pkg/types/pki" + "github.com/openshift/installer/pkg/asset/tls" + libpki "github.com/openshift/library-go/pkg/pki" ) var pkiCfgFilename = path.Join(manifestDir, "cluster-pki-02-config.yaml") @@ -34,17 +32,17 @@ func (*PKIConfiguration) Name() string { // the asset. func (*PKIConfiguration) Dependencies() []asset.Asset { return []asset.Asset{ - &installconfig.InstallConfig{}, + &tls.SignerKeyParams{}, } } // Generate generates the PKI custom resource manifest. // The manifest is only generated when the ConfigurablePKI feature gate is enabled. func (p *PKIConfiguration) Generate(_ context.Context, dependencies asset.Parents) error { - installConfig := &installconfig.InstallConfig{} - dependencies.Get(installConfig) + signerKeyParams := &tls.SignerKeyParams{} + dependencies.Get(signerKeyParams) - if !installConfig.Config.Enabled(features.FeatureGateConfigurablePKI) { + if !signerKeyParams.ConfigurablePKIEnabled { return nil } @@ -52,14 +50,14 @@ func (p *PKIConfiguration) Generate(_ context.Context, dependencies asset.Parent Mode: configv1alpha1.PKICertificateManagementModeDefault, } - if installConfig.Config.PKI != nil { - profile := pkidefaults.DefaultPKIProfile() - profile.SignerCertificates = convertToAPICertConfig(installConfig.Config.PKI.SignerCertificates) - + // When ConfigurablePKIEnabled is true but the profile equals the default, + // it means no user customization was provided. + defaultProfile := libpki.DefaultPKIProfile() + if signerKeyParams.Profile != defaultProfile { certMgmt = configv1alpha1.PKICertificateManagement{ Mode: configv1alpha1.PKICertificateManagementModeCustom, Custom: configv1alpha1.CustomPKIPolicy{ - PKIProfile: profile, + PKIProfile: signerKeyParams.Profile, }, } } @@ -101,20 +99,3 @@ func (p *PKIConfiguration) Files() []*asset.File { func (p *PKIConfiguration) Load(f asset.FileFetcher) (bool, error) { return false, nil } - -// convertToAPICertConfig converts the installer CertificateConfig -// to the openshift/api configv1alpha1.CertificateConfig for use in the PKI CR manifest. -func convertToAPICertConfig(certConf types.CertificateConfig) configv1alpha1.CertificateConfig { - out := configv1alpha1.CertificateConfig{ - Key: configv1alpha1.KeyConfig{ - Algorithm: configv1alpha1.KeyAlgorithm(certConf.Key.Algorithm), - }, - } - if certConf.Key.RSA != nil { - out.Key.RSA = configv1alpha1.RSAKeyConfig{KeySize: certConf.Key.RSA.KeySize} - } - if certConf.Key.ECDSA != nil { - out.Key.ECDSA = configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurve(certConf.Key.ECDSA.Curve)} - } - return out -} diff --git a/pkg/asset/manifests/pki_test.go b/pkg/asset/manifests/pki_test.go index 4136acd9ac7..42daa7970d9 100644 --- a/pkg/asset/manifests/pki_test.go +++ b/pkg/asset/manifests/pki_test.go @@ -7,17 +7,16 @@ import ( "github.com/stretchr/testify/assert" "sigs.k8s.io/yaml" - configv1 "github.com/openshift/api/config/v1" configv1alpha1 "github.com/openshift/api/config/v1alpha1" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/types" + "github.com/openshift/installer/pkg/asset/tls" + libpki "github.com/openshift/library-go/pkg/pki" ) func TestPKIConfigurationGenerate(t *testing.T) { - cases := []struct { + testCases := []struct { name string - installConfig *types.InstallConfig + signerKeyParams *tls.SignerKeyParams expectEmpty bool expectMode configv1alpha1.PKICertificateManagementMode expectSignerAlgo configv1alpha1.KeyAlgorithm @@ -29,31 +28,38 @@ func TestPKIConfigurationGenerate(t *testing.T) { }{ { name: "feature gate disabled - no manifest generated", - installConfig: &types.InstallConfig{ - FeatureSet: configv1.Default, + signerKeyParams: &tls.SignerKeyParams{ + ConfigurablePKIEnabled: false, }, expectEmpty: true, }, { name: "feature gate enabled, pki nil - mode Default", - installConfig: &types.InstallConfig{ - FeatureSet: configv1.TechPreviewNoUpgrade, + signerKeyParams: &tls.SignerKeyParams{ + Profile: libpki.DefaultPKIProfile(), + ConfigurablePKIEnabled: true, }, expectEmpty: false, expectMode: configv1alpha1.PKICertificateManagementModeDefault, }, { name: "feature gate enabled, pki RSA-4096", - installConfig: &types.InstallConfig{ - FeatureSet: configv1.TechPreviewNoUpgrade, - PKI: &types.PKIConfig{ - SignerCertificates: types.CertificateConfig{ - Key: types.KeyConfig{ - Algorithm: types.KeyAlgorithmRSA, - RSA: &types.RSAKeyConfig{KeySize: 4096}, + signerKeyParams: &tls.SignerKeyParams{ + Profile: configv1alpha1.PKIProfile{ + Defaults: configv1alpha1.DefaultCertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmRSA, + RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096}, + }, + }, + SignerCertificates: configv1alpha1.CertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmRSA, + RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096}, }, }, }, + ConfigurablePKIEnabled: true, }, expectEmpty: false, expectMode: configv1alpha1.PKICertificateManagementModeCustom, @@ -64,16 +70,22 @@ func TestPKIConfigurationGenerate(t *testing.T) { }, { name: "feature gate enabled, pki ECDSA P-384", - installConfig: &types.InstallConfig{ - FeatureSet: configv1.TechPreviewNoUpgrade, - PKI: &types.PKIConfig{ - SignerCertificates: types.CertificateConfig{ - Key: types.KeyConfig{ - Algorithm: types.KeyAlgorithmECDSA, - ECDSA: &types.ECDSAKeyConfig{Curve: types.ECDSACurveP384}, + signerKeyParams: &tls.SignerKeyParams{ + Profile: configv1alpha1.PKIProfile{ + Defaults: configv1alpha1.DefaultCertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmRSA, + RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096}, + }, + }, + SignerCertificates: configv1alpha1.CertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmECDSA, + ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurveP384}, }, }, }, + ConfigurablePKIEnabled: true, }, expectEmpty: false, expectMode: configv1alpha1.PKICertificateManagementModeCustom, @@ -84,16 +96,22 @@ func TestPKIConfigurationGenerate(t *testing.T) { }, { name: "feature gate enabled, pki RSA-2048 explicit", - installConfig: &types.InstallConfig{ - FeatureSet: configv1.TechPreviewNoUpgrade, - PKI: &types.PKIConfig{ - SignerCertificates: types.CertificateConfig{ - Key: types.KeyConfig{ - Algorithm: types.KeyAlgorithmRSA, - RSA: &types.RSAKeyConfig{KeySize: 2048}, + signerKeyParams: &tls.SignerKeyParams{ + Profile: configv1alpha1.PKIProfile{ + Defaults: configv1alpha1.DefaultCertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmRSA, + RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096}, + }, + }, + SignerCertificates: configv1alpha1.CertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmRSA, + RSA: configv1alpha1.RSAKeyConfig{KeySize: 2048}, }, }, }, + ConfigurablePKIEnabled: true, }, expectEmpty: false, expectMode: configv1alpha1.PKICertificateManagementModeCustom, @@ -104,10 +122,10 @@ func TestPKIConfigurationGenerate(t *testing.T) { }, } - for _, tc := range cases { + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { parents := asset.Parents{} - parents.Add(installconfig.MakeAsset(tc.installConfig)) + parents.Add(tc.signerKeyParams) pkiAsset := &PKIConfiguration{} err := pkiAsset.Generate(context.Background(), parents) diff --git a/pkg/asset/tls/adminkubeconfig.go b/pkg/asset/tls/adminkubeconfig.go index 85be095f89d..5a76e0a3691 100644 --- a/pkg/asset/tls/adminkubeconfig.go +++ b/pkg/asset/tls/adminkubeconfig.go @@ -6,6 +6,7 @@ import ( "crypto/x509/pkix" "github.com/openshift/installer/pkg/asset" + libpki "github.com/openshift/library-go/pkg/pki" ) // AdminKubeConfigSignerCertKey is a key/cert pair that signs the admin kubeconfig client certs. @@ -29,14 +30,27 @@ func (c *AdminKubeConfigSignerCertKey) Dependencies() []asset.Asset { func (c *AdminKubeConfigSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { signerKeyParams := &SignerKeyParams{} parents.Get(signerKeyParams) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "admin-kubeconfig-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears(), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "admin-kubeconfig-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "installer.admin-kubeconfig-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "admin-kubeconfig-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "admin-kubeconfig-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityTenYears(), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "admin-kubeconfig-signer", signerKeyParams.PKIConfig) + return c.SelfSignedCertKey.Generate(ctx, cfg, "admin-kubeconfig-signer", keyGen) } // Load reads the asset files from disk. @@ -79,35 +93,52 @@ func (a *AdminKubeConfigCABundle) Name() string { return "Certificate (admin-kubeconfig-ca-bundle)" } -// AdminKubeConfigClientCertKey is the asset that generates the key/cert pair for admin client to apiserver. +// AdminKubeConfigClientCertKey is the asset that generates the client +// cert/key pair embedded in the admin kubeconfig. type AdminKubeConfigClientCertKey struct { SignedCertKey } var _ asset.WritableAsset = (*AdminKubeConfigClientCertKey)(nil) -// Dependencies returns the dependency of the the cert/key pair, which includes -// the parent CA, and install config if it depends on the install config for -// DNS names, etc. +// Dependencies returns the dependencies. func (a *AdminKubeConfigClientCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &AdminKubeConfigSignerCertKey{}, + &SignerKeyParams{}, } } // Generate generates the cert/key pair based on its dependencies. func (a *AdminKubeConfigClientCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &AdminKubeConfigSignerCertKey{} - dependencies.Get(ca) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears(), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "admin-kubeconfig-client", DoNotAppendParent, nil) + } + // The legacy path includes ServerAuth for backward compatibility, but this + // cert is only used as a client credential (including localhost-recovery). + // The library-go path uses ClientAuth only. + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "installer.admin-kubeconfig-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityTenYears(), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "admin-kubeconfig-client", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "admin-kubeconfig-client", DoNotAppendParent, keyGen) } // Load reads the asset files from disk. diff --git a/pkg/asset/tls/aggregator.go b/pkg/asset/tls/aggregator.go index e4a91b197e4..dc95a40262d 100644 --- a/pkg/asset/tls/aggregator.go +++ b/pkg/asset/tls/aggregator.go @@ -7,7 +7,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - pkidefaults "github.com/openshift/installer/pkg/types/pki" + libpki "github.com/openshift/library-go/pkg/pki" ) // AggregatorCA is the asset that generates the aggregator-ca key/cert pair. @@ -23,23 +23,37 @@ var _ asset.Asset = (*AggregatorCA)(nil) // DNS names, etc. func (a *AggregatorCA) Dependencies() []asset.Asset { return []asset.Asset{ + &SignerKeyParams{}, &installconfig.InstallConfig{}, } } // Generate generates the cert/key pair based on its dependencies. func (a *AggregatorCA) Generate(ctx context.Context, dependencies asset.Parents) error { + signerKeyParams := &SignerKeyParams{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(installConfig) + dependencies.Get(signerKeyParams, installConfig) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityOneDay(installConfig), + IsCA: true, + } + return a.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-ca", nil) + } + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.aggregator-front-proxy-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, Validity: ValidityOneDay(installConfig), IsCA: true, } - - return a.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-ca", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config)) + return a.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-ca", keyGen) } // Name returns the human-friendly name of the asset. @@ -62,6 +76,7 @@ func (a *APIServerProxyCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &AggregatorCA{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -69,16 +84,30 @@ func (a *APIServerProxyCertKey) Dependencies() []asset.Asset { func (a *APIServerProxyCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { aggregatorCA := &AggregatorCA{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(aggregatorCA, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(aggregatorCA, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver-proxy", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityOneDay(installConfig), + } + return a.SignedCertKey.Generate(ctx, cfg, aggregatorCA, "apiserver-proxy", DoNotAppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "kube-apiserver.aggregator-front-proxy-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver-proxy", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityOneDay(installConfig), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, aggregatorCA, "apiserver-proxy", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, aggregatorCA, "apiserver-proxy", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. @@ -95,21 +124,35 @@ var _ asset.WritableAsset = (*AggregatorSignerCertKey)(nil) // Dependencies returns the dependency of the root-ca, which is empty. func (c *AggregatorSignerCertKey) Dependencies() []asset.Asset { - return []asset.Asset{&installconfig.InstallConfig{}} + return []asset.Asset{&SignerKeyParams{}, &installconfig.InstallConfig{}} } // Generate generates the root-ca key and cert pair. func (c *AggregatorSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { + signerKeyParams := &SignerKeyParams{} installConfig := &installconfig.InstallConfig{} - parents.Get(installConfig) + parents.Get(signerKeyParams, installConfig) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "aggregator-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityOneDay(installConfig), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.aggregator-front-proxy-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "aggregator-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "aggregator-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityOneDay(installConfig), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config)) + return c.SelfSignedCertKey.Generate(ctx, cfg, "aggregator-signer", keyGen) } // Name returns the human-friendly name of the asset. @@ -159,6 +202,7 @@ func (a *AggregatorClientCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &AggregatorSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -166,16 +210,30 @@ func (a *AggregatorClientCertKey) Dependencies() []asset.Asset { func (a *AggregatorClientCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &AggregatorSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver-proxy", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityOneDay(installConfig), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "aggregator-client", DoNotAppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "kube-apiserver.aggregator-front-proxy-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver-proxy", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityOneDay(installConfig), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "aggregator-client", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "aggregator-client", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/apiserver.go b/pkg/asset/tls/apiserver.go index 59412e91ee7..679c6f0fbb9 100644 --- a/pkg/asset/tls/apiserver.go +++ b/pkg/asset/tls/apiserver.go @@ -10,7 +10,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - pkidefaults "github.com/openshift/installer/pkg/types/pki" + libpki "github.com/openshift/library-go/pkg/pki" ) // KubeAPIServerToKubeletSignerCertKey is a key/cert pair that signs the kube-apiserver to kubelet client certs. @@ -22,21 +22,35 @@ var _ asset.WritableAsset = (*KubeAPIServerToKubeletSignerCertKey)(nil) // Dependencies returns the dependency of the root-ca, which is empty. func (c *KubeAPIServerToKubeletSignerCertKey) Dependencies() []asset.Asset { - return []asset.Asset{&installconfig.InstallConfig{}} + return []asset.Asset{&SignerKeyParams{}, &installconfig.InstallConfig{}} } // Generate generates the root-ca key and cert pair. func (c *KubeAPIServerToKubeletSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { + signerKeyParams := &SignerKeyParams{} installConfig := &installconfig.InstallConfig{} - parents.Get(installConfig) + parents.Get(signerKeyParams, installConfig) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-apiserver-to-kubelet-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityOneYear(installConfig), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-to-kubelet-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.kubelet-client-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kube-apiserver-to-kubelet-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kube-apiserver-to-kubelet-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityOneYear(installConfig), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-to-kubelet-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config)) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-to-kubelet-signer", keyGen) } // Name returns the human-friendly name of the asset. @@ -86,6 +100,7 @@ func (a *KubeAPIServerToKubeletClientCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &KubeAPIServerToKubeletSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -93,16 +108,30 @@ func (a *KubeAPIServerToKubeletClientCertKey) Dependencies() []asset.Asset { func (a *KubeAPIServerToKubeletClientCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeAPIServerToKubeletSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityOneYear(installConfig), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-to-kubelet-client", DoNotAppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "kube-apiserver.kubelet-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityOneYear(installConfig), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-to-kubelet-client", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-to-kubelet-client", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. @@ -131,14 +160,27 @@ func (c *KubeAPIServerLocalhostSignerCertKey) Dependencies() []asset.Asset { func (c *KubeAPIServerLocalhostSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { signerKeyParams := &SignerKeyParams{} parents.Get(signerKeyParams) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-apiserver-localhost-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears(), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-localhost-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.localhost-serving-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kube-apiserver-localhost-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kube-apiserver-localhost-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityTenYears(), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-localhost-signer", signerKeyParams.PKIConfig) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-localhost-signer", keyGen) } // Load reads the asset files from disk. @@ -193,6 +235,7 @@ func (a *KubeAPIServerLocalhostServerCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &KubeAPIServerLocalhostSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -200,20 +243,38 @@ func (a *KubeAPIServerLocalhostServerCertKey) Dependencies() []asset.Asset { func (a *KubeAPIServerLocalhostServerCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeAPIServerLocalhostSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityOneDay(installConfig), + DNSNames: []string{ + "localhost", + }, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-localhost-server", AppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeServing, "kube-apiserver.localhost-serving") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, Validity: ValidityOneDay(installConfig), DNSNames: []string{ "localhost", }, IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, + CertType: libpki.CertificateTypeServing, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-localhost-server", AppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-localhost-server", AppendParent, keyGen) } // Name returns the human-friendly name of the asset. @@ -242,14 +303,27 @@ func (c *KubeAPIServerServiceNetworkSignerCertKey) Dependencies() []asset.Asset func (c *KubeAPIServerServiceNetworkSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { signerKeyParams := &SignerKeyParams{} parents.Get(signerKeyParams) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-apiserver-service-network-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears(), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-service-network-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.service-network-serving-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kube-apiserver-service-network-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kube-apiserver-service-network-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityTenYears(), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-service-network-signer", signerKeyParams.PKIConfig) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-service-network-signer", keyGen) } // Load reads the asset files from disk. @@ -304,6 +378,7 @@ func (a *KubeAPIServerServiceNetworkServerCertKey) Dependencies() []asset.Asset return []asset.Asset{ &KubeAPIServerServiceNetworkSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -311,15 +386,38 @@ func (a *KubeAPIServerServiceNetworkServerCertKey) Dependencies() []asset.Asset func (a *KubeAPIServerServiceNetworkServerCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeAPIServerServiceNetworkSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) serviceAddress, err := cidrhost(installConfig.Config.Networking.ServiceNetwork[0].IPNet, 1) if err != nil { return errors.Wrap(err, "failed to get service address for kube-apiserver from InstallConfig") } + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityOneDay(installConfig), + DNSNames: []string{ + "kubernetes", "kubernetes.default", + "kubernetes.default.svc", + "kubernetes.default.svc.cluster.local", + "openshift", "openshift.default", + "openshift.default.svc", + "openshift.default.svc.cluster.local", + }, + IPAddresses: []net.IP{net.ParseIP(serviceAddress)}, + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-service-network-server", AppendParent, nil) + } + + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeServing, "kube-apiserver.service-network-serving") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, Validity: ValidityOneDay(installConfig), DNSNames: []string{ @@ -331,9 +429,9 @@ func (a *KubeAPIServerServiceNetworkServerCertKey) Generate(ctx context.Context, "openshift.default.svc.cluster.local", }, IPAddresses: []net.IP{net.ParseIP(serviceAddress)}, + CertType: libpki.CertificateTypeServing, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-service-network-server", AppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-service-network-server", AppendParent, keyGen) } // Name returns the human-friendly name of the asset. @@ -362,14 +460,27 @@ func (c *KubeAPIServerLBSignerCertKey) Dependencies() []asset.Asset { func (c *KubeAPIServerLBSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { signerKeyParams := &SignerKeyParams{} parents.Get(signerKeyParams) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-apiserver-lb-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears(), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-lb-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.loadbalancer-serving-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kube-apiserver-lb-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kube-apiserver-lb-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityTenYears(), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-lb-signer", signerKeyParams.PKIConfig) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-apiserver-lb-signer", keyGen) } // Load reads the asset files from disk. @@ -424,6 +535,7 @@ func (a *KubeAPIServerExternalLBServerCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &KubeAPIServerLBSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -431,19 +543,36 @@ func (a *KubeAPIServerExternalLBServerCertKey) Dependencies() []asset.Asset { func (a *KubeAPIServerExternalLBServerCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeAPIServerLBSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityOneDay(installConfig), + DNSNames: []string{ + apiAddress(installConfig.Config), + }, + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-lb-server", AppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeServing, "kube-apiserver.external-loadbalancer-serving") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, Validity: ValidityOneDay(installConfig), DNSNames: []string{ apiAddress(installConfig.Config), }, + CertType: libpki.CertificateTypeServing, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-lb-server", AppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-lb-server", AppendParent, keyGen) } // Name returns the human-friendly name of the asset. @@ -463,6 +592,7 @@ func (a *KubeAPIServerInternalLBServerCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &KubeAPIServerLBSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -470,19 +600,36 @@ func (a *KubeAPIServerInternalLBServerCertKey) Dependencies() []asset.Asset { func (a *KubeAPIServerInternalLBServerCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeAPIServerLBSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityOneDay(installConfig), + DNSNames: []string{ + internalAPIAddress(installConfig.Config), + }, + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-internal-lb-server", AppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeServing, "kube-apiserver.internal-loadbalancer-serving") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:kube-apiserver", Organization: []string{"kube-master"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, Validity: ValidityOneDay(installConfig), DNSNames: []string{ internalAPIAddress(installConfig.Config), }, + CertType: libpki.CertificateTypeServing, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-internal-lb-server", AppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-apiserver-internal-lb-server", AppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/certkey.go b/pkg/asset/tls/certkey.go index f51d93ad1ca..ebceb44e22b 100644 --- a/pkg/asset/tls/certkey.go +++ b/pkg/asset/tls/certkey.go @@ -3,13 +3,18 @@ package tls import ( "bytes" "context" + "crypto/x509" "fmt" "os" "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/authentication/user" "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/types" + libcrypto "github.com/openshift/library-go/pkg/crypto" + libpki "github.com/openshift/library-go/pkg/pki" ) // CertInterface contains cert. @@ -121,42 +126,107 @@ type SignedCertKey struct { } // Generate generates a cert/key pair signed by the specified parent CA. +// When keyGen is nil, uses the legacy path (GenerateSignedCertificate with RSA 2048 keys). +// When keyGen is non-nil, uses library-go based on cfg.CertType (server/client/peer). func (c *SignedCertKey) Generate(_ context.Context, cfg *CertCfg, parentCA CertKeyInterface, filenameBase string, appendParent AppendParentChoice, + keyGen libcrypto.KeyPairGenerator, ) error { - caKey, err := PemToPrivateKey(parentCA.Key()) - if err != nil { - logrus.Debugf("Failed to parse private key: %s", err) - return fmt.Errorf("failed to parse private key: %w", err) + if keyGen == nil { + // Legacy path + caKey, err := PemToPrivateKey(parentCA.Key()) + if err != nil { + logrus.Debugf("Failed to parse private key: %s", err) + return fmt.Errorf("failed to parse private key: %w", err) + } + + caCert, err := PemToCertificate(parentCA.Cert()) + if err != nil { + logrus.Debugf("Failed to parse x509 certificate: %s", err) + return fmt.Errorf("failed to parse x509 certificate: %w", err) + } + + key, crt, err := GenerateSignedCertificate(caKey, caCert, cfg) + if err != nil { + logrus.Debugf("Failed to generate signed cert/key pair: %s", err) + return fmt.Errorf("failed to generate signed cert/key pair: %w", err) + } + + c.KeyRaw, err = PrivateKeyToPem(key) + if err != nil { + return fmt.Errorf("failed to encode private key to PEM: %w", err) + } + c.CertRaw = CertToPem(crt) + + if appendParent { + c.CertRaw = bytes.Join([][]byte{c.CertRaw, CertToPem(caCert)}, []byte("\n")) + } + + c.generateFiles(filenameBase) + return nil } - caCert, err := PemToCertificate(parentCA.Cert()) + // Library-go path + ca, err := libcrypto.GetCAFromBytes(parentCA.Cert(), parentCA.Key()) if err != nil { - logrus.Debugf("Failed to parse x509 certificate: %s", err) - return fmt.Errorf("failed to parse x509 certificate: %w", err) + return fmt.Errorf("failed to parse parent CA: %w", err) } - key, crt, err := GenerateSignedCertificate(caKey, caCert, cfg) - if err != nil { - logrus.Debugf("Failed to generate signed cert/key pair: %s", err) - return fmt.Errorf("failed to generate signed cert/key pair: %w", err) + var tlsCfg *libcrypto.TLSCertificateConfig + opts := []libcrypto.CertificateOption{ + libcrypto.WithLifetime(cfg.Validity), + } + + switch cfg.CertType { + case libpki.CertificateTypeServing: + hostnames := hostnamesFromCfg(cfg) + if len(cfg.ExtKeyUsages) > 0 { + opts = append(opts, libcrypto.WithExtensions(func(template *x509.Certificate) error { + template.ExtKeyUsage = cfg.ExtKeyUsages + return nil + })) + } + tlsCfg, err = ca.NewServerCertificate(hostnames, keyGen, opts...) + case libpki.CertificateTypeClient: + u := userInfoFromCfg(cfg) + tlsCfg, err = ca.NewClientCertificate(u, keyGen, opts...) + case libpki.CertificateTypePeer: + hostnames := hostnamesFromCfg(cfg) + u := userInfoFromCfg(cfg) + if len(cfg.ExtKeyUsages) > 0 { + opts = append(opts, libcrypto.WithExtensions(func(template *x509.Certificate) error { + template.ExtKeyUsage = cfg.ExtKeyUsages + return nil + })) + } + tlsCfg, err = ca.NewPeerCertificate(hostnames, u, keyGen, opts...) + default: + return fmt.Errorf("unsupported certificate type: %v", cfg.CertType) } - c.KeyRaw, err = PrivateKeyToPem(key) if err != nil { - return fmt.Errorf("failed to encode private key to PEM: %w", err) + return fmt.Errorf("failed to generate signed certificate: %w", err) } - c.CertRaw = CertToPem(crt) + // Encode cert and key if appendParent { - c.CertRaw = bytes.Join([][]byte{c.CertRaw, CertToPem(caCert)}, []byte("\n")) + c.CertRaw, err = libcrypto.EncodeCertificates(tlsCfg.Certs...) + } else { + c.CertRaw, err = libcrypto.EncodeCertificates(tlsCfg.Certs[0]) + } + if err != nil { + return fmt.Errorf("failed to encode certificates: %w", err) } - c.generateFiles(filenameBase) + c.KeyRaw, err = libcrypto.EncodeKey(tlsCfg.Key) + if err != nil { + return fmt.Errorf("failed to encode private key: %w", err) + } + c.generateFiles(filenameBase) return nil } @@ -166,12 +236,69 @@ type SelfSignedCertKey struct { } // Generate generates a self-signed cert/key pair using the specified PKI profile. +// When keyGen is nil, uses the legacy path (PKIConfigToKeyParams(nil) + GenerateSelfSignedCertificate). +// When keyGen is non-nil and cfg.IsCA, uses libcrypto.NewSigningCertificate (library-go path). +// When keyGen is non-nil and not IsCA, converts keyGen to PrivateKeyParams and uses legacy GenerateSelfSignedCertificate. func (c *SelfSignedCertKey) Generate(_ context.Context, cfg *CertCfg, filenameBase string, - pkiConfig *types.PKIConfig, + keyGen libcrypto.KeyPairGenerator, ) error { - params := PKIConfigToKeyParams(pkiConfig) + if keyGen == nil { + // Legacy path + params := PKIConfigToKeyParams(nil) + key, crt, err := GenerateSelfSignedCertificate(cfg, params) + if err != nil { + return fmt.Errorf("failed to generate self-signed cert/key pair: %w", err) + } + c.KeyRaw, err = PrivateKeyToPem(key) + if err != nil { + return fmt.Errorf("failed to encode private key to PEM: %w", err) + } + c.CertRaw = CertToPem(crt) + c.generateFiles(filenameBase) + return nil + } + + if cfg.IsCA { + return c.generateSigningCert(cfg, filenameBase, keyGen) + } + return c.generateSelfSignedCert(cfg, filenameBase, keyGen) +} + +// generateSigningCert generates a self-signed CA certificate using library-go. +func (c *SelfSignedCertKey) generateSigningCert(cfg *CertCfg, filenameBase string, keyGen libcrypto.KeyPairGenerator) error { + var opts []libcrypto.CertificateOption + if cfg.Subject.CommonName != "" || len(cfg.Subject.OrganizationalUnit) > 0 { + opts = append(opts, libcrypto.WithSubject(cfg.Subject)) + } + opts = append(opts, libcrypto.WithLifetime(cfg.Validity)) + + tlsCfg, err := libcrypto.NewSigningCertificate(filenameBase, keyGen, opts...) + if err != nil { + return fmt.Errorf("failed to generate signing certificate: %w", err) + } + + c.CertRaw, err = libcrypto.EncodeCertificates(tlsCfg.Certs...) + if err != nil { + return fmt.Errorf("failed to encode certificates: %w", err) + } + + c.KeyRaw, err = libcrypto.EncodeKey(tlsCfg.Key) + if err != nil { + return fmt.Errorf("failed to encode private key: %w", err) + } + + c.generateFiles(filenameBase) + return nil +} + +// generateSelfSignedCert generates a self-signed non-CA certificate using the legacy path. +func (c *SelfSignedCertKey) generateSelfSignedCert(cfg *CertCfg, filenameBase string, keyGen libcrypto.KeyPairGenerator) error { + params, err := keyGenToParams(keyGen) + if err != nil { + return fmt.Errorf("failed to convert keyGen to PrivateKeyParams: %w", err) + } key, crt, err := GenerateSelfSignedCertificate(cfg, params) if err != nil { @@ -185,7 +312,6 @@ func (c *SelfSignedCertKey) Generate(_ context.Context, c.CertRaw = CertToPem(crt) c.generateFiles(filenameBase) - return nil } @@ -226,3 +352,52 @@ func RegenerateSignedCertKey( return keyRaw, certRaw, nil } + +// hostnamesFromCfg extracts hostnames (DNS names and IP addresses) from CertCfg. +func hostnamesFromCfg(cfg *CertCfg) sets.Set[string] { + hostnames := sets.New[string]() + for _, dns := range cfg.DNSNames { + hostnames.Insert(dns) + } + for _, ip := range cfg.IPAddresses { + hostnames.Insert(ip.String()) + } + return hostnames +} + +// userInfoFromCfg converts CertCfg.Subject to user.Info for library-go client cert generation. +func userInfoFromCfg(cfg *CertCfg) user.Info { + return &user.DefaultInfo{ + Name: cfg.Subject.CommonName, + Groups: cfg.Subject.Organization, + } +} + +// keyGenToParams converts a libcrypto.KeyPairGenerator to PrivateKeyParams. +func keyGenToParams(keyGen libcrypto.KeyPairGenerator) (PrivateKeyParams, error) { + switch kg := keyGen.(type) { + case libcrypto.RSAKeyPairGenerator: + return PrivateKeyParams{ + Algorithm: types.KeyAlgorithmRSA, + RSAKeySize: int32(kg.Bits), + }, nil + case libcrypto.ECDSAKeyPairGenerator: + var curve types.ECDSACurve + switch kg.Curve { + case libcrypto.P256: + curve = types.ECDSACurveP256 + case libcrypto.P384: + curve = types.ECDSACurveP384 + case libcrypto.P521: + curve = types.ECDSACurveP521 + default: + return PrivateKeyParams{}, fmt.Errorf("unsupported ECDSA curve: %v", kg.Curve) + } + return PrivateKeyParams{ + Algorithm: types.KeyAlgorithmECDSA, + ECDSACurve: curve, + }, nil + default: + return PrivateKeyParams{}, fmt.Errorf("unsupported KeyPairGenerator type: %T", keyGen) + } +} diff --git a/pkg/asset/tls/certkey_test.go b/pkg/asset/tls/certkey_test.go index 4ceee86a07f..973705d3586 100644 --- a/pkg/asset/tls/certkey_test.go +++ b/pkg/asset/tls/certkey_test.go @@ -2,7 +2,6 @@ package tls import ( "context" - "crypto/ecdsa" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" @@ -11,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/openshift/installer/pkg/types" + libcrypto "github.com/openshift/library-go/pkg/crypto" ) func TestSignedCertKeyGenerate(t *testing.T) { @@ -60,7 +59,7 @@ func TestSignedCertKeyGenerate(t *testing.T) { assert.NoError(t, err, "failed to generate root CA") certKey := &SignedCertKey{} - err = certKey.Generate(context.Background(), tt.certCfg, rootCA, tt.filenameBase, tt.appendParent) + err = certKey.Generate(context.Background(), tt.certCfg, rootCA, tt.filenameBase, tt.appendParent, nil) if err != nil { assert.EqualErrorf(t, err, tt.errString, tt.name) return @@ -100,42 +99,51 @@ func TestSignedCertKeyGenerate(t *testing.T) { } } -func TestSelfSignedCertKeyGenerateWithPKIConfig(t *testing.T) { - cases := []struct { +func TestSelfSignedCertKeyGenerateLegacyPath(t *testing.T) { + // Test the legacy path (keyGen = nil) which uses PKIConfigToKeyParams(nil) + // and generates RSA 2048 by default. + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "test-legacy-ca", OrganizationalUnit: []string{"openshift"}}, + Validity: ValidityTenYears(), + IsCA: true, + } + + ca := &SelfSignedCertKey{} + err := ca.Generate(t.Context(), cfg, "test-legacy-ca", nil) + assert.NoError(t, err) + + key, err := PemToPrivateKey(ca.Key()) + assert.NoError(t, err) + assert.IsType(t, &rsa.PrivateKey{}, key) + + rsaKey := key.(*rsa.PrivateKey) + assert.Equal(t, 2048, rsaKey.N.BitLen(), "legacy path should generate RSA 2048") + + cert, err := PemToCertificate(ca.Cert()) + assert.NoError(t, err) + assert.Equal(t, x509.RSA, cert.PublicKeyAlgorithm) + assert.True(t, cert.IsCA) +} + +func TestSelfSignedCertKeyGenerateWithKeyGen(t *testing.T) { + testCases := []struct { name string - pkiConfig *types.PKIConfig - expectKeyType interface{} + keyGen libcrypto.KeyPairGenerator expectPubKeyAlg x509.PublicKeyAlgorithm }{ { - name: "RSA 4096", - pkiConfig: &types.PKIConfig{ - SignerCertificates: types.CertificateConfig{ - Key: types.KeyConfig{ - Algorithm: types.KeyAlgorithmRSA, - RSA: &types.RSAKeyConfig{KeySize: 4096}, - }, - }, - }, - expectKeyType: &rsa.PrivateKey{}, + name: "RSA 4096", + keyGen: libcrypto.RSAKeyPairGenerator{Bits: 4096}, expectPubKeyAlg: x509.RSA, }, { - name: "ECDSA P384", - pkiConfig: &types.PKIConfig{ - SignerCertificates: types.CertificateConfig{ - Key: types.KeyConfig{ - Algorithm: types.KeyAlgorithmECDSA, - ECDSA: &types.ECDSAKeyConfig{Curve: types.ECDSACurveP384}, - }, - }, - }, - expectKeyType: &ecdsa.PrivateKey{}, + name: "ECDSA P384", + keyGen: libcrypto.ECDSAKeyPairGenerator{Curve: libcrypto.P384}, expectPubKeyAlg: x509.ECDSA, }, } - for _, tc := range cases { + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cfg := &CertCfg{ Subject: pkix.Name{CommonName: "test-pki-ca", OrganizationalUnit: []string{"openshift"}}, @@ -144,20 +152,9 @@ func TestSelfSignedCertKeyGenerateWithPKIConfig(t *testing.T) { } ca := &SelfSignedCertKey{} - err := ca.Generate(t.Context(), cfg, "test-pki-ca", tc.pkiConfig) + err := ca.Generate(context.Background(), cfg, "test-pki-ca", tc.keyGen) assert.NoError(t, err) - key, err := PemToPrivateKey(ca.Key()) - assert.NoError(t, err) - assert.IsType(t, tc.expectKeyType, key) - - switch k := key.(type) { - case *rsa.PrivateKey: - assert.Equal(t, 4096, k.N.BitLen()) - case *ecdsa.PrivateKey: - assert.Equal(t, "P-384", k.Curve.Params().Name) - } - cert, err := PemToCertificate(ca.Cert()) assert.NoError(t, err) assert.Equal(t, tc.expectPubKeyAlg, cert.PublicKeyAlgorithm) @@ -165,61 +162,3 @@ func TestSelfSignedCertKeyGenerateWithPKIConfig(t *testing.T) { }) } } - -func TestCrossAlgorithmCertificateSigning(t *testing.T) { - // Generate ECDSA P384 CA - ecdsaPKI := &types.PKIConfig{ - SignerCertificates: types.CertificateConfig{ - Key: types.KeyConfig{ - Algorithm: types.KeyAlgorithmECDSA, - ECDSA: &types.ECDSAKeyConfig{Curve: types.ECDSACurveP384}, - }, - }, - } - rootCA := &SelfSignedCertKey{} - rootCACfg := &CertCfg{ - Subject: pkix.Name{CommonName: "ecdsa-ca", OrganizationalUnit: []string{"openshift"}}, - Validity: ValidityTenYears(), - IsCA: true, - } - err := rootCA.Generate(t.Context(), rootCACfg, "ecdsa-ca", ecdsaPKI) - assert.NoError(t, err) - - // Verify CA key is ECDSA - caKey, err := PemToPrivateKey(rootCA.Key()) - assert.NoError(t, err) - assert.IsType(t, &ecdsa.PrivateKey{}, caKey) - - // Generate RSA leaf signed by ECDSA CA - leafCfg := &CertCfg{ - Subject: pkix.Name{CommonName: "leaf-cert", OrganizationalUnit: []string{"openshift"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - Validity: ValidityTenYears(), - DNSNames: []string{"test.openshift.io"}, - } - certKey := &SignedCertKey{} - err = certKey.Generate(t.Context(), leafCfg, rootCA, "cross-algo-leaf", DoNotAppendParent) - assert.NoError(t, err) - - // Verify leaf key is RSA (SignedCertKey always generates RSA leaf keys) - leafKey, err := PemToPrivateKey(certKey.Key()) - assert.NoError(t, err) - assert.IsType(t, &rsa.PrivateKey{}, leafKey) - - // Verify the leaf cert was signed by the ECDSA CA - leafCert, err := PemToCertificate(certKey.Cert()) - assert.NoError(t, err) - assert.Equal(t, x509.ECDSAWithSHA384, leafCert.SignatureAlgorithm) - - // Verify cert chain: leaf validates against CA - caCert, err := PemToCertificate(rootCA.Cert()) - assert.NoError(t, err) - certPool := x509.NewCertPool() - certPool.AddCert(caCert) - _, err = leafCert.Verify(x509.VerifyOptions{ - Roots: certPool, - DNSName: "test.openshift.io", - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - }) - assert.NoError(t, err, "leaf cert should validate against ECDSA CA") -} diff --git a/pkg/asset/tls/certnames.go b/pkg/asset/tls/certnames.go new file mode 100644 index 00000000000..61038cf8d60 --- /dev/null +++ b/pkg/asset/tls/certnames.go @@ -0,0 +1,24 @@ +package tls + +import ( + "fmt" + + libcrypto "github.com/openshift/library-go/pkg/crypto" + libpki "github.com/openshift/library-go/pkg/pki" +) + +// resolveSignerKeyGen resolves the KeyPairGenerator for a signer certificate +// from the SignerKeyParams's profile. +func resolveSignerKeyGen(pkiCfg *SignerKeyParams, certName string) (libcrypto.KeyPairGenerator, error) { + return resolveKeyGen(pkiCfg, libpki.CertificateTypeSigner, certName) +} + +// resolveKeyGen resolves the KeyPairGenerator for a certificate of the given type. +func resolveKeyGen(pkiCfg *SignerKeyParams, certType libpki.CertificateType, certName string) (libcrypto.KeyPairGenerator, error) { + provider := libpki.NewStaticPKIProfileProvider(&pkiCfg.Profile) + resolved, err := libpki.ResolveCertificateConfig(provider, certType, certName) + if err != nil { + return nil, fmt.Errorf("failed to resolve PKI config for %s certificate %q: %w", certType, certName, err) + } + return resolved.Key, nil +} diff --git a/pkg/asset/tls/iricertkey.go b/pkg/asset/tls/iricertkey.go index 79e34e9c14e..d190916ef27 100644 --- a/pkg/asset/tls/iricertkey.go +++ b/pkg/asset/tls/iricertkey.go @@ -12,6 +12,7 @@ import ( baremetaltypes "github.com/openshift/installer/pkg/types/baremetal" nutanixtypes "github.com/openshift/installer/pkg/types/nutanix" vspheretypes "github.com/openshift/installer/pkg/types/vsphere" + libpki "github.com/openshift/library-go/pkg/pki" ) // IRICertKey is the asset that generates the InternalReleaseImage registry key/cert pair. @@ -29,6 +30,7 @@ func (a *IRICertKey) Dependencies() []asset.Asset { &RootCA{}, &installconfig.InstallConfig{}, &manifests.InternalReleaseImage{}, + &SignerKeyParams{}, } } @@ -37,7 +39,8 @@ func (a *IRICertKey) Generate(ctx context.Context, dependencies asset.Parents) e ca := &RootCA{} installConfig := &installconfig.InstallConfig{} iri := &manifests.InternalReleaseImage{} - dependencies.Get(ca, installConfig, iri) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, iri, pkiCfg) // Skip if InternalReleaseImage manifest wasn't found. if len(iri.FileList) == 0 { @@ -46,12 +49,6 @@ func (a *IRICertKey) Generate(ctx context.Context, dependencies asset.Parents) e apiInt := internalAPIAddress(installConfig.Config) - cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "system:internal-release-image"}, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - Validity: ValidityTenYears(), - } - var vips []string switch installConfig.Config.Platform.Name() { case baremetaltypes.Name: @@ -62,15 +59,50 @@ func (a *IRICertKey) Generate(ctx context.Context, dependencies asset.Parents) e vips = installConfig.Config.VSphere.APIVIPs } + localIPs := []string{ + "127.0.0.1", + "::1", + } + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:internal-release-image"}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityTenYears(), + } + cfg.IPAddresses = []net.IP{} + cfg.DNSNames = []string{ + "localhost", + apiInt, + } + for _, vip := range vips { + cfg.IPAddresses = append(cfg.IPAddresses, net.ParseIP(vip)) + cfg.DNSNames = append(cfg.DNSNames, vip) + } + for _, i := range localIPs { + if ip := net.ParseIP(i); ip != nil { + cfg.IPAddresses = append(cfg.IPAddresses, ip) + } + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "internal-release-image", DoNotAppendParent, nil) + } + + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeServing, "installer.internal-release-image-serving") + if err != nil { + return err + } + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:internal-release-image"}, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityTenYears(), + CertType: libpki.CertificateTypeServing, + } cfg.IPAddresses = []net.IP{} cfg.DNSNames = []string{ "localhost", apiInt, } - localIPs := []string{ - "127.0.0.1", - "::1", - } for _, vip := range vips { cfg.IPAddresses = append(cfg.IPAddresses, net.ParseIP(vip)) cfg.DNSNames = append(cfg.DNSNames, vip) @@ -80,8 +112,7 @@ func (a *IRICertKey) Generate(ctx context.Context, dependencies asset.Parents) e cfg.IPAddresses = append(cfg.IPAddresses, ip) } } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "internal-release-image", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "internal-release-image", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/journalcertkey.go b/pkg/asset/tls/journalcertkey.go index 79c787ea713..b9f3c955f51 100644 --- a/pkg/asset/tls/journalcertkey.go +++ b/pkg/asset/tls/journalcertkey.go @@ -6,38 +6,56 @@ import ( "crypto/x509/pkix" "github.com/openshift/installer/pkg/asset" + libpki "github.com/openshift/library-go/pkg/pki" ) -// JournalCertKey is the asset that generates the key/cert pair that is used to -// authenticate with journal-gatewayd on the bootstrap node. +// JournalCertKey is the asset that generates the key/cert pair for +// journal-gatewayd on the bootstrap node. This is a peer certificate: +// journal-gatewayd uses it to serve HTTPS (ServerAuth), and the +// installer uses the same cert/key to authenticate as a client when +// streaming logs via curl (ClientAuth). type JournalCertKey struct { SignedCertKey } var _ asset.WritableAsset = (*JournalCertKey)(nil) -// Dependencies returns the dependency of the the cert/key pair, which includes -// the parent CA, and install config if it depends on the install config for -// DNS names, etc. +// Dependencies returns the dependencies. func (a *JournalCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &RootCA{}, + &SignerKeyParams{}, } } // Generate generates the cert/key pair based on its dependencies. func (a *JournalCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &RootCA{} - dependencies.Get(ca) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, pkiCfg) + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "journal-gatewayd", Organization: []string{"OpenShift Bootstrap"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears(), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "journal-gatewayd", DoNotAppendParent, nil) + } + + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypePeer, "installer.journal-gateway") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "journal-gatewayd", Organization: []string{"OpenShift Bootstrap"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, Validity: ValidityTenYears(), + DNSNames: []string{"journal-gatewayd"}, + CertType: libpki.CertificateTypePeer, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "journal-gatewayd", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "journal-gatewayd", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/kubecontrolplane.go b/pkg/asset/tls/kubecontrolplane.go index d393774fb20..ba7a677ef89 100644 --- a/pkg/asset/tls/kubecontrolplane.go +++ b/pkg/asset/tls/kubecontrolplane.go @@ -7,7 +7,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - pkidefaults "github.com/openshift/installer/pkg/types/pki" + libpki "github.com/openshift/library-go/pkg/pki" ) // KubeControlPlaneSignerCertKey is a key/cert pair that signs the kube control-plane client certs. @@ -19,21 +19,35 @@ var _ asset.WritableAsset = (*KubeControlPlaneSignerCertKey)(nil) // Dependencies returns the dependency of the root-ca, which is empty. func (c *KubeControlPlaneSignerCertKey) Dependencies() []asset.Asset { - return []asset.Asset{&installconfig.InstallConfig{}} + return []asset.Asset{&SignerKeyParams{}, &installconfig.InstallConfig{}} } // Generate generates the root-ca key and cert pair. func (c *KubeControlPlaneSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { + signerKeyParams := &SignerKeyParams{} installConfig := &installconfig.InstallConfig{} - parents.Get(installConfig) + parents.Get(signerKeyParams, installConfig) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-control-plane-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityOneYear(installConfig), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-control-plane-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "kube-apiserver.control-plane-client-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kube-control-plane-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kube-control-plane-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityOneYear(installConfig), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-control-plane-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config)) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kube-control-plane-signer", keyGen) } // Name returns the human-friendly name of the asset. @@ -86,6 +100,7 @@ func (a *KubeControlPlaneKubeControllerManagerClientCertKey) Dependencies() []as return []asset.Asset{ &KubeControlPlaneSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -93,16 +108,30 @@ func (a *KubeControlPlaneKubeControllerManagerClientCertKey) Dependencies() []as func (a *KubeControlPlaneKubeControllerManagerClientCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeControlPlaneSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityOneYear(installConfig), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-control-plane-kube-controller-manager-client", DoNotAppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "kube-apiserver.kube-controller-manager-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityOneYear(installConfig), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-control-plane-kube-controller-manager-client", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-control-plane-kube-controller-manager-client", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. @@ -122,6 +151,7 @@ func (a *KubeControlPlaneKubeSchedulerClientCertKey) Dependencies() []asset.Asse return []asset.Asset{ &KubeControlPlaneSignerCertKey{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -129,16 +159,30 @@ func (a *KubeControlPlaneKubeSchedulerClientCertKey) Dependencies() []asset.Asse func (a *KubeControlPlaneKubeSchedulerClientCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeControlPlaneSignerCertKey{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityOneYear(installConfig), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-control-plane-kube-scheduler-client", DoNotAppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "kube-apiserver.kube-scheduler-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityOneYear(installConfig), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-control-plane-kube-scheduler-client", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kube-control-plane-kube-scheduler-client", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/kubelet.go b/pkg/asset/tls/kubelet.go index a7cf404b379..d09048b961b 100644 --- a/pkg/asset/tls/kubelet.go +++ b/pkg/asset/tls/kubelet.go @@ -7,7 +7,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - pkidefaults "github.com/openshift/installer/pkg/types/pki" + libpki "github.com/openshift/library-go/pkg/pki" ) // KubeletCSRSignerCertKey is a key/cert pair that signs the kubelet client certs. @@ -19,21 +19,35 @@ var _ asset.WritableAsset = (*KubeletCSRSignerCertKey)(nil) // Dependencies returns the dependency of the root-ca, which is empty. func (c *KubeletCSRSignerCertKey) Dependencies() []asset.Asset { - return []asset.Asset{&installconfig.InstallConfig{}} + return []asset.Asset{&SignerKeyParams{}, &installconfig.InstallConfig{}} } // Generate generates the root-ca key and cert pair. func (c *KubeletCSRSignerCertKey) Generate(ctx context.Context, parents asset.Parents) error { + signerKeyParams := &SignerKeyParams{} installConfig := &installconfig.InstallConfig{} - parents.Get(installConfig) + parents.Get(signerKeyParams, installConfig) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kubelet-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityOneDay(installConfig), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kubelet-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "installer.kubelet-csr-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kubelet-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kubelet-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityOneDay(installConfig), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kubelet-signer", pkidefaults.EffectiveSignerPKIConfig(installConfig.Config)) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kubelet-signer", keyGen) } // Name returns the human-friendly name of the asset. @@ -123,14 +137,27 @@ func (c *KubeletBootstrapCertSigner) Dependencies() []asset.Asset { func (c *KubeletBootstrapCertSigner) Generate(ctx context.Context, parents asset.Parents) error { signerKeyParams := &SignerKeyParams{} parents.Get(signerKeyParams) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kubelet-bootstrap-kubeconfig-signer", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears(), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "kubelet-bootstrap-kubeconfig-signer", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "installer.kubelet-bootstrap-kubeconfig-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "kubelet-bootstrap-kubeconfig-signer", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "kubelet-bootstrap-kubeconfig-signer", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityTenYears(), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "kubelet-bootstrap-kubeconfig-signer", signerKeyParams.PKIConfig) + return c.SelfSignedCertKey.Generate(ctx, cfg, "kubelet-bootstrap-kubeconfig-signer", keyGen) } // Name returns the human-friendly name of the asset. @@ -182,22 +209,37 @@ var _ asset.Asset = (*KubeletClientCertKey)(nil) func (a *KubeletClientCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &KubeletBootstrapCertSigner{}, + &SignerKeyParams{}, } } // Generate generates the cert/key pair based on its dependencies. func (a *KubeletClientCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &KubeletBootstrapCertSigner{} - dependencies.Get(ca) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, pkiCfg) + + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:serviceaccount:openshift-machine-config-operator:node-bootstrapper", Organization: []string{"system:serviceaccounts:openshift-machine-config-operator", "system:serviceaccounts"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears(), + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "kubelet-client", DoNotAppendParent, nil) + } + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeClient, "installer.kubelet-client") + if err != nil { + return err + } cfg := &CertCfg{ Subject: pkix.Name{CommonName: "system:serviceaccount:openshift-machine-config-operator:node-bootstrapper", Organization: []string{"system:serviceaccounts:openshift-machine-config-operator", "system:serviceaccounts"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Validity: ValidityTenYears(), + CertType: libpki.CertificateTypeClient, } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "kubelet-client", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "kubelet-client", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/mcscertkey.go b/pkg/asset/tls/mcscertkey.go index c41ae0efd7e..b1d4d0a904d 100644 --- a/pkg/asset/tls/mcscertkey.go +++ b/pkg/asset/tls/mcscertkey.go @@ -14,6 +14,7 @@ import ( ovirttypes "github.com/openshift/installer/pkg/types/ovirt" powervctypes "github.com/openshift/installer/pkg/types/powervc" vspheretypes "github.com/openshift/installer/pkg/types/vsphere" + libpki "github.com/openshift/library-go/pkg/pki" ) // MCSCertKey is the asset that generates the MCS key/cert pair. @@ -30,6 +31,7 @@ func (a *MCSCertKey) Dependencies() []asset.Asset { return []asset.Asset{ &RootCA{}, &installconfig.InstallConfig{}, + &SignerKeyParams{}, } } @@ -37,16 +39,11 @@ func (a *MCSCertKey) Dependencies() []asset.Asset { func (a *MCSCertKey) Generate(ctx context.Context, dependencies asset.Parents) error { ca := &RootCA{} installConfig := &installconfig.InstallConfig{} - dependencies.Get(ca, installConfig) + pkiCfg := &SignerKeyParams{} + dependencies.Get(ca, installConfig, pkiCfg) hostname := internalAPIAddress(installConfig.Config) - cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "system:machine-config-server"}, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - Validity: ValidityTenYears(), - } - var vips []string switch installConfig.Config.Platform.Name() { case baremetaltypes.Name: @@ -61,14 +58,39 @@ func (a *MCSCertKey) Generate(ctx context.Context, dependencies asset.Parents) e vips = installConfig.Config.VSphere.APIVIPs } + if !pkiCfg.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:machine-config-server"}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityTenYears(), + } + cfg.IPAddresses = []net.IP{} + cfg.DNSNames = []string{hostname} + for _, vip := range vips { + cfg.IPAddresses = append(cfg.IPAddresses, net.ParseIP(vip)) + cfg.DNSNames = append(cfg.DNSNames, vip) + } + return a.SignedCertKey.Generate(ctx, cfg, ca, "machine-config-server", DoNotAppendParent, nil) + } + + keyGen, err := resolveKeyGen(pkiCfg, libpki.CertificateTypeServing, "machine-config-operator.machine-config-server-serving") + if err != nil { + return err + } + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:machine-config-server"}, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityTenYears(), + CertType: libpki.CertificateTypeServing, + } cfg.IPAddresses = []net.IP{} cfg.DNSNames = []string{hostname} for _, vip := range vips { cfg.IPAddresses = append(cfg.IPAddresses, net.ParseIP(vip)) cfg.DNSNames = append(cfg.DNSNames, vip) } - - return a.SignedCertKey.Generate(ctx, cfg, ca, "machine-config-server", DoNotAppendParent) + return a.SignedCertKey.Generate(ctx, cfg, ca, "machine-config-server", DoNotAppendParent, keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/root.go b/pkg/asset/tls/root.go index 4984d0ca814..94858da93a3 100644 --- a/pkg/asset/tls/root.go +++ b/pkg/asset/tls/root.go @@ -2,6 +2,7 @@ package tls import ( "context" + "crypto/x509" "crypto/x509/pkix" "github.com/openshift/installer/pkg/asset" @@ -35,14 +36,27 @@ func (c *RootCA) Dependencies() []asset.Asset { func (c *RootCA) Generate(ctx context.Context, parents asset.Parents) error { signerKeyParams := &SignerKeyParams{} parents.Get(signerKeyParams) + + if !signerKeyParams.ConfigurablePKIEnabled { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "root-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears(), + IsCA: true, + } + return c.SelfSignedCertKey.Generate(ctx, cfg, "root-ca", nil) + } + + keyGen, err := resolveSignerKeyGen(signerKeyParams, "machine-config-operator.machine-config-server-signer") + if err != nil { + return err + } cfg := &CertCfg{ - Subject: pkix.Name{CommonName: "root-ca", OrganizationalUnit: []string{"openshift"}}, - // KeyUsages is set by GenerateSelfSignedCertificate based on the key algorithm. + Subject: pkix.Name{CommonName: "root-ca", OrganizationalUnit: []string{"openshift"}}, Validity: ValidityTenYears(), IsCA: true, } - - return c.SelfSignedCertKey.Generate(ctx, cfg, "root-ca", signerKeyParams.PKIConfig) + return c.SelfSignedCertKey.Generate(ctx, cfg, "root-ca", keyGen) } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/signerkey_params.go b/pkg/asset/tls/signerkey_params.go index 4b13dd7d71d..3acb25a2877 100644 --- a/pkg/asset/tls/signerkey_params.go +++ b/pkg/asset/tls/signerkey_params.go @@ -3,20 +3,27 @@ package tls //nolint:revive // pre-existing package name import ( "context" + configv1alpha1 "github.com/openshift/api/config/v1alpha1" "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/types" pkidefaults "github.com/openshift/installer/pkg/types/pki" ) -// SignerKeyParams resolves the effective PKI configuration for signer -// certificates. It has no asset dependencies and reads install-config.yaml +// SignerKeyParams resolves the effective PKI configuration for certificate +// generation. It has no asset dependencies and reads install-config.yaml // directly from disk so that signer certs can be generated without triggering // standard InstallConfig validation. When no install-config is present (e.g. -// agent create certificates, node-joiner add-nodes), it defaults to nil -// PKIConfig which maps to RSA-2048. +// agent create certificates, node-joiner add-nodes), Generate() leaves +// ConfigurablePKIEnabled false, which causes all cert assets to take the +// legacy RSA-2048 code path. type SignerKeyParams struct { - PKIConfig *types.PKIConfig + // Profile is the resolved PKI profile. Only meaningful when + // ConfigurablePKIEnabled is true. + Profile configv1alpha1.PKIProfile + + // ConfigurablePKIEnabled indicates whether the ConfigurablePKI feature + // gate is active. When false, cert assets take the legacy code path. + ConfigurablePKIEnabled bool } var _ asset.WritableAsset = (*SignerKeyParams)(nil) @@ -32,8 +39,9 @@ func (*SignerKeyParams) Dependencies() []asset.Asset { return []asset.Asset{} } -// Generate is a no-op that leaves PKIConfig as nil (RSA-2048). This is the -// fallback when Load() finds no install-config on disk (e.g. agent flow). +// Generate is the fallback when Load() finds no install-config on disk +// (e.g. agent flow). It leaves ConfigurablePKIEnabled false so all cert +// assets take the legacy RSA-2048 path. func (s *SignerKeyParams) Generate(_ context.Context, _ asset.Parents) error { return nil } @@ -45,7 +53,7 @@ func (*SignerKeyParams) Files() []*asset.File { // Load reads install-config.yaml through the standard LoadFromFile pipeline // (strict YAML, deprecated field conversion, defaults) and extracts the -// effective PKI config. Returns (false, nil) when the file is missing, +// effective PKI profile. Returns (false, nil) when the file is missing, // allowing the asset store to fall back to the state file between // multi-step invocations (e.g. create manifests followed by create cluster). // @@ -58,6 +66,6 @@ func (s *SignerKeyParams) Load(f asset.FileFetcher) (bool, error) { if !found || err != nil { return found, err } - s.PKIConfig = pkidefaults.EffectiveSignerPKIConfig(base.Config) + s.Profile, s.ConfigurablePKIEnabled = pkidefaults.EffectiveProfile(base.Config) return true, nil } diff --git a/pkg/asset/tls/tls.go b/pkg/asset/tls/tls.go index a9700e8a696..488da05b5ae 100644 --- a/pkg/asset/tls/tls.go +++ b/pkg/asset/tls/tls.go @@ -22,6 +22,7 @@ import ( features "github.com/openshift/api/features" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" + libpki "github.com/openshift/library-go/pkg/pki" ) const ( @@ -135,6 +136,7 @@ type CertCfg struct { Subject pkix.Name Validity time.Duration IsCA bool + CertType libpki.CertificateType } // rsaPublicKey reflects the ASN.1 structure of a PKCS#1 public key. diff --git a/pkg/types/pki/defaults.go b/pkg/types/pki/defaults.go index bae5edd9696..9857c8c0354 100644 --- a/pkg/types/pki/defaults.go +++ b/pkg/types/pki/defaults.go @@ -4,34 +4,41 @@ import ( configv1alpha1 "github.com/openshift/api/config/v1alpha1" features "github.com/openshift/api/features" "github.com/openshift/installer/pkg/types" + libpki "github.com/openshift/library-go/pkg/pki" ) -// DefaultPKIProfile returns the default PKI profile for OpenShift clusters. -// Currently uses RSA-4096 until all day-2 operators (CKAO, CKMO, etc.) support -// ECDSA certificate rotation. Once operator support lands, switch to ECDSA P-384 -// signers and ECDSA P-256 defaults to match the upstream library-go profile: -// https://github.com/openshift/library-go/blob/12d8376369b7c5b76f688d01089882ca28e351c3/pkg/pki/profile.go#L11-L26 -func DefaultPKIProfile() configv1alpha1.PKIProfile { - return configv1alpha1.PKIProfile{ - Defaults: configv1alpha1.DefaultCertificateConfig{ - Key: configv1alpha1.KeyConfig{ - Algorithm: configv1alpha1.KeyAlgorithmRSA, - RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096}, +// EffectiveProfile returns the resolved PKI profile and whether ConfigurablePKI is enabled. +// - Feature gate off: returns an explicit RSA-2048 profile (legacy behavior) and false. +// - Feature gate on, no user PKI: returns library-go's DefaultPKIProfile() and true. +// - Feature gate on, user PKI set: returns DefaultPKIProfile() with user's signerCertificates overlaid, and true. +func EffectiveProfile(ic *types.InstallConfig) (configv1alpha1.PKIProfile, bool) { + if ic == nil || !ic.Enabled(features.FeatureGateConfigurablePKI) { + rsa2048 := configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmRSA, + RSA: configv1alpha1.RSAKeyConfig{KeySize: 2048}, + } + return configv1alpha1.PKIProfile{ + Defaults: configv1alpha1.DefaultCertificateConfig{ + Key: rsa2048, }, - }, - SignerCertificates: configv1alpha1.CertificateConfig{ - Key: configv1alpha1.KeyConfig{ - Algorithm: configv1alpha1.KeyAlgorithmRSA, - RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096}, + SignerCertificates: configv1alpha1.CertificateConfig{ + Key: rsa2048, }, - }, + }, false + } + + profile := libpki.DefaultPKIProfile() + + if ic.PKI != nil { + profile.SignerCertificates = toAPICertificateConfig(ic.PKI.SignerCertificates) } + + return profile, true } // EffectiveSignerPKIConfig returns the effective PKI config for signer certificate generation. -// - If ConfigurablePKI feature gate is disabled, returns nil (RSA-2048 legacy path). -// - If user specified pki in install-config, returns that config unchanged. -// - If pki is nil, returns a PKIConfig derived from DefaultPKIProfile().SignerCertificates. +// +// Deprecated: Use EffectiveProfile via the SignerKeyParams asset instead. func EffectiveSignerPKIConfig(ic *types.InstallConfig) *types.PKIConfig { if ic == nil { return nil @@ -45,19 +52,25 @@ func EffectiveSignerPKIConfig(ic *types.InstallConfig) *types.PKIConfig { return ic.PKI } - profile := DefaultPKIProfile() - keyConfig := types.KeyConfig{ - Algorithm: types.KeyAlgorithm(profile.SignerCertificates.Key.Algorithm), - } - switch keyConfig.Algorithm { - case types.KeyAlgorithmRSA: - keyConfig.RSA = &types.RSAKeyConfig{KeySize: profile.SignerCertificates.Key.RSA.KeySize} - case types.KeyAlgorithmECDSA: - keyConfig.ECDSA = &types.ECDSAKeyConfig{Curve: types.ECDSACurve(profile.SignerCertificates.Key.ECDSA.Curve)} - } return &types.PKIConfig{ SignerCertificates: types.CertificateConfig{ - Key: keyConfig, + Key: types.KeyConfig{ + Algorithm: types.KeyAlgorithmECDSA, + ECDSA: &types.ECDSAKeyConfig{Curve: types.ECDSACurveP384}, + }, }, } } + +func toAPICertificateConfig(local types.CertificateConfig) configv1alpha1.CertificateConfig { + apiKey := configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithm(local.Key.Algorithm), + } + if local.Key.RSA != nil { + apiKey.RSA = configv1alpha1.RSAKeyConfig{KeySize: local.Key.RSA.KeySize} + } + if local.Key.ECDSA != nil { + apiKey.ECDSA = configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurve(local.Key.ECDSA.Curve)} + } + return configv1alpha1.CertificateConfig{Key: apiKey} +} diff --git a/pkg/types/pki/defaults_test.go b/pkg/types/pki/defaults_test.go index 3cc00622d15..a108b17e79a 100644 --- a/pkg/types/pki/defaults_test.go +++ b/pkg/types/pki/defaults_test.go @@ -10,13 +10,79 @@ import ( "github.com/openshift/installer/pkg/types" ) -func TestDefaultPKIProfile(t *testing.T) { - profile := DefaultPKIProfile() +func TestEffectiveProfile(t *testing.T) { + testCases := []struct { + name string + ic *types.InstallConfig + expectEnabled bool + expectDefaultsAlgo configv1alpha1.KeyAlgorithm + expectDefaultsCurve configv1alpha1.ECDSACurve + expectDefaultsRSASize int32 + expectSignerAlgo configv1alpha1.KeyAlgorithm + expectSignerCurve configv1alpha1.ECDSACurve + expectSignerRSASize int32 + }{ + { + name: "feature gate off - legacy RSA-2048", + ic: &types.InstallConfig{ + FeatureSet: configv1.Default, + }, + expectEnabled: false, + expectDefaultsAlgo: configv1alpha1.KeyAlgorithmRSA, + expectDefaultsRSASize: 2048, + expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA, + expectSignerRSASize: 2048, + }, + { + name: "feature gate on, pki nil - library-go defaults", + ic: &types.InstallConfig{ + FeatureSet: configv1.TechPreviewNoUpgrade, + }, + expectEnabled: true, + expectDefaultsAlgo: configv1alpha1.KeyAlgorithmECDSA, + expectDefaultsCurve: configv1alpha1.ECDSACurveP256, + expectSignerAlgo: configv1alpha1.KeyAlgorithmECDSA, + expectSignerCurve: configv1alpha1.ECDSACurveP384, + }, + { + name: "feature gate on, user RSA-4096 signers", + ic: &types.InstallConfig{ + FeatureSet: configv1.TechPreviewNoUpgrade, + PKI: &types.PKIConfig{ + SignerCertificates: types.CertificateConfig{ + Key: types.KeyConfig{ + Algorithm: types.KeyAlgorithmRSA, + RSA: &types.RSAKeyConfig{KeySize: 4096}, + }, + }, + }, + }, + expectEnabled: true, + expectDefaultsAlgo: configv1alpha1.KeyAlgorithmECDSA, + expectDefaultsCurve: configv1alpha1.ECDSACurveP256, + expectSignerAlgo: configv1alpha1.KeyAlgorithmRSA, + expectSignerRSASize: 4096, + }, + } - assert.Equal(t, configv1alpha1.KeyAlgorithmRSA, profile.Defaults.Key.Algorithm) - assert.Equal(t, int32(4096), profile.Defaults.Key.RSA.KeySize) - assert.Equal(t, configv1alpha1.KeyAlgorithmRSA, profile.SignerCertificates.Key.Algorithm) - assert.Equal(t, int32(4096), profile.SignerCertificates.Key.RSA.KeySize) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + profile, enabled := EffectiveProfile(tc.ic) + assert.Equal(t, tc.expectEnabled, enabled) + assert.Equal(t, tc.expectDefaultsAlgo, profile.Defaults.Key.Algorithm) + if tc.expectDefaultsAlgo == configv1alpha1.KeyAlgorithmECDSA { + assert.Equal(t, tc.expectDefaultsCurve, profile.Defaults.Key.ECDSA.Curve) + } else { + assert.Equal(t, tc.expectDefaultsRSASize, profile.Defaults.Key.RSA.KeySize) + } + assert.Equal(t, tc.expectSignerAlgo, profile.SignerCertificates.Key.Algorithm) + if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmECDSA { + assert.Equal(t, tc.expectSignerCurve, profile.SignerCertificates.Key.ECDSA.Curve) + } else { + assert.Equal(t, tc.expectSignerRSASize, profile.SignerCertificates.Key.RSA.KeySize) + } + }) + } } func TestEffectiveSignerPKIConfig(t *testing.T) { @@ -36,13 +102,13 @@ func TestEffectiveSignerPKIConfig(t *testing.T) { expectNil: true, }, { - name: "feature gate on, pki nil - returns RSA-4096 from DefaultPKIProfile", + name: "feature gate on, pki nil - returns ECDSA P-384 from library-go DefaultPKIProfile", ic: &types.InstallConfig{ FeatureSet: configv1.TechPreviewNoUpgrade, }, - expectNil: false, - expectAlgo: types.KeyAlgorithmRSA, - expectSize: 4096, + expectNil: false, + expectAlgo: types.KeyAlgorithmECDSA, + expectCurve: types.ECDSACurveP384, }, { name: "feature gate on, pki specified - returns user config", diff --git a/vendor/github.com/openshift/library-go/pkg/pki/profile.go b/vendor/github.com/openshift/library-go/pkg/pki/profile.go new file mode 100644 index 00000000000..6f534c94f0e --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/pki/profile.go @@ -0,0 +1,114 @@ +package pki + +import ( + "fmt" + + configv1alpha1 "github.com/openshift/api/config/v1alpha1" + "github.com/openshift/library-go/pkg/crypto" +) + +// DefaultPKIProfile returns the default PKIProfile for OpenShift. +func DefaultPKIProfile() configv1alpha1.PKIProfile { + return configv1alpha1.PKIProfile{ + Defaults: configv1alpha1.DefaultCertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmECDSA, + ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurveP256}, + }, + }, + SignerCertificates: configv1alpha1.CertificateConfig{ + Key: configv1alpha1.KeyConfig{ + Algorithm: configv1alpha1.KeyAlgorithmECDSA, + ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurveP384}, + }, + }, + } +} + +// KeyPairGeneratorFromAPI converts a configv1alpha1.KeyConfig to a +// crypto.KeyPairGenerator. +func KeyPairGeneratorFromAPI(apiKey configv1alpha1.KeyConfig) (crypto.KeyPairGenerator, error) { + switch apiKey.Algorithm { + case configv1alpha1.KeyAlgorithmRSA: + return crypto.RSAKeyPairGenerator{ + Bits: int(apiKey.RSA.KeySize), + }, nil + case configv1alpha1.KeyAlgorithmECDSA: + curve, err := ecdsaCurveFromAPI(apiKey.ECDSA.Curve) + if err != nil { + return nil, err + } + return crypto.ECDSAKeyPairGenerator{ + Curve: curve, + }, nil + default: + return nil, fmt.Errorf("unknown key algorithm: %q", apiKey.Algorithm) + } +} + +// ecdsaCurveFromAPI converts an API ECDSA curve name to the crypto package's ECDSACurve. +func ecdsaCurveFromAPI(c configv1alpha1.ECDSACurve) (crypto.ECDSACurve, error) { + switch c { + case configv1alpha1.ECDSACurveP256: + return crypto.P256, nil + case configv1alpha1.ECDSACurveP384: + return crypto.P384, nil + case configv1alpha1.ECDSACurveP521: + return crypto.P521, nil + default: + return "", fmt.Errorf("unknown ECDSA curve: %q", c) + } +} + +// securityBits returns the NIST security strength in bits for a given +// KeyPairGenerator. For RSA, values come from the rsaSecurityStrength table. +// For ECDSA, security strength is half the key size (fixed per curve). +func securityBits(g crypto.KeyPairGenerator) int { + switch g := g.(type) { + case crypto.RSAKeyPairGenerator: + return rsaSecurityStrength[g.Bits] + case crypto.ECDSAKeyPairGenerator: + switch g.Curve { + case crypto.P256: + return 128 + case crypto.P384: + return 192 + case crypto.P521: + return 256 + } + } + return 0 +} + +// rsaSecurityStrength maps RSA key sizes (2048-8192 in 1024-bit increments) +// to their security strengths from NIST SP 800-56B Rev 2 Table 2 or +// pre-calculated from the GNFS complexity estimate. +var rsaSecurityStrength = map[int]int{ + 2048: 112, + 3072: 128, + 4096: 152, + 5120: 168, + 6144: 176, + 7168: 192, + 8192: 200, +} + +// strongerKeyPairGenerator returns whichever of a or b provides higher NIST +// security strength. In case of a tie, ECDSA is preferred over RSA. +func strongerKeyPairGenerator(a, b crypto.KeyPairGenerator) crypto.KeyPairGenerator { + sa, sb := securityBits(a), securityBits(b) + if sb > sa { + return b + } + if sa > sb { + return a + } + // Equal strength: prefer ECDSA over RSA. + if _, ok := a.(crypto.ECDSAKeyPairGenerator); ok { + return a + } + if _, ok := b.(crypto.ECDSAKeyPairGenerator); ok { + return b + } + return a +} diff --git a/vendor/github.com/openshift/library-go/pkg/pki/provider.go b/vendor/github.com/openshift/library-go/pkg/pki/provider.go new file mode 100644 index 00000000000..2007aa890e3 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/pki/provider.go @@ -0,0 +1,75 @@ +package pki + +import ( + "fmt" + + configv1alpha1 "github.com/openshift/api/config/v1alpha1" + configv1alpha1listers "github.com/openshift/client-go/config/listers/config/v1alpha1" +) + +// PKIProfileProvider provides the PKIProfile that determines certificate key +// configuration. A nil profile indicates Unmanaged mode where the caller +// should use its own defaults. +type PKIProfileProvider interface { + PKIProfile() (*configv1alpha1.PKIProfile, error) +} + +// StaticPKIProfileProvider is a PKIProfileProvider backed by a fixed PKIProfile. +type StaticPKIProfileProvider struct { + profile *configv1alpha1.PKIProfile +} + +// NewStaticPKIProfileProvider returns a PKIProfileProvider backed by the given +// profile. A nil profile signals Unmanaged mode. +func NewStaticPKIProfileProvider(profile *configv1alpha1.PKIProfile) *StaticPKIProfileProvider { + return &StaticPKIProfileProvider{profile: profile} +} + +// PKIProfile returns the static PKIProfile. +func (s *StaticPKIProfileProvider) PKIProfile() (*configv1alpha1.PKIProfile, error) { + return s.profile, nil +} + +// ListerPKIProfileProvider is a PKIProfileProvider that reads a named +// cluster-scoped PKI resource via a lister. +type ListerPKIProfileProvider struct { + lister configv1alpha1listers.PKILister + resourceName string +} + +// NewClusterPKIProfileProvider creates a PKIProfileProvider that resolves the +// PKIProfile from the OpenShift cluster configuration PKI resource. +func NewClusterPKIProfileProvider(lister configv1alpha1listers.PKILister) *ListerPKIProfileProvider { + return NewListerPKIProfileProvider(lister, "cluster") +} + +// NewListerPKIProfileProvider returns a PKIProfileProvider that reads the +// named cluster-scoped PKI resource via a lister. +func NewListerPKIProfileProvider(lister configv1alpha1listers.PKILister, resourceName string) *ListerPKIProfileProvider { + return &ListerPKIProfileProvider{ + lister: lister, + resourceName: resourceName, + } +} + +// PKIProfile reads the PKI resource and returns the profile based on its +// certificate management mode. Returns nil for Unmanaged mode. +func (l *ListerPKIProfileProvider) PKIProfile() (*configv1alpha1.PKIProfile, error) { + pki, err := l.lister.Get(l.resourceName) + if err != nil { + return nil, fmt.Errorf("failed to get PKI resource %q: %w", l.resourceName, err) + } + + switch pki.Spec.CertificateManagement.Mode { + case configv1alpha1.PKICertificateManagementModeUnmanaged: + return nil, nil + case configv1alpha1.PKICertificateManagementModeDefault: + profile := DefaultPKIProfile() + return &profile, nil + case configv1alpha1.PKICertificateManagementModeCustom: + profile := pki.Spec.CertificateManagement.Custom.PKIProfile + return &profile, nil + default: + return nil, fmt.Errorf("unknown PKI certificate management mode: %q", pki.Spec.CertificateManagement.Mode) + } +} diff --git a/vendor/github.com/openshift/library-go/pkg/pki/resolve.go b/vendor/github.com/openshift/library-go/pkg/pki/resolve.go new file mode 100644 index 00000000000..1154fd48eaf --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/pki/resolve.go @@ -0,0 +1,77 @@ +package pki + +import ( + "fmt" + + configv1alpha1 "github.com/openshift/api/config/v1alpha1" + "github.com/openshift/library-go/pkg/crypto" +) + +// CertificateConfig holds the resolved configuration for a specific certificate. +// Currently contains key configuration; will grow as the PKI API expands to +// include additional certificate properties. +type CertificateConfig struct { + // Key is the resolved key pair generator. + Key crypto.KeyPairGenerator +} + +// ResolveCertificateConfig resolves the effective certificate configuration +// for a given certificate type and name from the PKI profile. +// +// Returns nil if the provider returns a nil profile (Unmanaged mode), +// indicating that the caller should use its own default behavior. +// +// The name parameter is reserved for future per-certificate overrides and +// can be used for metrics and logging. +func ResolveCertificateConfig(provider PKIProfileProvider, certType CertificateType, name string) (*CertificateConfig, error) { + profile, err := provider.PKIProfile() + if err != nil { + return nil, fmt.Errorf("resolving PKI profile for %s certificate %q: %w", certType, name, err) + } + if profile == nil { + return nil, nil + } + + switch certType { + case CertificateTypeSigner: + return resolveKeyConfig(profile.Defaults, profile.SignerCertificates) + case CertificateTypeServing: + return resolveKeyConfig(profile.Defaults, profile.ServingCertificates) + case CertificateTypeClient: + return resolveKeyConfig(profile.Defaults, profile.ClientCertificates) + case CertificateTypePeer: + return resolvePeerKeyConfig(profile) + default: + return nil, fmt.Errorf("unknown certificate type: %q", certType) + } +} + +// resolveKeyConfig returns the override KeyConfig if its Algorithm is set, +// otherwise falls back to the default. +func resolveKeyConfig(defaults configv1alpha1.DefaultCertificateConfig, override configv1alpha1.CertificateConfig) (*CertificateConfig, error) { + apiKey := defaults.Key + if override.Key.Algorithm != "" { + apiKey = override.Key + } + g, err := KeyPairGeneratorFromAPI(apiKey) + if err != nil { + return nil, err + } + return &CertificateConfig{Key: g}, nil +} + +// resolvePeerKeyConfig resolves both the serving and client configs and +// returns whichever has higher NIST security strength. +func resolvePeerKeyConfig(profile *configv1alpha1.PKIProfile) (*CertificateConfig, error) { + servingCfg, err := resolveKeyConfig(profile.Defaults, profile.ServingCertificates) + if err != nil { + return nil, fmt.Errorf("resolving serving config for peer: %w", err) + } + clientCfg, err := resolveKeyConfig(profile.Defaults, profile.ClientCertificates) + if err != nil { + return nil, fmt.Errorf("resolving client config for peer: %w", err) + } + return &CertificateConfig{ + Key: strongerKeyPairGenerator(servingCfg.Key, clientCfg.Key), + }, nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/pki/types.go b/vendor/github.com/openshift/library-go/pkg/pki/types.go new file mode 100644 index 00000000000..2cf8282255e --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/pki/types.go @@ -0,0 +1,23 @@ +package pki + +// CertificateType identifies the category of a certificate for profile resolution. +type CertificateType string + +const ( + // CertificateTypeSigner identifies certificate authority (CA) certificates + // that sign other certificates. + CertificateTypeSigner CertificateType = "signer" + + // CertificateTypeServing identifies TLS server certificates used to serve + // HTTPS endpoints. + CertificateTypeServing CertificateType = "serving" + + // CertificateTypeClient identifies client authentication certificates used + // to authenticate to servers. + CertificateTypeClient CertificateType = "client" + + // CertificateTypePeer identifies certificates used for both server and client + // authentication. The resolved key configuration is the stronger of the + // serving and client configurations. + CertificateTypePeer CertificateType = "peer" +) diff --git a/vendor/modules.txt b/vendor/modules.txt index b53f2728f0b..b7029be8db9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1459,6 +1459,7 @@ github.com/openshift/library-go/pkg/config/clusterstatus github.com/openshift/library-go/pkg/config/leaderelection github.com/openshift/library-go/pkg/crypto github.com/openshift/library-go/pkg/image/dockerv1client +github.com/openshift/library-go/pkg/pki github.com/openshift/library-go/pkg/route/routeapihelpers # github.com/openshift/machine-api-operator v0.2.1-0.20251110092458-e0af0f3f44b8 ## explicit; go 1.24.0