diff --git a/installer/pkg/config-generator/BUILD.bazel b/installer/pkg/config-generator/BUILD.bazel index 68ad4942d00..a9bdf6661b3 100644 --- a/installer/pkg/config-generator/BUILD.bazel +++ b/installer/pkg/config-generator/BUILD.bazel @@ -12,7 +12,7 @@ go_library( deps = [ "//installer/pkg/config:go_default_library", "//installer/pkg/copy:go_default_library", - "//installer/pkg/tls:go_default_library", + "//pkg/asset/tls:go_default_library", "//vendor/github.com/apparentlymart/go-cidr/cidr:go_default_library", "//vendor/github.com/coreos/ignition/config/v2_2:go_default_library", "//vendor/github.com/coreos/ignition/config/v2_2/types:go_default_library", @@ -35,6 +35,6 @@ go_test( embed = [":go_default_library"], deps = [ "//installer/pkg/config:go_default_library", - "//installer/pkg/tls:go_default_library", + "//pkg/asset/tls:go_default_library", ], ) diff --git a/installer/pkg/config-generator/generator_test.go b/installer/pkg/config-generator/generator_test.go index 3e41dd244aa..7cd10327bc6 100644 --- a/installer/pkg/config-generator/generator_test.go +++ b/installer/pkg/config-generator/generator_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/openshift/installer/installer/pkg/config" - "github.com/openshift/installer/installer/pkg/tls" + "github.com/openshift/installer/pkg/asset/tls" ) func initConfig(t *testing.T, file string) ConfigGenerator { @@ -131,7 +131,7 @@ func TestGenerateCert(t *testing.T) { CommonName: "test-self-signed-ca", OrganizationalUnit: []string{"openshift"}, }, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, } caCert, err := tls.SelfSignedCACert(caCfg, caKey) if err != nil { @@ -151,7 +151,7 @@ func TestGenerateCert(t *testing.T) { KeyUsages: x509.KeyUsageKeyEncipherment, DNSNames: []string{"test-api.kubernetes.default"}, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: false, }, clusterDir: "./", diff --git a/installer/pkg/config-generator/tls.go b/installer/pkg/config-generator/tls.go index 3c81ba505ab..99c9b502ba3 100644 --- a/installer/pkg/config-generator/tls.go +++ b/installer/pkg/config-generator/tls.go @@ -1,7 +1,6 @@ package configgenerator import ( - "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" @@ -10,10 +9,9 @@ import ( "io/ioutil" "net" "path/filepath" - "time" "github.com/openshift/installer/installer/pkg/copy" - "github.com/openshift/installer/installer/pkg/tls" + "github.com/openshift/installer/pkg/asset/tls" ) const ( @@ -48,9 +46,6 @@ const ( tncKeyPath = "generated/tls/tnc.key" serviceAccountPubkeyPath = "generated/tls/service-account.pub" serviceAccountPrivateKeyPath = "generated/tls/service-account.key" - - validityTenYears = time.Hour * 24 * 365 * 10 - validityThirtyMinutes = time.Minute * 30 ) // GenerateTLSConfig fetches and validates the TLS cert files @@ -77,7 +72,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { cfg := &tls.CertCfg{ Subject: pkix.Name{CommonName: "kube-ca", OrganizationalUnit: []string{"bootkube"}}, KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: true, } kubeCAKey, kubeCACert, err := generateCert(clusterDir, caKey, caCert, kubeCAKeyPath, kubeCACertPath, cfg, false) @@ -90,7 +85,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, IsCA: true, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, } etcdCAKey, etcdCACert, err := generateCert(clusterDir, caKey, caCert, etcdCAKeyPath, etcdCACertPath, cfg, false) if err != nil { @@ -109,7 +104,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, KeyUsages: x509.KeyUsageKeyEncipherment, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, } if _, _, err := generateCert(clusterDir, etcdCAKey, etcdCACert, etcdClientKeyPath, etcdClientCertPath, cfg, false); err != nil { return fmt.Errorf("failed to generate etcd client certificate: %v", err) @@ -119,7 +114,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { cfg = &tls.CertCfg{ Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: true, } aggregatorCAKey, aggregatorCACert, err := generateCert(clusterDir, caKey, caCert, aggregatorCAKeyPath, aggregatorCACertPath, cfg, false) @@ -131,7 +126,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { cfg = &tls.CertCfg{ Subject: pkix.Name{CommonName: "service-serving", OrganizationalUnit: []string{"bootkube"}}, KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: true, } if _, _, err := generateCert(clusterDir, caKey, caCert, serviceServingCAKeyPath, serviceServingCACertPath, cfg, false); err != nil { @@ -152,7 +147,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { fmt.Sprintf("*.%s", baseAddress), }, Subject: pkix.Name{CommonName: baseAddress, Organization: []string{"ingress"}}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: false, } @@ -165,7 +160,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: false, } @@ -188,7 +183,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { "kubernetes.default.svc", "kubernetes.default.svc.cluster.local", }, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IPAddresses: []net.IP{net.ParseIP(apiServerAddress)}, IsCA: false, } @@ -209,7 +204,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { "openshift-apiserver.kube-system.svc", "openshift-apiserver.kube-system.svc.cluster.local", "localhost", "127.0.0.1"}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IPAddresses: []net.IP{net.ParseIP(apiServerAddress)}, IsCA: false, } @@ -223,7 +218,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Subject: pkix.Name{CommonName: "kube-apiserver-proxy", Organization: []string{"kube-master"}}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: false, } @@ -236,7 +231,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Subject: pkix.Name{CommonName: "system:serviceaccount:kube-system:default", Organization: []string{"system:serviceaccounts:kube-system"}}, - Validity: validityThirtyMinutes, + Validity: tls.ValidityThirtyMinutes, IsCA: false, } @@ -250,7 +245,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, DNSNames: []string{tncDomain}, Subject: pkix.Name{CommonName: tncDomain}, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: false, } @@ -262,7 +257,7 @@ func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error { cfg = &tls.CertCfg{ Subject: pkix.Name{CommonName: "cluster-apiserver", OrganizationalUnit: []string{"bootkube"}}, KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: validityTenYears, + Validity: tls.ValidityTenYears, IsCA: true, } if _, _, err := generateCert(clusterDir, aggregatorCAKey, aggregatorCACert, clusterAPIServerKeyPath, clusterAPIServerCertPath, cfg, true); err != nil { @@ -302,15 +297,32 @@ func generatePrivateKey(clusterDir string, path string) (*rsa.PrivateKey, error) // generateRootCert creates the rootCAKey and rootCACert func generateRootCert(clusterDir string) (cert *x509.Certificate, key *rsa.PrivateKey, err error) { - // generate key and certificate - caKey, err := generatePrivateKey(clusterDir, rootCAKeyPath) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate private key: %v", err) + targetKeyPath := filepath.Join(clusterDir, rootCAKeyPath) + targetCertPath := filepath.Join(clusterDir, rootCACertPath) + + cfg := &tls.CertCfg{ + Subject: pkix.Name{ + CommonName: "root-ca", + OrganizationalUnit: []string{"openshift"}, + }, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: tls.ValidityTenYears, + IsCA: true, } - caCert, err := generateRootCA(clusterDir, caKey) + + caKey, caCert, err := tls.GenerateRootCertKey(cfg) if err != nil { - return nil, nil, fmt.Errorf("failed to create a certificate: %v", err) + return nil, nil, err } + + if err := ioutil.WriteFile(targetKeyPath, []byte(tls.PrivateKeyToPem(caKey)), 0600); err != nil { + return nil, nil, err + } + + if err := ioutil.WriteFile(targetCertPath, []byte(tls.CertToPem(caCert)), 0666); err != nil { + return nil, nil, err + } + return caCert, caKey, nil } @@ -361,74 +373,26 @@ func generateCert(clusterDir string, cfg *tls.CertCfg, appendCA bool) (*rsa.PrivateKey, *x509.Certificate, error) { - // create a private key - key, err := generatePrivateKey(clusterDir, keyPath) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate private key: %v", err) - } - - // create a CSR - csrTmpl := x509.CertificateRequest{Subject: cfg.Subject, DNSNames: cfg.DNSNames, IPAddresses: cfg.IPAddresses} - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTmpl, key) - if err != nil { - return nil, nil, fmt.Errorf("error creating certificate request: %v", err) - } - csr, err := x509.ParseCertificateRequest(csrBytes) - if err != nil { - return nil, nil, fmt.Errorf("error parsing certificate request: %v", err) - } - - // create a cert - cert, err := generateSignedCert(cfg, csr, key, caKey, caCert, clusterDir, certPath, appendCA) - if err != nil { - return nil, nil, fmt.Errorf("failed to create a certificate: %v", err) - } - return key, cert, nil -} + targetKeyPath := filepath.Join(clusterDir, keyPath) + targetCertPath := filepath.Join(clusterDir, certPath) -// generateRootCA creates and returns the root CA -func generateRootCA(path string, key *rsa.PrivateKey) (*x509.Certificate, error) { - fileTargetPath := filepath.Join(path, rootCACertPath) - cfg := &tls.CertCfg{ - Subject: pkix.Name{ - CommonName: "root-ca", - OrganizationalUnit: []string{"openshift"}, - }, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: validityTenYears, - IsCA: true, - } - cert, err := tls.SelfSignedCACert(cfg, key) + key, cert, err := tls.GenerateCert(caKey, caCert, cfg) if err != nil { - return nil, fmt.Errorf("error generating self signed certificate: %v", err) - } - if err := ioutil.WriteFile(fileTargetPath, []byte(tls.CertToPem(cert)), 0666); err != nil { - return nil, err + return nil, nil, err } - return cert, nil -} -func generateSignedCert(cfg *tls.CertCfg, - csr *x509.CertificateRequest, - key *rsa.PrivateKey, - caKey *rsa.PrivateKey, - caCert *x509.Certificate, - clusterDir string, - path string, - appendCA bool) (*x509.Certificate, error) { - cert, err := tls.SignedCertificate(cfg, csr, key, caCert, caKey) - if err != nil { - return nil, fmt.Errorf("error signing certificate: %v", err) + if err := ioutil.WriteFile(targetKeyPath, []byte(tls.PrivateKeyToPem(key)), 0600); err != nil { + return nil, nil, err } - fileTargetPath := filepath.Join(clusterDir, path) content := []byte(tls.CertToPem(cert)) if appendCA { content = append(content, '\n') content = append(content, []byte(tls.CertToPem(caCert))...) } - if err := ioutil.WriteFile(fileTargetPath, content, 0666); err != nil { - return nil, err + if err := ioutil.WriteFile(targetCertPath, content, 0666); err != nil { + return nil, nil, err } - return cert, nil + + return key, cert, nil } diff --git a/installer/pkg/validate/BUILD.bazel b/installer/pkg/validate/BUILD.bazel index 62f0f1eb7da..83216ac3151 100644 --- a/installer/pkg/validate/BUILD.bazel +++ b/installer/pkg/validate/BUILD.bazel @@ -6,7 +6,7 @@ go_test( srcs = ["validate_test.go"], embed = [":go_default_library"], deps = [ - "//installer/pkg/tls:go_default_library", + "//pkg/asset/tls:go_default_library", "//vendor/gopkg.in/square/go-jose.v2:go_default_library", ], ) diff --git a/installer/pkg/validate/validate_test.go b/installer/pkg/validate/validate_test.go index 67944177a64..8d1613c5598 100644 --- a/installer/pkg/validate/validate_test.go +++ b/installer/pkg/validate/validate_test.go @@ -15,7 +15,7 @@ import ( "testing" "time" - "github.com/openshift/installer/installer/pkg/tls" + "github.com/openshift/installer/pkg/asset/tls" jose "gopkg.in/square/go-jose.v2" ) diff --git a/pkg/asset/installconfig/stock.go b/pkg/asset/installconfig/stock.go index 4e97310780f..358d5c9b3d2 100644 --- a/pkg/asset/installconfig/stock.go +++ b/pkg/asset/installconfig/stock.go @@ -32,7 +32,7 @@ type Stock interface { Platform() asset.Asset } -// StockImpl is the +// StockImpl implements the Stock interface for installconfig and user inputs. type StockImpl struct { installConfig asset.Asset clusterID asset.Asset diff --git a/pkg/asset/stock/stock.go b/pkg/asset/stock/stock.go index 64a5574e775..9f608984caf 100644 --- a/pkg/asset/stock/stock.go +++ b/pkg/asset/stock/stock.go @@ -5,17 +5,23 @@ import ( "os" "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" ) // Stock is the stock of assets that can be generated. type Stock struct { installConfigStock + tlsStock } type installConfigStock struct { installconfig.StockImpl } +type tlsStock struct { + tls.StockImpl +} + var _ installconfig.Stock = (*Stock)(nil) // EstablishStock establishes the stock of assets in the specified directory. @@ -23,5 +29,7 @@ func EstablishStock(directory string) *Stock { s := &Stock{} inputReader := bufio.NewReader(os.Stdin) s.installConfigStock.EstablishStock(directory, inputReader) + s.tlsStock.EstablishStock(directory, &s.installConfigStock) + return s } diff --git a/installer/pkg/tls/BUILD.bazel b/pkg/asset/tls/BUILD.bazel similarity index 74% rename from installer/pkg/tls/BUILD.bazel rename to pkg/asset/tls/BUILD.bazel index a2c38c00514..e02e0770d0f 100644 --- a/installer/pkg/tls/BUILD.bazel +++ b/pkg/asset/tls/BUILD.bazel @@ -3,7 +3,9 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_test( name = "go_default_test", size = "small", - srcs = ["tls_test.go"], + srcs = [ + "tls_test.go", + ], embed = [":go_default_library"], ) @@ -13,6 +15,6 @@ go_library( "tls.go", "utils.go", ], - importpath = "github.com/openshift/installer/installer/pkg/tls", + importpath = "github.com/openshift/installer/pkg/asset/tls", visibility = ["//visibility:public"], ) diff --git a/pkg/asset/tls/certkey.go b/pkg/asset/tls/certkey.go new file mode 100644 index 00000000000..5da63a4b03b --- /dev/null +++ b/pkg/asset/tls/certkey.go @@ -0,0 +1,171 @@ +package tls + +import ( + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "net" + "path/filepath" + "time" + + "github.com/ghodss/yaml" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/types" +) + +// CertKey contains the private key and the cert that's +// signed by the parent CA. +type CertKey struct { + installConfig asset.Asset + rootDir string + + // Common fields. + Subject pkix.Name + KeyUsages x509.KeyUsage + ExtKeyUsages []x509.ExtKeyUsage + Validity time.Duration + KeyFileName string + CertFileName string + ParentCA asset.Asset + + IsCA bool + AppendParent bool // Whether append the parent CA in the cert. + + // Some certs might need to set Subject, DNSNames and IPAddresses. + GenDNSNames func(*types.InstallConfig) ([]string, error) + GenIPAddresses func(*types.InstallConfig) ([]net.IP, error) + GenSubject func(*types.InstallConfig) (pkix.Name, error) +} + +var _ asset.Asset = (*CertKey)(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. +func (c *CertKey) Dependencies() []asset.Asset { + parents := []asset.Asset{c.ParentCA} + + // Require the InstallConfig if we need additional info from install config. + if c.GenDNSNames != nil || c.GenIPAddresses != nil || c.GenSubject != nil { + parents = append(parents, c.installConfig) + } + + return parents +} + +// Generate generates the cert/key pair based on its dependencies. +func (c *CertKey) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { + cfg := &CertCfg{ + Subject: c.Subject, + KeyUsages: c.KeyUsages, + ExtKeyUsages: c.ExtKeyUsages, + Validity: c.Validity, + IsCA: c.IsCA, + } + + if c.GenSubject != nil || c.GenDNSNames != nil || c.GenIPAddresses != nil { + state, ok := parents[c.installConfig] + if !ok { + return nil, fmt.Errorf("failed to get install config state in the parent asset states") + } + + var installConfig types.InstallConfig + if err := yaml.Unmarshal(state.Contents[0].Data, &installConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal install config: %v", err) + } + + var err error + if c.GenSubject != nil { + cfg.Subject, err = c.GenSubject(&installConfig) + if err != nil { + return nil, fmt.Errorf("failed to generate Subject: %v", err) + } + } + if c.GenDNSNames != nil { + cfg.DNSNames, err = c.GenDNSNames(&installConfig) + if err != nil { + return nil, fmt.Errorf("failed to generate DNSNames: %v", err) + } + } + if c.GenIPAddresses != nil { + cfg.IPAddresses, err = c.GenIPAddresses(&installConfig) + if err != nil { + return nil, fmt.Errorf("failed to generate IPAddresses: %v", err) + } + } + } + + var key *rsa.PrivateKey + var crt *x509.Certificate + var err error + + state, ok := parents[c.ParentCA] + if !ok { + return nil, fmt.Errorf("failed to get parent CA %v in the parent asset states", c.ParentCA) + } + + caKey, caCert, err := parseCAFromAssetState(state) + if err != nil { + return nil, fmt.Errorf("failed to parse CA from asset: %v", err) + } + + key, crt, err = GenerateCert(caKey, caCert, cfg) + if err != nil { + return nil, fmt.Errorf("failed to generate cert/key pair: %v", err) + } + + keyData := []byte(PrivateKeyToPem(key)) + certData := []byte(CertToPem(crt)) + if c.AppendParent { + certData = append(certData, '\n') + certData = append(certData, []byte(CertToPem(caCert))...) + } + + var st asset.State + st.Contents = []asset.Content{ + { + Name: assetFilePath(c.rootDir, c.KeyFileName), + Data: keyData, + }, + { + Name: assetFilePath(c.rootDir, c.CertFileName), + Data: certData, + }, + } + + if err := st.PersistToFile(); err != nil { + return nil, err + } + + return &st, nil +} + +func parseCAFromAssetState(ca *asset.State) (*rsa.PrivateKey, *x509.Certificate, error) { + var key *rsa.PrivateKey + var cert *x509.Certificate + var err error + + if len(ca.Contents) != 2 { + return nil, nil, fmt.Errorf("expect key and cert in the contents of CA, got: %v", ca) + } + + for _, c := range ca.Contents { + switch filepath.Ext(c.Name) { + case ".key": + key, err = PemToPrivateKey(c.Data) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse rsa private key: %v", err) + } + case ".crt": + cert, err = PemToCertificate(c.Data) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse x509 certificate: %v", err) + } + default: + return nil, nil, fmt.Errorf("unexpected content name: %v", c.Name) + } + } + + return key, cert, nil +} diff --git a/pkg/asset/tls/certkey_test.go b/pkg/asset/tls/certkey_test.go new file mode 100644 index 00000000000..5fb1dec6245 --- /dev/null +++ b/pkg/asset/tls/certkey_test.go @@ -0,0 +1,186 @@ +package tls + +import ( + "bytes" + "crypto/x509" + "crypto/x509/pkix" + "io/ioutil" + "net" + "os" + "testing" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/types" +) + +type fakeInstallConfig bool + +var _ asset.Asset = fakeInstallConfig(false) + +func (f fakeInstallConfig) Dependencies() []asset.Asset { + return nil +} + +func (f fakeInstallConfig) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { + return &asset.State{ + Contents: []asset.Content{ + { + Name: "fakeInstallConfig", + Data: []byte{}, + }, + }, + }, nil +} + +func TestCertKeyGenerate(t *testing.T) { + testDir, err := ioutil.TempDir(os.TempDir(), "certkey_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(testDir) + + root := &RootCA{rootDir: testDir} + rootState, err := root.Generate(nil) + if err != nil { + t.Fatal(err) + } + + var installConfig fakeInstallConfig + installConfigState, err := installConfig.Generate(nil) + if err != nil { + t.Fatal(err) + } + + testGenSubject := func(*types.InstallConfig) (pkix.Name, error) { + return pkix.Name{CommonName: "test", OrganizationalUnit: []string{"openshift"}}, nil + } + + testGenDNSNames := func(*types.InstallConfig) ([]string, error) { + return []string{"test.openshift.io"}, nil + } + + testGenIPAddresses := func(*types.InstallConfig) ([]net.IP, error) { + return []net.IP{net.ParseIP("10.0.0.1")}, nil + } + + tests := []struct { + certKey *CertKey + err bool + parents map[asset.Asset]*asset.State + }{ + { + &CertKey{ + rootDir: testDir, + installConfig: installConfig, + Subject: pkix.Name{CommonName: "test0-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "test0-ca.key", + CertFileName: "test0-ca.crt", + IsCA: true, + ParentCA: root, + }, + false, + map[asset.Asset]*asset.State{ + root: rootState, + }, + }, + { + &CertKey{ + rootDir: testDir, + installConfig: installConfig, + Subject: pkix.Name{CommonName: "test1-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "test1-ca.key", + CertFileName: "test1-ca.crt", + IsCA: true, + ParentCA: root, + AppendParent: true, + GenSubject: testGenSubject, + GenDNSNames: testGenDNSNames, + GenIPAddresses: testGenIPAddresses, + }, + false, + map[asset.Asset]*asset.State{ + root: rootState, + installConfig: installConfigState, + }, + }, + { + &CertKey{ + rootDir: testDir, + installConfig: installConfig, + Subject: pkix.Name{CommonName: "test1-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "test2-ca.key", + CertFileName: "test2-ca.crt", + IsCA: true, + ParentCA: root, + AppendParent: true, + GenSubject: testGenSubject, + GenDNSNames: testGenDNSNames, + GenIPAddresses: testGenIPAddresses, + }, + true, + nil, + }, + } + + for i, tt := range tests { + st, err := tt.certKey.Generate(tt.parents) + if tt.err != (err != nil) { + t.Errorf("test #%d error is not expected, expect %v, saw %v", i, tt.err, err != nil) + } + + if err != nil { + continue + + } + + keyFileName := assetFilePath(testDir, tt.certKey.KeyFileName) + crtFileName := assetFilePath(testDir, tt.certKey.CertFileName) + + keyData, err := ioutil.ReadFile(keyFileName) + if err != nil { + t.Errorf("test #%d failed to read key file %q: %v", i, keyFileName, err) + } + + crtData, err := ioutil.ReadFile(crtFileName) + if err != nil { + t.Errorf("test #%d failed to read key file %q: %v", i, crtFileName, err) + } + + if !bytes.Equal(st.Contents[0].Data, keyData) { + t.Errorf("test #%d expect key data: %q, saw %q", i, string(st.Contents[0].Data), string(keyData)) + } + + if !bytes.Equal(st.Contents[1].Data, crtData) { + t.Errorf("test #%d expect crt data: %q, saw %q", i, string(st.Contents[1].Data), string(crtData)) + } + + // Briefly check the certs. + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(crtData) { + t.Errorf("test #%d failed to append certs from PEM", i) + } + + opts := x509.VerifyOptions{ + Roots: certPool, + DNSName: tt.certKey.Subject.CommonName, + } + if tt.certKey.GenDNSNames != nil { + opts.DNSName = "test.openshift.io" + } + + cert, err := PemToCertificate(crtData) + if err != nil { + t.Errorf("test #%d failed to parse certificate: %v", i, err) + } + + if _, err := cert.Verify(opts); err != nil { + t.Errorf("test #%d failed to verify cert: %v", i, err) + } + } +} diff --git a/pkg/asset/tls/doc.go b/pkg/asset/tls/doc.go new file mode 100644 index 00000000000..f5cced4b334 --- /dev/null +++ b/pkg/asset/tls/doc.go @@ -0,0 +1,2 @@ +// Package tls defines and generates the tls assets based on its dependencies. +package tls diff --git a/pkg/asset/tls/helper.go b/pkg/asset/tls/helper.go new file mode 100644 index 00000000000..33371d77d71 --- /dev/null +++ b/pkg/asset/tls/helper.go @@ -0,0 +1,88 @@ +package tls + +import ( + "crypto/x509/pkix" + "fmt" + "net" + "path/filepath" + + "github.com/apparentlymart/go-cidr/cidr" + "github.com/openshift/installer/pkg/types" +) + +const ( + tlsDir = "tls" +) + +func assetFilePath(rootDir, filename string) string { + return filepath.Join(rootDir, tlsDir, filename) +} + +func getBaseAddress(cfg *types.InstallConfig) string { + return fmt.Sprintf("%s.%s", cfg.Name, cfg.BaseDomain) +} + +func cidrhost(network net.IPNet, hostNum int) (string, error) { + ip, err := cidr.Host(&network, hostNum) + if err != nil { + return "", err + } + + return ip.String(), nil +} + +func genSubjectForIngressCertKey(cfg *types.InstallConfig) (pkix.Name, error) { + return pkix.Name{CommonName: getBaseAddress(cfg), Organization: []string{"ingress"}}, nil +} + +func genDNSNamesForIngressCertKey(cfg *types.InstallConfig) ([]string, error) { + baseAddress := getBaseAddress(cfg) + return []string{ + baseAddress, + fmt.Sprintf("*.%s", baseAddress), + }, nil +} + +func genDNSNamesForAPIServerCertKey(cfg *types.InstallConfig) ([]string, error) { + return []string{ + fmt.Sprintf("%s-api.%s", cfg.Name, cfg.BaseDomain), + "kubernetes", "kubernetes.default", + "kubernetes.default.svc", + "kubernetes.default.svc.cluster.local", + }, nil +} + +func genIPAddressesForAPIServerCertKey(cfg *types.InstallConfig) ([]net.IP, error) { + apiServerAddress, err := cidrhost(cfg.Networking.ServiceCIDR, 1) + if err != nil { + return nil, err + } + return []net.IP{net.ParseIP(apiServerAddress)}, nil +} + +func genDNSNamesForOpenshiftAPIServerCertKey(cfg *types.InstallConfig) ([]string, error) { + return []string{ + fmt.Sprintf("%s-api.%s", cfg.Name, cfg.BaseDomain), + "openshift-apiserver", + "openshift-apiserver.kube-system", + "openshift-apiserver.kube-system.svc", + "openshift-apiserver.kube-system.svc.cluster.local", + "localhost", "127.0.0.1", + }, nil +} + +func genIPAddressesForOpenshiftAPIServerCertKey(cfg *types.InstallConfig) ([]net.IP, error) { + apiServerAddress, err := cidrhost(cfg.Networking.ServiceCIDR, 1) + if err != nil { + return nil, err + } + return []net.IP{net.ParseIP(apiServerAddress)}, nil +} + +func genDNSNamesForTNCCertKey(cfg *types.InstallConfig) ([]string, error) { + return []string{fmt.Sprintf("%s-tnc.%s", cfg.Name, cfg.BaseDomain)}, nil +} + +func genSubjectForTNCCertKey(cfg *types.InstallConfig) (pkix.Name, error) { + return pkix.Name{CommonName: fmt.Sprintf("%s-tnc.%s", cfg.Name, cfg.BaseDomain)}, nil +} diff --git a/pkg/asset/tls/keypair.go b/pkg/asset/tls/keypair.go new file mode 100644 index 00000000000..179873f8373 --- /dev/null +++ b/pkg/asset/tls/keypair.go @@ -0,0 +1,53 @@ +package tls + +import ( + "fmt" + + "github.com/openshift/installer/pkg/asset" +) + +// KeyPair implements the Asset interface and +// generates an RSA public/private key pair. +type KeyPair struct { + rootDir string + PrivKeyFileName string + PubKeyFileName string +} + +var _ asset.Asset = (*KeyPair)(nil) + +// Dependencies returns the dependency of an rsa private / public key pair. +func (k *KeyPair) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate generates the rsa private / public key pair. +func (k *KeyPair) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { + key, err := PrivateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate private key: %v", err) + } + + pubkeyData, err := PublicKeyToPem(&key.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to get public key data: %v", err) + } + + var st asset.State + st.Contents = []asset.Content{ + { + Name: assetFilePath(k.rootDir, k.PrivKeyFileName), + Data: []byte(PrivateKeyToPem(key)), + }, + { + Name: assetFilePath(k.rootDir, k.PubKeyFileName), + Data: []byte(pubkeyData), + }, + } + + if err := st.PersistToFile(); err != nil { + return nil, err + } + + return &st, nil +} diff --git a/pkg/asset/tls/root.go b/pkg/asset/tls/root.go new file mode 100644 index 00000000000..ac7b6ece559 --- /dev/null +++ b/pkg/asset/tls/root.go @@ -0,0 +1,55 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + "fmt" + + "github.com/openshift/installer/pkg/asset" +) + +// RootCA contains the private key and the cert that's +// self-signed as the root CA. +type RootCA struct { + rootDir string +} + +var _ asset.Asset = (*CertKey)(nil) + +// Dependencies returns the dependency of the root-ca, which is empty. +func (c *RootCA) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate generates the root-ca key and cert pair. +func (c *RootCA) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "root-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + IsCA: true, + } + + key, crt, err := GenerateRootCertKey(cfg) + if err != nil { + return nil, fmt.Errorf("failed to generate RootCA %v", err) + } + + var st asset.State + st.Contents = []asset.Content{ + { + Name: assetFilePath(c.rootDir, "root-ca.key"), + Data: []byte(PrivateKeyToPem(key)), + }, + { + Name: assetFilePath(c.rootDir, "root-ca.crt"), + Data: []byte(CertToPem(crt)), + }, + } + + if err := st.PersistToFile(); err != nil { + return nil, err + } + + return &st, nil +} diff --git a/pkg/asset/tls/stock.go b/pkg/asset/tls/stock.go new file mode 100644 index 00000000000..c56118c5bdf --- /dev/null +++ b/pkg/asset/tls/stock.go @@ -0,0 +1,297 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" +) + +// Stock is the stock of TLS assets that can be generated. +type Stock interface { + // RootCA is the asset that generates the root-ca key/cert pair. + RootCA() asset.Asset + // KubeCA is the asset that generates the kube-ca key/cert pair. + KubeCA() asset.Asset + // EtcdCA is the asset that generates the etcd-ca key/cert pair. + EtcdCA() asset.Asset + // AggregatorCA is the asset that generates the aggregator-ca key/cert pair. + AggregatorCA() asset.Asset + // ServiceServingCA is the asset that generates the service-serving-ca key/cert pair. + ServiceServingCA() asset.Asset + // EtcdClientCertKey is the asset that generates the etcd client key/cert pair. + EtcdClientCertKey() asset.Asset + // AdminCertKey is the asset that generates the admin key/cert pair. + AdminCertKey() asset.Asset + // IngressCertKey is the asset that generates the ingress key/cert pair. + IngressCertKey() asset.Asset + // APIServerCertKey is the asset that generates the API server key/cert pair. + APIServerCertKey() asset.Asset + // OpenshiftAPIServerCertKey is the asset that generates the Openshift API server key/cert pair. + OpenshiftAPIServerCertKey() asset.Asset + // APIServerProxyCertKey is the asset that generates the API server proxy key/cert pair. + APIServerProxyCertKey() asset.Asset + // KubeletCertKey is the asset that generates the kubelet key/cert pair. + KubeletCertKey() asset.Asset + // TNCCertKey is the asset that generates the TNC key/cert pair. + TNCCertKey() asset.Asset + // ClusterAPIServerCertKey is the asset that generates the cluster API server key/cert pair. + ClusterAPIServerCertKey() asset.Asset + // ServiceAccountKeyPair is the asset that generates the service-account public/private key pair. + ServiceAccountKeyPair() asset.Asset +} + +// StockImpl implements the Stock interface for tls assets. +type StockImpl struct { + rootCA asset.Asset + kubeCA asset.Asset + etcdCA asset.Asset + aggregatorCA asset.Asset + serviceServingCA asset.Asset + etcdClientCertKey asset.Asset + adminCertKey asset.Asset + ingressCertKey asset.Asset + apiServerCertKey asset.Asset + openshiftAPIServerCertKey asset.Asset + apiServerProxyCertKey asset.Asset + kubeletCertKey asset.Asset + tncCertKey asset.Asset + clusterAPIServerCertKey asset.Asset + serviceAccountKeyPair asset.Asset +} + +var _ Stock = (*StockImpl)(nil) + +// EstablishStock establishes the stock of assets in the specified directory. +func (s *StockImpl) EstablishStock(rootDir string, stock installconfig.Stock) { + s.rootCA = &RootCA{rootDir: rootDir} + s.kubeCA = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "kube-ca", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "kube-ca.key", + CertFileName: "kube-ca.crt", + + IsCA: true, + ParentCA: s.rootCA, + } + + s.etcdCA = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "etcd-ca.key", + CertFileName: "etcd-ca.crt", + + IsCA: true, + ParentCA: s.rootCA, + } + + s.aggregatorCA = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "aggregator-ca.key", + CertFileName: "aggregator-ca.crt", + + IsCA: true, + ParentCA: s.rootCA, + } + + s.serviceServingCA = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "service-serving", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "service-serving-ca.key", + CertFileName: "service-serving-ca.crt", + + IsCA: true, + ParentCA: s.rootCA, + } + + s.etcdClientCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, + KeyUsages: x509.KeyUsageKeyEncipherment, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + KeyFileName: "etcd-client.key", + CertFileName: "etcd-client.crt", + + ParentCA: s.etcdCA, + } + + s.adminCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + KeyFileName: "admin.key", + CertFileName: "admin.crt", + + ParentCA: s.kubeCA, + } + + s.ingressCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + KeyFileName: "ingress.key", + CertFileName: "ingress.crt", + + ParentCA: s.kubeCA, + AppendParent: true, + GenSubject: genSubjectForIngressCertKey, + GenDNSNames: genDNSNamesForIngressCertKey, + } + + s.apiServerCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Subject: pkix.Name{CommonName: "kube-apiserver", Organization: []string{"kube-master"}}, + Validity: ValidityTenYears, + KeyFileName: "apiserver.key", + CertFileName: "apiserver.crt", + + ParentCA: s.kubeCA, + AppendParent: true, + GenDNSNames: genDNSNamesForAPIServerCertKey, + GenIPAddresses: genIPAddressesForAPIServerCertKey, + } + + s.openshiftAPIServerCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Subject: pkix.Name{CommonName: "openshift-apiserver", Organization: []string{"kube-master"}}, + Validity: ValidityTenYears, + KeyFileName: "openshift-apiserver.key", + CertFileName: "openshift-apiserver.crt", + + ParentCA: s.aggregatorCA, + AppendParent: true, + GenDNSNames: genDNSNamesForOpenshiftAPIServerCertKey, + GenIPAddresses: genIPAddressesForOpenshiftAPIServerCertKey, + } + + s.apiServerProxyCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Subject: pkix.Name{CommonName: "kube-apiserver-proxy", Organization: []string{"kube-master"}}, + Validity: ValidityTenYears, + KeyFileName: "apiserver-proxy.key", + CertFileName: "apiserver-proxy.crt", + + ParentCA: s.aggregatorCA, + } + + s.kubeletCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Subject: pkix.Name{CommonName: "system:serviceaccount:kube-system:default", Organization: []string{"system:serviceaccounts:kube-system"}}, + Validity: ValidityThirtyMinutes, + KeyFileName: "kubelet.key", + CertFileName: "kubelet.crt", + + ParentCA: s.kubeCA, + } + + s.tncCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityTenYears, + KeyFileName: "tnc.key", + CertFileName: "tnc.crt", + + ParentCA: s.rootCA, + GenDNSNames: genDNSNamesForTNCCertKey, + GenSubject: genSubjectForTNCCertKey, + } + + s.clusterAPIServerCertKey = &CertKey{ + rootDir: rootDir, + installConfig: stock.InstallConfig(), + Subject: pkix.Name{CommonName: "cluster-apiserver", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + KeyFileName: "cluster-apiserver-ca.key", + CertFileName: "cluster-apiserver-ca.crt", + IsCA: true, + + ParentCA: s.aggregatorCA, + AppendParent: true, + } + + s.serviceAccountKeyPair = &KeyPair{ + rootDir: rootDir, + PrivKeyFileName: "service-account.key", + PubKeyFileName: "service-account.pub", + } +} + +// RootCA is the asset that generates the root-ca key/cert pair. +func (s *StockImpl) RootCA() asset.Asset { return s.rootCA } + +// KubeCA is the asset that generates the kube-ca key/cert pair. +func (s *StockImpl) KubeCA() asset.Asset { return s.kubeCA } + +// EtcdCA is the asset that generates the etcd-ca key/cert pair. +func (s *StockImpl) EtcdCA() asset.Asset { return s.etcdCA } + +// AggregatorCA is the asset that generates the aggregator-ca key/cert pair. +func (s *StockImpl) AggregatorCA() asset.Asset { return s.aggregatorCA } + +// ServiceServingCA is the asset that generates the service-serving-ca key/cert pair. +func (s *StockImpl) ServiceServingCA() asset.Asset { return s.serviceServingCA } + +// EtcdClientCertKey is the asset that generates the etcd client key/cert pair. +func (s *StockImpl) EtcdClientCertKey() asset.Asset { return s.etcdClientCertKey } + +// AdminCertKey is the asset that generates the admin key/cert pair. +func (s *StockImpl) AdminCertKey() asset.Asset { return s.adminCertKey } + +// IngressCertKey is the asset that generates the ingress key/cert pair. +func (s *StockImpl) IngressCertKey() asset.Asset { return s.ingressCertKey } + +// APIServerCertKey is the asset that generates the API server key/cert pair. +func (s *StockImpl) APIServerCertKey() asset.Asset { return s.apiServerCertKey } + +// OpenshiftAPIServerCertKey is the asset that generates the Openshift API server key/cert pair. +func (s *StockImpl) OpenshiftAPIServerCertKey() asset.Asset { return s.openshiftAPIServerCertKey } + +// APIServerProxyCertKey is the asset that generates the API server proxy key/cert pair. +func (s *StockImpl) APIServerProxyCertKey() asset.Asset { return s.apiServerProxyCertKey } + +// KubeletCertKey is the asset that generates the kubelet key/cert pair. +func (s *StockImpl) KubeletCertKey() asset.Asset { return s.kubeletCertKey } + +// TNCCertKey is the asset that generates the TNC key/cert pair. +func (s *StockImpl) TNCCertKey() asset.Asset { return s.tncCertKey } + +// ClusterAPIServerCertKey is the asset that generates the cluster API server key/cert pair. +func (s *StockImpl) ClusterAPIServerCertKey() asset.Asset { return s.clusterAPIServerCertKey } + +// ServiceAccountKeyPair is the asset that generates the service-account public/private key pair. +func (s *StockImpl) ServiceAccountKeyPair() asset.Asset { return s.serviceAccountKeyPair } diff --git a/installer/pkg/tls/tls.go b/pkg/asset/tls/tls.go similarity index 60% rename from installer/pkg/tls/tls.go rename to pkg/asset/tls/tls.go index 9553d660df9..7990a6545d3 100644 --- a/installer/pkg/tls/tls.go +++ b/pkg/asset/tls/tls.go @@ -20,6 +20,13 @@ import ( const ( keySize = 2048 + + // ValidityTenYears sets the validity of a cert to 10 years. + ValidityTenYears = time.Hour * 24 * 365 * 10 + + // ValidityThirtyMinutes sets the validity of a cert to 30 minutes. + // This is for the kubelet bootstrap. + ValidityThirtyMinutes = time.Minute * 30 ) // CertCfg contains all needed fields to configure a new certificate @@ -136,3 +143,72 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { hash := sha1.Sum(publicKeyBytes) return hash[:], nil } + +// GenerateCert creates a key, csr & a signed cert +// This is useful for apiserver and openshift-apiser cert which will be +// authenticated by the kubeconfig using root-ca. +func GenerateCert(caKey *rsa.PrivateKey, + caCert *x509.Certificate, + cfg *CertCfg) (*rsa.PrivateKey, *x509.Certificate, error) { + + // create a private key + key, err := PrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("failed to generate private key: %v", err) + } + + // create a CSR + csrTmpl := x509.CertificateRequest{Subject: cfg.Subject, DNSNames: cfg.DNSNames, IPAddresses: cfg.IPAddresses} + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTmpl, key) + if err != nil { + return nil, nil, fmt.Errorf("error creating certificate request: %v", err) + } + csr, err := x509.ParseCertificateRequest(csrBytes) + if err != nil { + return nil, nil, fmt.Errorf("error parsing certificate request: %v", err) + } + + // create a cert + cert, err := GenerateSignedCert(cfg, csr, key, caKey, caCert) + if err != nil { + return nil, nil, fmt.Errorf("failed to create a certificate: %v", err) + } + return key, cert, nil +} + +// GenerateRootCA creates and returns the root CA +func GenerateRootCA(key *rsa.PrivateKey, cfg *CertCfg) (*x509.Certificate, error) { + cert, err := SelfSignedCACert(cfg, key) + if err != nil { + return nil, fmt.Errorf("error generating self signed certificate: %v", err) + } + return cert, nil +} + +// GenerateSignedCert generates a signed certificate. +func GenerateSignedCert(cfg *CertCfg, + csr *x509.CertificateRequest, + key *rsa.PrivateKey, + caKey *rsa.PrivateKey, + caCert *x509.Certificate) (*x509.Certificate, error) { + cert, err := SignedCertificate(cfg, csr, key, caCert, caKey) + if err != nil { + return nil, fmt.Errorf("error signing certificate: %v", err) + } + return cert, nil +} + +// GenerateRootCertKey generates a root key/cert pair. +func GenerateRootCertKey(cfg *CertCfg) (*rsa.PrivateKey, *x509.Certificate, error) { + key, err := PrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("failed to generate private key: %v", err) + } + + crt, err := GenerateRootCA(key, cfg) + if err != nil { + return nil, nil, fmt.Errorf("failed to create a certificate: %v", err) + } + + return key, crt, nil +} diff --git a/installer/pkg/tls/tls_test.go b/pkg/asset/tls/tls_test.go similarity index 100% rename from installer/pkg/tls/tls_test.go rename to pkg/asset/tls/tls_test.go diff --git a/installer/pkg/tls/utils.go b/pkg/asset/tls/utils.go similarity index 75% rename from installer/pkg/tls/utils.go rename to pkg/asset/tls/utils.go index 4c322e5e720..e972476d12e 100644 --- a/installer/pkg/tls/utils.go +++ b/pkg/asset/tls/utils.go @@ -54,3 +54,15 @@ func PublicKeyToPem(key *rsa.PublicKey) (string, error) { ) return string(keyinPem), nil } + +// PemToPrivateKey converts a data block to rsa.PrivateKey. +func PemToPrivateKey(data []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(data) + return x509.ParsePKCS1PrivateKey(block.Bytes) +} + +// PemToCertificate converts a data block to x509.Certificate. +func PemToCertificate(data []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(data) + return x509.ParseCertificate(block.Bytes) +}