mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
kubeadm alpha certs generate-csr
* Creates private keys and CSR files for all the control-plane certificates * Helps with External CA mode of kubeadm Signed-off-by: Richard Wall <richard.wall@jetstack.io>
This commit is contained in:
parent
57712220a1
commit
81554ffdc0
@ -21,6 +21,7 @@ go_library(
|
|||||||
"//cmd/kubeadm/app/cmd/util:go_default_library",
|
"//cmd/kubeadm/app/cmd/util:go_default_library",
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
"//cmd/kubeadm/app/features:go_default_library",
|
"//cmd/kubeadm/app/features:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/certs/renewal:go_default_library",
|
"//cmd/kubeadm/app/phases/certs/renewal:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/copycerts:go_default_library",
|
"//cmd/kubeadm/app/phases/copycerts:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||||
@ -34,6 +35,7 @@ go_library(
|
|||||||
"//vendor/github.com/lithammer/dedent:go_default_library",
|
"//vendor/github.com/lithammer/dedent:go_default_library",
|
||||||
"//vendor/github.com/pkg/errors:go_default_library",
|
"//vendor/github.com/pkg/errors: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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,6 +61,8 @@ go_test(
|
|||||||
],
|
],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
|
||||||
"//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/phases/certs:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||||
@ -68,5 +72,8 @@ go_test(
|
|||||||
"//cmd/kubeadm/test/kubeconfig:go_default_library",
|
"//cmd/kubeadm/test/kubeconfig:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
|
"//staging/src/k8s.io/client-go/tools/clientcmd: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/github.com/stretchr/testify/assert:go_default_library",
|
||||||
|
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/lithammer/dedent"
|
"github.com/lithammer/dedent"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/duration"
|
"k8s.io/apimachinery/pkg/util/duration"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
@ -33,8 +34,10 @@ import (
|
|||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal"
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts"
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts"
|
||||||
|
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||||
)
|
)
|
||||||
@ -68,6 +71,22 @@ var (
|
|||||||
|
|
||||||
You can also use "kubeadm init --upload-certs" without specifying a certificate key and it will
|
You can also use "kubeadm init --upload-certs" without specifying a certificate key and it will
|
||||||
generate and print one for you.
|
generate and print one for you.
|
||||||
|
`)
|
||||||
|
generateCSRLongDesc = cmdutil.LongDesc(`
|
||||||
|
Generates keys and certificate signing requests (CSRs) for all the certificates required to run the control plane.
|
||||||
|
This command also generates partial kubeconfig files with private key data in the "users > user > client-key-data" field,
|
||||||
|
and for each kubeconfig file an accompanying ".csr" file is created.
|
||||||
|
|
||||||
|
This command is designed for use in [Kubeadm External CA Mode](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/#external-ca-mode).
|
||||||
|
It generates CSRs which you can then submit to your external certificate authority for signing.
|
||||||
|
|
||||||
|
The PEM encoded signed certificates should then be saved alongside the key files, using ".crt" as the file extension,
|
||||||
|
or in the case of kubeconfig files, the PEM encoded signed certificate should be base64 encoded
|
||||||
|
and added to the kubeconfig file in the "users > user > client-certificate-data" field.
|
||||||
|
`)
|
||||||
|
generateCSRExample = cmdutil.Examples(`
|
||||||
|
# The following command will generate keys and CSRs for all control-plane certificates and kubeconfig files:
|
||||||
|
kubeadm alpha certs generate-csr --kubeconfig-dir /tmp/etc-k8s --cert-dir /tmp/etc-k8s/pki
|
||||||
`)
|
`)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -82,9 +101,83 @@ func newCmdCertsUtility(out io.Writer) *cobra.Command {
|
|||||||
cmd.AddCommand(newCmdCertsRenewal(out))
|
cmd.AddCommand(newCmdCertsRenewal(out))
|
||||||
cmd.AddCommand(newCmdCertsExpiration(out, constants.KubernetesDir))
|
cmd.AddCommand(newCmdCertsExpiration(out, constants.KubernetesDir))
|
||||||
cmd.AddCommand(NewCmdCertificateKey())
|
cmd.AddCommand(NewCmdCertificateKey())
|
||||||
|
cmd.AddCommand(newCmdGenCSR())
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// genCSRConfig is the configuration required by the gencsr command
|
||||||
|
type genCSRConfig struct {
|
||||||
|
kubeadmConfigPath string
|
||||||
|
certDir string
|
||||||
|
kubeConfigDir string
|
||||||
|
kubeadmConfig *kubeadmapi.InitConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenCSRConfig() *genCSRConfig {
|
||||||
|
return &genCSRConfig{
|
||||||
|
kubeConfigDir: kubeadmconstants.KubernetesDir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *genCSRConfig) addFlagSet(flagSet *pflag.FlagSet) {
|
||||||
|
options.AddConfigFlag(flagSet, &o.kubeadmConfigPath)
|
||||||
|
options.AddCertificateDirFlag(flagSet, &o.certDir)
|
||||||
|
options.AddKubeConfigDirFlag(flagSet, &o.kubeConfigDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load merges command line flag values into kubeadm's config.
|
||||||
|
// Reads Kubeadm config from a file (if present)
|
||||||
|
// else use dynamically generated default config.
|
||||||
|
// This configuration contains the DNS names and IP addresses which
|
||||||
|
// are encoded in the control-plane CSRs.
|
||||||
|
func (o *genCSRConfig) load() (err error) {
|
||||||
|
o.kubeadmConfig, err = configutil.LoadOrDefaultInitConfiguration(
|
||||||
|
o.kubeadmConfigPath,
|
||||||
|
&kubeadmapiv1beta2.InitConfiguration{},
|
||||||
|
&kubeadmapiv1beta2.ClusterConfiguration{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// --cert-dir takes priority over kubeadm config if set.
|
||||||
|
if o.certDir != "" {
|
||||||
|
o.kubeadmConfig.CertificatesDir = o.certDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCmdGenCSR returns cobra.Command for generating keys and CSRs
|
||||||
|
func newCmdGenCSR() *cobra.Command {
|
||||||
|
config := newGenCSRConfig()
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "generate-csr",
|
||||||
|
Short: "Generate keys and certificate signing requests",
|
||||||
|
Long: generateCSRLongDesc,
|
||||||
|
Example: generateCSRExample,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := config.load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runGenCSR(config)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.addFlagSet(cmd.Flags())
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// runGenCSR contains the logic of the generate-csr sub-command.
|
||||||
|
func runGenCSR(config *genCSRConfig) error {
|
||||||
|
if err := certsphase.CreateDefaultKeysAndCSRFiles(config.kubeadmConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := kubeconfigphase.CreateDefaultKubeConfigsAndCSRFiles(config.kubeConfigDir, config.kubeadmConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewCmdCertificateKey returns cobra.Command for certificate key generate
|
// NewCmdCertificateKey returns cobra.Command for certificate key generate
|
||||||
func NewCmdCertificateKey() *cobra.Command {
|
func NewCmdCertificateKey() *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
|
@ -27,6 +27,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
|
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||||
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||||
@ -285,3 +292,185 @@ func TestRenewUsingCSR(t *testing.T) {
|
|||||||
t.Fatalf("couldn't load certificate %q: %v", cert.Name, err)
|
t.Fatalf("couldn't load certificate %q: %v", cert.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunGenCSR(t *testing.T) {
|
||||||
|
tmpDir := testutil.SetupTempDir(t)
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
kubeConfigDir := filepath.Join(tmpDir, "kubernetes")
|
||||||
|
certDir := kubeConfigDir + "/pki"
|
||||||
|
|
||||||
|
expectedCertificates := []string{
|
||||||
|
"apiserver",
|
||||||
|
"apiserver-etcd-client",
|
||||||
|
"apiserver-kubelet-client",
|
||||||
|
"front-proxy-client",
|
||||||
|
"etcd/healthcheck-client",
|
||||||
|
"etcd/peer",
|
||||||
|
"etcd/server",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedKubeConfigs := []string{
|
||||||
|
"admin",
|
||||||
|
"kubelet",
|
||||||
|
"controller-manager",
|
||||||
|
"scheduler",
|
||||||
|
}
|
||||||
|
|
||||||
|
config := genCSRConfig{
|
||||||
|
kubeConfigDir: kubeConfigDir,
|
||||||
|
kubeadmConfig: &kubeadmapi.InitConfiguration{
|
||||||
|
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
|
||||||
|
AdvertiseAddress: "192.0.2.1",
|
||||||
|
BindPort: 443,
|
||||||
|
},
|
||||||
|
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
|
||||||
|
Networking: kubeadmapi.Networking{
|
||||||
|
ServiceSubnet: "192.0.2.0/24",
|
||||||
|
},
|
||||||
|
CertificatesDir: certDir,
|
||||||
|
KubernetesVersion: "v1.19.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := runGenCSR(&config)
|
||||||
|
require.NoError(t, err, "expected runGenCSR to not fail")
|
||||||
|
|
||||||
|
t.Log("The command generates key and CSR files in the configured --cert-dir")
|
||||||
|
for _, name := range expectedCertificates {
|
||||||
|
_, err = pkiutil.TryLoadKeyFromDisk(certDir, name)
|
||||||
|
assert.NoErrorf(t, err, "failed to load key file: %s", name)
|
||||||
|
|
||||||
|
_, err = pkiutil.TryLoadCSRFromDisk(certDir, name)
|
||||||
|
assert.NoError(t, err, "failed to load CSR file: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("The command generates kubeconfig files in the configured --kubeconfig-dir")
|
||||||
|
for _, name := range expectedKubeConfigs {
|
||||||
|
_, err = clientcmd.LoadFromFile(kubeConfigDir + "/" + name + ".conf")
|
||||||
|
assert.NoErrorf(t, err, "failed to load kubeconfig file: %s", name)
|
||||||
|
|
||||||
|
_, err = pkiutil.TryLoadCSRFromDisk(kubeConfigDir, name+".conf")
|
||||||
|
assert.NoError(t, err, "failed to load kubeconfig CSR file: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenCSRConfig(t *testing.T) {
|
||||||
|
type assertion func(*testing.T, *genCSRConfig)
|
||||||
|
|
||||||
|
hasCertDir := func(expected string) assertion {
|
||||||
|
return func(t *testing.T, config *genCSRConfig) {
|
||||||
|
assert.Equal(t, expected, config.kubeadmConfig.CertificatesDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasKubeConfigDir := func(expected string) assertion {
|
||||||
|
return func(t *testing.T, config *genCSRConfig) {
|
||||||
|
assert.Equal(t, expected, config.kubeConfigDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasAdvertiseAddress := func(expected string) assertion {
|
||||||
|
return func(t *testing.T, config *genCSRConfig) {
|
||||||
|
assert.Equal(t, expected, config.kubeadmConfig.LocalAPIEndpoint.AdvertiseAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A minimal kubeadm config with just enough values to avoid triggering
|
||||||
|
// auto-detection of config values at runtime.
|
||||||
|
const kubeadmConfig = `
|
||||||
|
apiVersion: kubeadm.k8s.io/v1beta2
|
||||||
|
kind: InitConfiguration
|
||||||
|
localAPIEndpoint:
|
||||||
|
advertiseAddress: 192.0.2.1
|
||||||
|
nodeRegistration:
|
||||||
|
criSocket: /path/to/dockershim.sock
|
||||||
|
---
|
||||||
|
apiVersion: kubeadm.k8s.io/v1beta2
|
||||||
|
kind: ClusterConfiguration
|
||||||
|
certificatesDir: /custom/config/certificates-dir
|
||||||
|
kubernetesVersion: v1.19.0
|
||||||
|
`
|
||||||
|
|
||||||
|
tmpDir := testutil.SetupTempDir(t)
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
customConfigPath := tmpDir + "/kubeadm.conf"
|
||||||
|
|
||||||
|
f, err := os.Create(customConfigPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = f.Write([]byte(kubeadmConfig))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
flags []string
|
||||||
|
assertions []assertion
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
assertions: []assertion{
|
||||||
|
hasCertDir(kubeadmapiv1beta2.DefaultCertificatesDir),
|
||||||
|
hasKubeConfigDir(kubeadmconstants.KubernetesDir),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "--cert-dir overrides default",
|
||||||
|
flags: []string{"--cert-dir", "/foo/bar/pki"},
|
||||||
|
assertions: []assertion{
|
||||||
|
hasCertDir("/foo/bar/pki"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "--config is loaded",
|
||||||
|
flags: []string{"--config", customConfigPath},
|
||||||
|
assertions: []assertion{
|
||||||
|
hasCertDir("/custom/config/certificates-dir"),
|
||||||
|
hasAdvertiseAddress("192.0.2.1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "--config not found",
|
||||||
|
flags: []string{"--config", "/does/not/exist"},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "--cert-dir overrides --config certificatesDir",
|
||||||
|
flags: []string{
|
||||||
|
"--config", customConfigPath,
|
||||||
|
"--cert-dir", "/foo/bar/pki",
|
||||||
|
},
|
||||||
|
assertions: []assertion{
|
||||||
|
hasCertDir("/foo/bar/pki"),
|
||||||
|
hasAdvertiseAddress("192.0.2.1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "--kubeconfig-dir overrides default",
|
||||||
|
flags: []string{
|
||||||
|
"--kubeconfig-dir", "/foo/bar/kubernetes",
|
||||||
|
},
|
||||||
|
assertions: []assertion{
|
||||||
|
hasKubeConfigDir("/foo/bar/kubernetes"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
flagset := pflag.NewFlagSet("flags-for-gencsr", pflag.ContinueOnError)
|
||||||
|
config := newGenCSRConfig()
|
||||||
|
config.addFlagSet(flagset)
|
||||||
|
require.NoError(t, flagset.Parse(test.flags))
|
||||||
|
|
||||||
|
err := config.load()
|
||||||
|
if test.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
for _, assertFunc := range test.assertions {
|
||||||
|
assertFunc(t, config)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user