Merge pull request #70809 from liztio/csr

Generate CSRs for kubeadm
pull/58/head
k8s-ci-robot 2018-11-15 15:00:02 -08:00 committed by GitHub
commit 3d3cc63cc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 526 additions and 9 deletions

View File

@ -62,6 +62,7 @@ go_test(
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs:go_default_library",
"//cmd/kubeadm/app/util/pkiutil:go_default_library", "//cmd/kubeadm/app/util/pkiutil:go_default_library",
"//cmd/kubeadm/test:go_default_library", "//cmd/kubeadm/test:go_default_library",
"//cmd/kubeadm/test/certs:go_default_library", "//cmd/kubeadm/test/certs:go_default_library",

View File

@ -76,6 +76,8 @@ type renewConfig struct {
kubeconfigPath string kubeconfigPath string
cfg kubeadmapiv1beta1.InitConfiguration cfg kubeadmapiv1beta1.InitConfiguration
useAPI bool useAPI bool
useCSR bool
csrPath string
} }
func getRenewSubCommands() []*cobra.Command { func getRenewSubCommands() []*cobra.Command {
@ -126,6 +128,8 @@ func addFlags(cmd *cobra.Command, cfg *renewConfig) {
options.AddConfigFlag(cmd.Flags(), &cfg.cfgPath) options.AddConfigFlag(cmd.Flags(), &cfg.cfgPath)
options.AddCertificateDirFlag(cmd.Flags(), &cfg.cfg.CertificatesDir) options.AddCertificateDirFlag(cmd.Flags(), &cfg.cfg.CertificatesDir)
options.AddKubeConfigFlag(cmd.Flags(), &cfg.kubeconfigPath) options.AddKubeConfigFlag(cmd.Flags(), &cfg.kubeconfigPath)
options.AddCSRFlag(cmd.Flags(), &cfg.useCSR)
options.AddCSRDirFlag(cmd.Flags(), &cfg.csrPath)
cmd.Flags().BoolVar(&cfg.useAPI, "use-api", cfg.useAPI, "Use the Kubernetes certificate API to renew certificates") cmd.Flags().BoolVar(&cfg.useAPI, "use-api", cfg.useAPI, "Use the Kubernetes certificate API to renew certificates")
} }
@ -133,6 +137,17 @@ func generateRenewalFunction(cert *certsphase.KubeadmCert, caCert *certsphase.Ku
return func() { return func() {
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfg.cfgPath, &cfg.cfg) internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfg.cfgPath, &cfg.cfg)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
if cfg.useCSR {
path := cfg.csrPath
if path == "" {
path = cfg.cfg.CertificatesDir
}
err := certsphase.CreateCSR(cert, internalcfg, path)
kubeadmutil.CheckErr(err)
return
}
renewer, err := getRenewer(cfg, caCert.BaseName) renewer, err := getRenewer(cfg, caCert.BaseName)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)

View File

@ -30,6 +30,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test" testutil "k8s.io/kubernetes/cmd/kubeadm/test"
certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs" certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs"
@ -219,3 +220,16 @@ func TestRunRenewCommands(t *testing.T) {
}) })
} }
} }
func TestRenewUsingCSR(t *testing.T) {
tmpDir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpDir)
cert := &certs.KubeadmCertEtcdServer
renewCmds := getRenewSubCommands()
cmdtestutil.RunSubCommand(t, renewCmds, cert.Name, "--csr-only", "--csr-dir="+tmpDir)
if _, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(tmpDir, cert.BaseName); err != nil {
t.Fatalf("couldn't load certificate %q: %v", cert.BaseName, err)
}
}

View File

@ -22,3 +22,13 @@ import "github.com/spf13/pflag"
func AddCertificateDirFlag(fs *pflag.FlagSet, certsDir *string) { func AddCertificateDirFlag(fs *pflag.FlagSet, certsDir *string) {
fs.StringVar(certsDir, CertificatesDir, *certsDir, "The path where to save the certificates") fs.StringVar(certsDir, CertificatesDir, *certsDir, "The path where to save the certificates")
} }
// AddCSRFlag adds the --csr-only flag to the given flagset
func AddCSRFlag(fs *pflag.FlagSet, csr *bool) {
fs.BoolVar(csr, CSROnly, *csr, "Create CSRs instead of generating certificates")
}
// AddCSRDirFlag adds the --csr-dir flag to the given flagset
func AddCSRDirFlag(fs *pflag.FlagSet, csrDir *string) {
fs.StringVar(csrDir, CSRDir, *csrDir, "The path to output the CSRs and private keys to")
}

View File

@ -78,3 +78,9 @@ const SchedulerExtraArgs = "scheduler-extra-args"
// SkipTokenPrint flag instruct kubeadm to skip printing of the default bootstrap token generated by 'kubeadm init'. // SkipTokenPrint flag instruct kubeadm to skip printing of the default bootstrap token generated by 'kubeadm init'.
const SkipTokenPrint = "skip-token-print" const SkipTokenPrint = "skip-token-print"
// CSROnly flag instructs kubeadm to create CSRs instead of automatically creating or renewing certs
const CSROnly = "csr-only"
// CSRDir flag sets the location for CSRs and flags to be output
const CSRDir = "csr-dir"

View File

@ -52,6 +52,7 @@ go_library(
"//vendor/github.com/pkg/errors:go_default_library", "//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/renstrom/dedent:go_default_library", "//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
], ],
@ -59,10 +60,18 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["util_test.go"], srcs = [
"certs_test.go",
"util_test.go",
],
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/cmd/phases/workflow:go_default_library",
"//cmd/kubeadm/app/phases/certs:go_default_library",
"//cmd/kubeadm/app/util/pkiutil:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//pkg/version:go_default_library", "//pkg/version:go_default_library",
], ],
) )

View File

@ -21,6 +21,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1" kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
@ -47,6 +48,11 @@ var (
` + cmdutil.AlphaDisclaimer) ` + cmdutil.AlphaDisclaimer)
) )
var (
csrOnly bool
csrDir string
)
// certsData defines the behavior that a runtime data struct passed to the certs phase should // certsData defines the behavior that a runtime data struct passed to the certs phase should
// have. Please note that we are using an interface in order to make this phase reusable in different workflows // have. Please note that we are using an interface in order to make this phase reusable in different workflows
// (and thus with different runtime data struct, all of them requested to be compliant to this interface) // (and thus with different runtime data struct, all of them requested to be compliant to this interface)
@ -65,9 +71,17 @@ func NewCertsPhase() workflow.Phase {
Phases: newCertSubPhases(), Phases: newCertSubPhases(),
Run: runCerts, Run: runCerts,
InheritFlags: getCertPhaseFlags("all"), InheritFlags: getCertPhaseFlags("all"),
LocalFlags: localFlags(),
} }
} }
func localFlags() *pflag.FlagSet {
set := pflag.NewFlagSet("csr", pflag.ExitOnError)
options.AddCSRFlag(set, &csrOnly)
options.AddCSRDirFlag(set, &csrDir)
return set
}
// newCertSubPhases returns sub phases for certs phase // newCertSubPhases returns sub phases for certs phase
func newCertSubPhases() []workflow.Phase { func newCertSubPhases() []workflow.Phase {
subPhases := []workflow.Phase{} subPhases := []workflow.Phase{}
@ -109,6 +123,7 @@ func newCertSubPhase(certSpec *certsphase.KubeadmCert, run func(c workflow.RunDa
), ),
Run: run, Run: run,
InheritFlags: getCertPhaseFlags(certSpec.Name), InheritFlags: getCertPhaseFlags(certSpec.Name),
LocalFlags: localFlags(),
} }
return phase return phase
} }
@ -117,6 +132,8 @@ func getCertPhaseFlags(name string) []string {
flags := []string{ flags := []string{
options.CertificatesDir, options.CertificatesDir,
options.CfgPath, options.CfgPath,
options.CSROnly,
options.CSRDir,
} }
if name == "all" || name == "apiserver" { if name == "all" || name == "apiserver" {
flags = append(flags, flags = append(flags,
@ -140,7 +157,8 @@ func getSANDescription(certSpec *certsphase.KubeadmCert) string {
defaultInternalConfig := &kubeadmapi.InitConfiguration{} defaultInternalConfig := &kubeadmapi.InitConfiguration{}
kubeadmscheme.Scheme.Default(defaultConfig) kubeadmscheme.Scheme.Default(defaultConfig)
kubeadmscheme.Scheme.Convert(defaultConfig, defaultInternalConfig, nil) err := kubeadmscheme.Scheme.Convert(defaultConfig, defaultInternalConfig, nil)
kubeadmutil.CheckErr(err)
certConfig, err := certSpec.GetConfig(defaultInternalConfig) certConfig, err := certSpec.GetConfig(defaultInternalConfig)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
@ -236,6 +254,15 @@ func runCertPhase(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert)
return nil return nil
} }
if csrOnly {
fmt.Printf("[certs] Generating CSR for %s instead of certificate\n", cert.BaseName)
if csrDir == "" {
csrDir = data.CertificateWriteDir()
}
return certsphase.CreateCSR(cert, data.Cfg(), csrDir)
}
// if using external etcd, skips etcd certificates generation // if using external etcd, skips etcd certificates generation
if data.Cfg().Etcd.External != nil && cert.CAName == "etcd-ca" { if data.Cfg().Etcd.External != nil && cert.CAName == "etcd-ca" {
fmt.Printf("[certs] External etcd mode: Skipping %s certificate authority generation\n", cert.BaseName) fmt.Printf("[certs] External etcd mode: Skipping %s certificate authority generation\n", cert.BaseName)

View File

@ -0,0 +1,77 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package phases
import (
"os"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
type testCertsData struct {
cfg *kubeadmapi.InitConfiguration
}
func (t *testCertsData) Cfg() *kubeadmapi.InitConfiguration { return t.cfg }
func (t *testCertsData) ExternalCA() bool { return false }
func (t *testCertsData) CertificateDir() string { return t.cfg.CertificatesDir }
func (t *testCertsData) CertificateWriteDir() string { return t.cfg.CertificatesDir }
func TestCertsWithCSRs(t *testing.T) {
csrDir := testutil.SetupTempDir(t)
defer os.RemoveAll(csrDir)
certDir := testutil.SetupTempDir(t)
defer os.RemoveAll(certDir)
cert := &certs.KubeadmCertAPIServer
certsData := &testCertsData{
cfg: testutil.GetDefaultInternalConfig(t),
}
certsData.cfg.CertificatesDir = certDir
// global vars
csrOnly = true
csrDir = certDir
phase := NewCertsPhase()
// find the api cert phase
var apiServerPhase *workflow.Phase
for _, phase := range phase.Phases {
if phase.Name == cert.Name {
apiServerPhase = &phase
break
}
}
if apiServerPhase == nil {
t.Fatalf("couldn't find apiserver phase")
}
err := apiServerPhase.Run(certsData)
if err != nil {
t.Fatalf("couldn't run API server phase: %v", err)
}
if _, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(csrDir, cert.BaseName); err != nil {
t.Fatalf("couldn't load certificate %q: %v", cert.BaseName, err)
}
}

View File

@ -21,6 +21,7 @@ go_test(
"//cmd/kubeadm/test/certs:go_default_library", "//cmd/kubeadm/test/certs:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library", "//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
], ],
) )

View File

@ -130,6 +130,25 @@ func CreateCACertAndKeyFiles(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfigur
) )
} }
// NewCSR will generate a new CSR and accompanying key
func NewCSR(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
certConfig, err := certSpec.GetConfig(cfg)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve cert configuration: %v", err)
}
return pkiutil.NewCSRAndKey(certConfig)
}
// CreateCSR creates a certificate signing request
func CreateCSR(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration, path string) error {
csr, key, err := NewCSR(certSpec, cfg)
if err != nil {
return err
}
return writeCSRFilesIfNotExist(path, certSpec.BaseName, csr, key)
}
// CreateCertAndKeyFilesWithCA loads the given certificate authority from disk, then generates and writes out the given certificate and key. // CreateCertAndKeyFilesWithCA loads the given certificate authority from disk, then generates and writes out the given certificate and key.
// The certSpec and caCertSpec should both be one of the variables from this package. // The certSpec and caCertSpec should both be one of the variables from this package.
func CreateCertAndKeyFilesWithCA(certSpec *KubeadmCert, caCertSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration) error { func CreateCertAndKeyFilesWithCA(certSpec *KubeadmCert, caCertSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration) error {
@ -276,6 +295,33 @@ func writeKeyFilesIfNotExist(pkiDir string, baseName string, key *rsa.PrivateKey
return nil return nil
} }
// writeCertificateAuthorithyFilesIfNotExist write a new CSR to the given path.
// If there already is a CSR file at the given path; kubeadm tries to load it and check if it's a valid certificate.
// otherwise this function returns an error.
func writeCSRFilesIfNotExist(csrDir string, baseName string, csr *x509.CertificateRequest, key *rsa.PrivateKey) error {
if pkiutil.CSROrKeyExist(csrDir, baseName) {
_, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(csrDir, baseName)
if err != nil {
return errors.Wrapf(err, "%s CSR existed but it could not be loaded properly", baseName)
}
fmt.Printf("[certs] Using the existing %q CSR\n", baseName)
} else {
// Write .key and .csr files to disk
fmt.Printf("[certs] Generating %q key and CSR\n", baseName)
if err := pkiutil.WriteKey(csrDir, baseName, key); err != nil {
return errors.Wrapf(err, "failure while saving %s key", baseName)
}
if err := pkiutil.WriteCSR(csrDir, baseName, csr); err != nil {
return errors.Wrapf(err, "failure while saving %s CSR", baseName)
}
}
return nil
}
type certKeyLocation struct { type certKeyLocation struct {
pkiDir string pkiDir string
caBaseName string caBaseName string

View File

@ -18,13 +18,16 @@ package certs
import ( import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
certutil "k8s.io/client-go/util/cert" certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
@ -55,6 +58,18 @@ func createTestCert(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKe
return cert, key return cert, key
} }
func createTestCSR(t *testing.T) (*x509.CertificateRequest, *rsa.PrivateKey) {
csr, key, err := pkiutil.NewCSRAndKey(
&certutil.Config{
CommonName: "testCert",
})
if err != nil {
t.Fatalf("couldn't create test cert: %v", err)
}
return csr, key
}
func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) { func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) {
setupCert, setupKey := createCACert(t) setupCert, setupKey := createCACert(t)
caCert, caKey := createCACert(t) caCert, caKey := createCACert(t)
@ -209,6 +224,75 @@ func TestWriteCertificateFilesIfNotExist(t *testing.T) {
} }
} }
func TestWriteCSRFilesIfNotExist(t *testing.T) {
csr, key := createTestCSR(t)
csr2, key2 := createTestCSR(t)
var tests = []struct {
name string
setupFunc func(csrPath string) error
expectedError bool
expectedCSR *x509.CertificateRequest
}{
{
name: "no files exist",
expectedCSR: csr,
},
{
name: "other key exists",
setupFunc: func(csrPath string) error {
if err := pkiutil.WriteCSR(csrPath, "dummy", csr2); err != nil {
return err
}
return pkiutil.WriteKey(csrPath, "dummy", key2)
},
expectedCSR: csr2,
},
{
name: "existing CSR is garbage",
setupFunc: func(csrPath string) error {
return ioutil.WriteFile(path.Join(csrPath, "dummy.csr"), []byte("a--bunch--of-garbage"), os.ModePerm)
},
expectedError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
if test.setupFunc != nil {
if err := test.setupFunc(tmpdir); err != nil {
t.Fatalf("couldn't set up test: %v", err)
}
}
if err := writeCSRFilesIfNotExist(tmpdir, "dummy", csr, key); err != nil {
if test.expectedError {
return
}
t.Fatalf("unexpected error %v: ", err)
}
if test.expectedError {
t.Fatal("Expected error, but got none")
}
parsedCSR, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(tmpdir, "dummy")
if err != nil {
t.Fatalf("couldn't load csr and key: %v", err)
}
if sha256.Sum256(test.expectedCSR.Raw) != sha256.Sum256(parsedCSR.Raw) {
t.Error("expected csr's fingerprint does not match ")
}
})
}
}
func TestWriteKeyFilesIfNotExist(t *testing.T) { func TestWriteKeyFilesIfNotExist(t *testing.T) {
setupKey, _ := NewServiceAccountSigningKey() setupKey, _ := NewServiceAccountSigningKey()
@ -606,6 +690,38 @@ func TestValidateMethods(t *testing.T) {
} }
} }
func TestNewCSR(t *testing.T) {
kubeadmCert := KubeadmCertAPIServer
cfg := testutil.GetDefaultInternalConfig(t)
certConfig, err := kubeadmCert.GetConfig(cfg)
if err != nil {
t.Fatalf("couldn't get cert config: %v", err)
}
csr, _, err := NewCSR(&kubeadmCert, cfg)
if err != nil {
t.Errorf("invalid signature on CSR: %v", err)
}
assert.ElementsMatch(t, certConfig.Organization, csr.Subject.Organization, "organizations not equal")
if csr.Subject.CommonName != certConfig.CommonName {
t.Errorf("expected common name %q, got %q", certConfig.CommonName, csr.Subject.CommonName)
}
assert.ElementsMatch(t, certConfig.AltNames.DNSNames, csr.DNSNames, "dns names not equal")
assert.Len(t, csr.IPAddresses, len(certConfig.AltNames.IPs))
for i, ip := range csr.IPAddresses {
if !ip.Equal(certConfig.AltNames.IPs[i]) {
t.Errorf("[%d]: %v != %v", i, ip, certConfig.AltNames.IPs[i])
}
}
}
type pkiFiles map[string]interface{} type pkiFiles map[string]interface{}
func writePKIFiles(t *testing.T, dir string, files pkiFiles) { func writePKIFiles(t *testing.T, dir string, files pkiFiles) {

View File

@ -17,9 +17,14 @@ limitations under the License.
package pkiutil package pkiutil
import ( import (
"crypto"
cryptorand "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -65,6 +70,21 @@ func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *cert
return cert, key, nil return cert, key, nil
} }
// NewCSRAndKey generates a new key and CSR and that could be signed to create the given certificate
func NewCSRAndKey(config *certutil.Config) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
key, err := certutil.NewPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create private key")
}
csr, err := NewCSR(*config, key)
if err != nil {
return nil, nil, errors.Wrap(err, "unable to generate CSR")
}
return csr, key, nil
}
// HasServerAuth returns true if the given certificate is a ServerAuth // HasServerAuth returns true if the given certificate is a ServerAuth
func HasServerAuth(cert *x509.Certificate) bool { func HasServerAuth(cert *x509.Certificate) bool {
for i := range cert.ExtKeyUsage { for i := range cert.ExtKeyUsage {
@ -78,7 +98,7 @@ func HasServerAuth(cert *x509.Certificate) bool {
// WriteCertAndKey stores certificate and key at the specified location // WriteCertAndKey stores certificate and key at the specified location
func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error { func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
if err := WriteKey(pkiPath, name, key); err != nil { if err := WriteKey(pkiPath, name, key); err != nil {
return err return errors.Wrap(err, "couldn't write key")
} }
return WriteCert(pkiPath, name, cert) return WriteCert(pkiPath, name, cert)
@ -112,6 +132,27 @@ func WriteKey(pkiPath, name string, key *rsa.PrivateKey) error {
return nil return nil
} }
// WriteCSR writes the pem-encoded CSR data to csrPath.
// The CSR file will be created with file mode 0644.
// If the CSR file already exists, it will be overwritten.
// The parent directory of the csrPath will be created as needed with file mode 0755.
func WriteCSR(csrDir, name string, csr *x509.CertificateRequest) error {
if csr == nil {
return errors.New("certificate request cannot be nil when writing to file")
}
csrPath := pathForCSR(csrDir, name)
if err := os.MkdirAll(filepath.Dir(csrPath), os.FileMode(0755)); err != nil {
return errors.Wrapf(err, "failed to make directory %s", filepath.Dir(csrPath))
}
if err := ioutil.WriteFile(csrPath, EncodeCSRPEM(csr), os.FileMode(0644)); err != nil {
return errors.Wrapf(err, "unable to write CSR to file %s", csrPath)
}
return nil
}
// WritePublicKey stores the given public key at the given location // WritePublicKey stores the given public key at the given location
func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error { func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error {
if key == nil { if key == nil {
@ -145,16 +186,27 @@ func CertOrKeyExist(pkiPath, name string) bool {
return true return true
} }
// CSROrKeyExist returns true if one of the CSR or key exists
func CSROrKeyExist(csrDir, name string) bool {
csrPath := pathForCSR(csrDir, name)
keyPath := pathForKey(csrDir, name)
_, csrErr := os.Stat(csrPath)
_, keyErr := os.Stat(keyPath)
return !(os.IsNotExist(csrErr) && os.IsNotExist(keyErr))
}
// TryLoadCertAndKeyFromDisk tries to load a cert and a key from the disk and validates that they are valid // TryLoadCertAndKeyFromDisk tries to load a cert and a key from the disk and validates that they are valid
func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) { func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) {
cert, err := TryLoadCertFromDisk(pkiPath, name) cert, err := TryLoadCertFromDisk(pkiPath, name)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, errors.Wrap(err, "failed to load certificate")
} }
key, err := TryLoadKeyFromDisk(pkiPath, name) key, err := TryLoadKeyFromDisk(pkiPath, name)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, errors.Wrap(err, "failed to load key")
} }
return cert, key, nil return cert, key, nil
@ -207,6 +259,23 @@ func TryLoadKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, error) {
return key, nil return key, nil
} }
// TryLoadCSRAndKeyFromDisk tries to load the CSR and key from the disk
func TryLoadCSRAndKeyFromDisk(pkiPath, name string) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
csrPath := pathForCSR(pkiPath, name)
csr, err := CertificateRequestFromFile(csrPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "couldn't load the certificate request %s", csrPath)
}
key, err := TryLoadKeyFromDisk(pkiPath, name)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't load key file")
}
return csr, key, nil
}
// TryLoadPrivatePublicKeyFromDisk tries to load the key from the disk and validates that it is valid // TryLoadPrivatePublicKeyFromDisk tries to load the key from the disk and validates that it is valid
func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rsa.PublicKey, error) { func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rsa.PublicKey, error) {
privateKeyPath := pathForKey(pkiPath, name) privateKeyPath := pathForKey(pkiPath, name)
@ -253,6 +322,10 @@ func pathForPublicKey(pkiPath, name string) string {
return filepath.Join(pkiPath, fmt.Sprintf("%s.pub", name)) return filepath.Join(pkiPath, fmt.Sprintf("%s.pub", name))
} }
func pathForCSR(pkiPath, name string) string {
return filepath.Join(pkiPath, fmt.Sprintf("%s.csr", name))
}
// GetAPIServerAltNames builds an AltNames object for to be used when generating apiserver certificate // GetAPIServerAltNames builds an AltNames object for to be used when generating apiserver certificate
func GetAPIServerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) { func GetAPIServerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) {
// advertise address // advertise address
@ -373,3 +446,61 @@ func appendSANsToAltNames(altNames *certutil.AltNames, SANs []string, certName s
} }
} }
} }
// EncodeCSRPEM returns PEM-encoded CSR data
func EncodeCSRPEM(csr *x509.CertificateRequest) []byte {
block := pem.Block{
Type: certutil.CertificateRequestBlockType,
Bytes: csr.Raw,
}
return pem.EncodeToMemory(&block)
}
func parseCSRPEM(pemCSR []byte) (*x509.CertificateRequest, error) {
block, _ := pem.Decode(pemCSR)
if block == nil {
return nil, fmt.Errorf("data doesn't contain a valid certificate request")
}
if block.Type != certutil.CertificateRequestBlockType {
var block *pem.Block
return nil, fmt.Errorf("expected block type %q, but PEM had type %v", certutil.CertificateRequestBlockType, block.Type)
}
return x509.ParseCertificateRequest(block.Bytes)
}
// CertificateRequestFromFile returns the CertificateRequest from a given PEM-encoded file.
// Returns an error if the file could not be read or if the CSR could not be parsed.
func CertificateRequestFromFile(file string) (*x509.CertificateRequest, error) {
pemBlock, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.Wrap(err, "failed to read file")
}
csr, err := parseCSRPEM(pemBlock)
if err != nil {
return nil, fmt.Errorf("error reading certificate request file %s: %v", file, err)
}
return csr, nil
}
// NewCSR creates a new CSR
func NewCSR(cfg certutil.Config, key crypto.Signer) (*x509.CertificateRequest, error) {
template := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
}
csrBytes, err := x509.CreateCertificateRequest(cryptorand.Reader, template, key)
if err != nil {
return nil, errors.Wrap(err, "failed to create a CSR")
}
return x509.ParseCertificateRequest(csrBytes)
}

View File

@ -29,6 +29,24 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
) )
// certificateRequest is an x509 certificate request in PEM encoded format
// openssl req -new -key rsa2048.pem -sha256 -nodes -out x509certrequest.pem -subj "/C=US/CN=not-valid"
const certificateRequest = `-----BEGIN CERTIFICATE REQUEST-----
MIICZjCCAU4CAQAwITELMAkGA1UEBhMCVVMxEjAQBgNVBAMMCW5vdC12YWxpZDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdoBxV0SbSS+7XrgVDF/P4x
tqyun+DLxeRF5265ZOFRJDXCJgYH7wKlxlkEaHZQhnNmnqFiy96MHSKaiQmlkEm4
EhlqTf38yEWx+t98A0CDbHsIPZ0/+MPCjb2kf+OfBXJJl908io0grs02jxN9lceL
RFrKT6vaB+6i7LxbPQcOmjF7OUqWS6S2qSpShw2GY+mJz4HM7OFb9RcN4izh+GF6
7hajYgt7pAFyWF1ua/H98Ysn4FVgIYk30rHCNBkQpJnna7EyGYuj08VuFa088W9g
c/DCpL+VgBDwTel9tfeMxRAoLIPF9iJ8Ftr7dsRZ/Y/SnxfUJo2ed8y7dgIiLuEC
AwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCOjPB/4LKa2G7LarMMLAeNqvWF9SIG
y2VGQoTn9D5blXMvnfzWSYgU6nBzf/E/32q26OwiCriuOPXfxM/cxEMOJ62u7b50
OR52JFvQdONsCZaLgylGWppl0YeqylbTosHjsWJNlp+zjXcQHjCQ9OoLgfmrwYyD
2MsYJR4p7JZ2ZN8FF1hgMUrDzypZ0NSBKAiQMU9TFhxgyk75RNDtmX+2K35zqLyr
0otimyYwPCGPD2GHwNfvu1oP0A+/cT+rCPz6AlXhWEbz2JkLo6/muRfRl0QSRgHE
Q3+eWlA1YdqEBwvp3NEQI9BtMnzxJVWA5dvYluMNllsV/q8s2IEEAFG9
-----END CERTIFICATE REQUEST-----`
func TestNewCertificateAuthority(t *testing.T) { func TestNewCertificateAuthority(t *testing.T) {
cert, key, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) cert, key, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"})
@ -435,6 +453,13 @@ func TestPathForPublicKey(t *testing.T) {
} }
} }
func TestPathForCSR(t *testing.T) {
csrPath := pathForCSR("/foo", "bar")
if csrPath != "/foo/bar.csr" {
t.Errorf("unexpected certificate path: %s", csrPath)
}
}
func TestGetAPIServerAltNames(t *testing.T) { func TestGetAPIServerAltNames(t *testing.T) {
var tests = []struct { var tests = []struct {
@ -444,7 +469,7 @@ func TestGetAPIServerAltNames(t *testing.T) {
expectedIPAddresses []string expectedIPAddresses []string
}{ }{
{ {
name: "ControlPlaneEndpoint DNS", name: "",
cfg: &kubeadmapi.InitConfiguration{ cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"}, LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{ ClusterConfiguration: kubeadmapi.ClusterConfiguration{

View File

@ -33,6 +33,9 @@ go_test(
"skip", "skip",
], ],
deps = [ deps = [
"//cmd/kubeadm/app/phases/certs:go_default_library",
"//cmd/kubeadm/app/util/pkiutil:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//vendor/github.com/renstrom/dedent:go_default_library", "//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library", "//vendor/sigs.k8s.io/yaml:go_default_library",
], ],

View File

@ -20,6 +20,9 @@ import (
"testing" "testing"
"github.com/renstrom/dedent" "github.com/renstrom/dedent"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
) )
func runKubeadmInit(args ...string) (string, string, error) { func runKubeadmInit(args ...string) (string, string, error) {
@ -191,6 +194,33 @@ func TestCmdInitConfig(t *testing.T) {
} }
} }
func TestCmdInitCertPhaseCSR(t *testing.T) {
if *kubeadmCmdSkip {
t.Log("kubeadm cmd tests being skipped")
t.Skip()
}
csrDir := testutil.SetupTempDir(t)
cert := &certs.KubeadmCertKubeletClient
kubeadmPath := getKubeadmPath()
_, _, err := RunCmd(kubeadmPath,
"init",
"phase",
"certs",
cert.BaseName,
"--csr-only",
"--csr-dir="+csrDir,
)
if err != nil {
t.Fatalf("couldn't run kubeadm: %v", err)
}
if _, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(csrDir, cert.BaseName); err != nil {
t.Fatalf("couldn't load certificate %q: %v", cert.BaseName, err)
}
}
func TestCmdInitAPIPort(t *testing.T) { func TestCmdInitAPIPort(t *testing.T) {
if *kubeadmCmdSkip { if *kubeadmCmdSkip {
t.Log("kubeadm cmd tests being skipped") t.Log("kubeadm cmd tests being skipped")

View File

@ -18,24 +18,30 @@ package kubeadm
import ( import (
"bytes" "bytes"
"github.com/pkg/errors"
"os/exec" "os/exec"
"testing" "testing"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// Forked from test/e2e/framework because the e2e framework is quite bloated // Forked from test/e2e/framework because the e2e framework is quite bloated
// for our purposes here, and modified to remove undesired logging. // for our purposes here, and modified to remove undesired logging.
// RunCmd is a utility function for kubeadm testing that executes a specified command func runCmdNoWrap(command string, args ...string) (string, string, error) {
func RunCmd(command string, args ...string) (string, string, error) {
var bout, berr bytes.Buffer var bout, berr bytes.Buffer
cmd := exec.Command(command, args...) cmd := exec.Command(command, args...)
cmd.Stdout = &bout cmd.Stdout = &bout
cmd.Stderr = &berr cmd.Stderr = &berr
err := cmd.Run() err := cmd.Run()
stdout, stderr := bout.String(), berr.String() stdout, stderr := bout.String(), berr.String()
return stdout, stderr, err
}
// RunCmd is a utility function for kubeadm testing that executes a specified command
func RunCmd(command string, args ...string) (string, string, error) {
stdout, stderr, err := runCmdNoWrap(command, args...)
if err != nil { if err != nil {
return "", "", errors.Wrapf(err, "error running %s %v; \nstdout %q, \nstderr %q, \ngot error", return "", "", errors.Wrapf(err, "error running %s %v; \nstdout %q, \nstderr %q, \ngot error",
command, args, stdout, stderr) command, args, stdout, stderr)