mirror of https://github.com/k3s-io/k3s
commit
3d3cc63cc8
|
@ -62,6 +62,7 @@ go_test(
|
|||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//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/test:go_default_library",
|
||||
"//cmd/kubeadm/test/certs:go_default_library",
|
||||
|
|
|
@ -76,6 +76,8 @@ type renewConfig struct {
|
|||
kubeconfigPath string
|
||||
cfg kubeadmapiv1beta1.InitConfiguration
|
||||
useAPI bool
|
||||
useCSR bool
|
||||
csrPath string
|
||||
}
|
||||
|
||||
func getRenewSubCommands() []*cobra.Command {
|
||||
|
@ -126,6 +128,8 @@ func addFlags(cmd *cobra.Command, cfg *renewConfig) {
|
|||
options.AddConfigFlag(cmd.Flags(), &cfg.cfgPath)
|
||||
options.AddCertificateDirFlag(cmd.Flags(), &cfg.cfg.CertificatesDir)
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -133,6 +137,17 @@ func generateRenewalFunction(cert *certsphase.KubeadmCert, caCert *certsphase.Ku
|
|||
return func() {
|
||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfg.cfgPath, &cfg.cfg)
|
||||
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)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,3 +22,13 @@ import "github.com/spf13/pflag"
|
|||
func AddCertificateDirFlag(fs *pflag.FlagSet, certsDir *string) {
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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'.
|
||||
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"
|
||||
|
|
|
@ -52,6 +52,7 @@ go_library(
|
|||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/github.com/renstrom/dedent: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/utils/exec:go_default_library",
|
||||
],
|
||||
|
@ -59,10 +60,18 @@ go_library(
|
|||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["util_test.go"],
|
||||
srcs = [
|
||||
"certs_test.go",
|
||||
"util_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm: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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
|
||||
|
@ -47,6 +48,11 @@ var (
|
|||
` + cmdutil.AlphaDisclaimer)
|
||||
)
|
||||
|
||||
var (
|
||||
csrOnly bool
|
||||
csrDir string
|
||||
)
|
||||
|
||||
// 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
|
||||
// (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(),
|
||||
Run: runCerts,
|
||||
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
|
||||
func newCertSubPhases() []workflow.Phase {
|
||||
subPhases := []workflow.Phase{}
|
||||
|
@ -109,6 +123,7 @@ func newCertSubPhase(certSpec *certsphase.KubeadmCert, run func(c workflow.RunDa
|
|||
),
|
||||
Run: run,
|
||||
InheritFlags: getCertPhaseFlags(certSpec.Name),
|
||||
LocalFlags: localFlags(),
|
||||
}
|
||||
return phase
|
||||
}
|
||||
|
@ -117,6 +132,8 @@ func getCertPhaseFlags(name string) []string {
|
|||
flags := []string{
|
||||
options.CertificatesDir,
|
||||
options.CfgPath,
|
||||
options.CSROnly,
|
||||
options.CSRDir,
|
||||
}
|
||||
if name == "all" || name == "apiserver" {
|
||||
flags = append(flags,
|
||||
|
@ -140,7 +157,8 @@ func getSANDescription(certSpec *certsphase.KubeadmCert) string {
|
|||
defaultInternalConfig := &kubeadmapi.InitConfiguration{}
|
||||
|
||||
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)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
@ -236,6 +254,15 @@ func runCertPhase(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert)
|
|||
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 data.Cfg().Etcd.External != nil && cert.CAName == "etcd-ca" {
|
||||
fmt.Printf("[certs] External etcd mode: Skipping %s certificate authority generation\n", cert.BaseName)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ go_test(
|
|||
"//cmd/kubeadm/test/certs: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/stretchr/testify/assert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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.
|
||||
// The certSpec and caCertSpec should both be one of the variables from this package.
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
pkiDir string
|
||||
caBaseName string
|
||||
|
|
|
@ -18,13 +18,16 @@ package certs
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
setupCert, setupKey := 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) {
|
||||
|
||||
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{}
|
||||
|
||||
func writePKIFiles(t *testing.T, dir string, files pkiFiles) {
|
||||
|
|
|
@ -17,9 +17,14 @@ limitations under the License.
|
|||
package pkiutil
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -65,6 +70,21 @@ func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *cert
|
|||
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
|
||||
func HasServerAuth(cert *x509.Certificate) bool {
|
||||
for i := range cert.ExtKeyUsage {
|
||||
|
@ -78,7 +98,7 @@ func HasServerAuth(cert *x509.Certificate) bool {
|
|||
// WriteCertAndKey stores certificate and key at the specified location
|
||||
func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
|
||||
if err := WriteKey(pkiPath, name, key); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "couldn't write key")
|
||||
}
|
||||
|
||||
return WriteCert(pkiPath, name, cert)
|
||||
|
@ -112,6 +132,27 @@ func WriteKey(pkiPath, name string, key *rsa.PrivateKey) error {
|
|||
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
|
||||
func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error {
|
||||
if key == nil {
|
||||
|
@ -145,16 +186,27 @@ func CertOrKeyExist(pkiPath, name string) bool {
|
|||
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
|
||||
func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
cert, err := TryLoadCertFromDisk(pkiPath, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, errors.Wrap(err, "failed to load certificate")
|
||||
}
|
||||
|
||||
key, err := TryLoadKeyFromDisk(pkiPath, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, errors.Wrap(err, "failed to load key")
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
|
@ -207,6 +259,23 @@ func TryLoadKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, error) {
|
|||
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
|
||||
func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rsa.PublicKey, error) {
|
||||
privateKeyPath := pathForKey(pkiPath, name)
|
||||
|
@ -253,6 +322,10 @@ func pathForPublicKey(pkiPath, name string) string {
|
|||
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
|
||||
func GetAPIServerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) {
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,24 @@ import (
|
|||
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) {
|
||||
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) {
|
||||
|
||||
var tests = []struct {
|
||||
|
@ -444,7 +469,7 @@ func TestGetAPIServerAltNames(t *testing.T) {
|
|||
expectedIPAddresses []string
|
||||
}{
|
||||
{
|
||||
name: "ControlPlaneEndpoint DNS",
|
||||
name: "",
|
||||
cfg: &kubeadmapi.InitConfiguration{
|
||||
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
|
||||
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
|
||||
|
|
|
@ -33,6 +33,9 @@ go_test(
|
|||
"skip",
|
||||
],
|
||||
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/sigs.k8s.io/yaml:go_default_library",
|
||||
],
|
||||
|
|
|
@ -20,6 +20,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"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) {
|
||||
|
@ -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) {
|
||||
if *kubeadmCmdSkip {
|
||||
t.Log("kubeadm cmd tests being skipped")
|
||||
|
|
|
@ -18,24 +18,30 @@ package kubeadm
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Forked from test/e2e/framework because the e2e framework is quite bloated
|
||||
// for our purposes here, and modified to remove undesired logging.
|
||||
|
||||
// RunCmd is a utility function for kubeadm testing that executes a specified command
|
||||
func RunCmd(command string, args ...string) (string, string, error) {
|
||||
func runCmdNoWrap(command string, args ...string) (string, string, error) {
|
||||
var bout, berr bytes.Buffer
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdout = &bout
|
||||
cmd.Stderr = &berr
|
||||
err := cmd.Run()
|
||||
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 {
|
||||
return "", "", errors.Wrapf(err, "error running %s %v; \nstdout %q, \nstderr %q, \ngot error",
|
||||
command, args, stdout, stderr)
|
||||
|
|
Loading…
Reference in New Issue