mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Merge pull request #48196 from fabriziopandini/kubeadm-phase-certs2
Automatic merge from submit-queue kubeadm: Implementing the certificates phase fully **What this PR does / why we need it:** This contains implementation of certs phases in kubeadm, which is part of the wider effort of implementing phases in kubeadm, previously in alpha stage. The original proposal for this activity can be found [here](https://github.com/kubernetes/kubeadm/pull/156/files) and related comments. Kubeadm phase implementation checklist is defined [here](https://github.com/kubernetes/kubeadm/issues/267) Common implementation guidelines and principles for all phases are defined [here](https://docs.google.com/document/d/1VQMyFIVMfRGQPP3oCUpfjiWtOr3pLxp4g7cP-hXQFXc/edit?usp=sharing) This PR implements: - [x] kubeadm phase certs - [x] kubeadm phase certs all - [x] kubeadm phase certs ca - [x] kubeadm phase certs apiserver - [x] kubeadm phase certs apiserver-kubelet-client - [x] kubeadm phase certs sa - [x] kubeadm phase certs front-proxy-ca - [x] kubeadm phase certs front-proxy-client **Which issue this PR fixes:** none **Special notes for your reviewer:** This PR resubmits the work of #45617, and already includes @luxas reviews. Please note that: - the API - phase\certs.go - is now totally free by any UX concerns, and implements only the core logic for cert generation. - the UX - cmd\phase\certs.go - now takes charge of UX commands and kubeadm own's rules for placing certs in the certificate dir (e.g. create only if exists)
This commit is contained in:
commit
e11020fa39
@ -13,7 +13,6 @@ go_library(
|
||||
srcs = [
|
||||
"cmd.go",
|
||||
"completion.go",
|
||||
"defaults.go",
|
||||
"init.go",
|
||||
"join.go",
|
||||
"reset.go",
|
||||
@ -31,13 +30,13 @@ go_library(
|
||||
"//cmd/kubeadm/app/node:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/addons:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/apiconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/token:go_default_library",
|
||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/config:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
@ -55,7 +54,6 @@ go_library(
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
@ -66,7 +64,6 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"defaults_test.go",
|
||||
"reset_test.go",
|
||||
"token_test.go",
|
||||
],
|
||||
|
@ -31,16 +31,17 @@ import (
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||
cmdphases "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons"
|
||||
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
|
||||
certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
|
||||
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
|
||||
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
@ -164,11 +165,20 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight,
|
||||
}
|
||||
|
||||
// Set defaults dynamically that the API group defaulting can't (by fetching information from the internet, looking up network interfaces, etc.)
|
||||
err := setInitDynamicDefaults(cfg)
|
||||
err := configutil.SetInitDynamicDefaults(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion)
|
||||
fmt.Printf("[init] Using Authorization mode: %v\n", cfg.AuthorizationModes)
|
||||
|
||||
// Warn about the limitations with the current cloudprovider solution.
|
||||
if cfg.CloudProvider != "" {
|
||||
fmt.Println("[init] WARNING: For cloudprovider integrations to work --cloud-provider must be set for all kubelets in the cluster.")
|
||||
fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)")
|
||||
}
|
||||
|
||||
if !skipPreFlight {
|
||||
fmt.Println("[preflight] Running pre-flight checks")
|
||||
|
||||
@ -202,7 +212,7 @@ func (i *Init) Validate(cmd *cobra.Command) error {
|
||||
func (i *Init) Run(out io.Writer) error {
|
||||
|
||||
// PHASE 1: Generate certificates
|
||||
err := certphase.CreatePKIAssets(i.cfg)
|
||||
err := cmdphases.CreatePKIAssets(i.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ licenses(["notice"])
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
@ -21,16 +22,31 @@ go_library(
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
|
||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/config:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["certs_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm/install:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//vendor/github.com/renstrom/dedent:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -17,18 +17,20 @@ limitations under the License.
|
||||
package phases
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
netutil "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
@ -40,63 +42,342 @@ func NewCmdCerts() *cobra.Command {
|
||||
RunE: subCmdRunE("certs"),
|
||||
}
|
||||
|
||||
cmd.AddCommand(NewCmdSelfSign())
|
||||
cmd.AddCommand(newSubCmdCerts()...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewCmdSelfSign() *cobra.Command {
|
||||
// TODO: Move this into a dedicated Certificates Phase API object
|
||||
// newSubCmdCerts returns sub commands for certs phase
|
||||
func newSubCmdCerts() []*cobra.Command {
|
||||
|
||||
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||
// Default values for the cobra help text
|
||||
api.Scheme.Default(cfg)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "selfsign",
|
||||
Short: "Generate the CA, APIServer signing/client cert, the ServiceAccount public/private keys and a CA and client cert for the front proxy",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var cfgPath string
|
||||
var subCmds []*cobra.Command
|
||||
|
||||
// Run the defaulting once again to take passed flags into account
|
||||
api.Scheme.Default(cfg)
|
||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||
api.Scheme.Convert(cfg, internalcfg, nil)
|
||||
|
||||
err := RunSelfSign(internalcfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
subCmdProperties := []struct {
|
||||
use string
|
||||
short string
|
||||
cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error
|
||||
}{
|
||||
{
|
||||
use: "all",
|
||||
short: "Generate all PKI assets necessary to establish the control plane",
|
||||
cmdFunc: CreatePKIAssets,
|
||||
},
|
||||
{
|
||||
use: "ca",
|
||||
short: "Generate CA certificate and key for a Kubernetes cluster.",
|
||||
cmdFunc: createOrUseCACertAndKey,
|
||||
},
|
||||
{
|
||||
use: "apiserver",
|
||||
short: "Generate API Server serving certificate and key.",
|
||||
cmdFunc: createOrUseAPIServerCertAndKey,
|
||||
},
|
||||
{
|
||||
use: "apiserver-kubelet-client",
|
||||
short: "Generate a client certificate for the API Server to connect to the kubelets securely.",
|
||||
cmdFunc: createOrUseAPIServerKubeletClientCertAndKey,
|
||||
},
|
||||
{
|
||||
use: "sa",
|
||||
short: "Generate a private key for signing service account tokens along with its public key.",
|
||||
cmdFunc: createOrUseServiceAccountKeyAndPublicKey,
|
||||
},
|
||||
{
|
||||
use: "front-proxy-ca",
|
||||
short: "Generate front proxy CA certificate and key for a Kubernetes cluster.",
|
||||
cmdFunc: createOrUseFrontProxyCACertAndKey,
|
||||
},
|
||||
{
|
||||
use: "front-proxy-client",
|
||||
short: "Generate front proxy CA client certificate and key for a Kubernetes cluster.",
|
||||
cmdFunc: createOrUseFrontProxyClientCertAndKey,
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "dns-domain", cfg.Networking.DNSDomain, "The DNS Domain for the Kubernetes cluster.")
|
||||
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save and store the certificates.")
|
||||
cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "The subnet for the Services in the cluster.")
|
||||
cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "cert-altnames", []string{}, "Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.")
|
||||
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.")
|
||||
|
||||
return cmd
|
||||
for _, properties := range subCmdProperties {
|
||||
// Creates the UX Command
|
||||
cmd := &cobra.Command{
|
||||
Use: properties.use,
|
||||
Short: properties.short,
|
||||
Run: runCmdFunc(properties.cmdFunc, &cfgPath, cfg),
|
||||
}
|
||||
|
||||
// Add flags to the command
|
||||
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
|
||||
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save and store the certificates")
|
||||
if properties.use == "all" || properties.use == "apiserver" {
|
||||
cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Use alternative domain for services, e.g. \"myorg.internal\"")
|
||||
cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Use alternative range of IP address for service VIPs")
|
||||
cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.")
|
||||
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.")
|
||||
}
|
||||
|
||||
subCmds = append(subCmds, cmd)
|
||||
}
|
||||
|
||||
return subCmds
|
||||
}
|
||||
|
||||
// RunSelfSign generates certificate assets in the specified directory
|
||||
func RunSelfSign(config *kubeadmapi.MasterConfiguration) error {
|
||||
if err := validateArgs(config); err != nil {
|
||||
return fmt.Errorf("The argument validation failed: %v", err)
|
||||
// runCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of inpunt parameters)
|
||||
func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// the following statement build a clousure that wraps a call to a CreateCertFunc, binding
|
||||
// the function itself with the specific parameters of each sub command.
|
||||
// Please note that specific parameter should be passed as value, while other parameters - passed as reference -
|
||||
// are shared between sub commnands and gets access to current value e.g. flags value.
|
||||
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||
|
||||
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
|
||||
// static default values to cfg only for values not provided with flags
|
||||
api.Scheme.Default(cfg)
|
||||
api.Scheme.Convert(cfg, internalcfg, nil)
|
||||
|
||||
// Loads configuration from config file, if provided
|
||||
// Nb. --config overrides command line flags
|
||||
err := configutil.TryLoadMasterConfiguration(*cfgPath, internalcfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
// Applies dynamic defaults to settings not provided with flags
|
||||
err = configutil.SetInitDynamicDefaults(internalcfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
// Validates cfg (flags/configs + defaults + dynamic defaults)
|
||||
err = validation.ValidateMasterConfiguration(internalcfg).ToAggregate()
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
// Execute the cmdFunc
|
||||
err = cmdFunc(internalcfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.
|
||||
// Please note that this action is a bulk action calling all the atomic certphase actions
|
||||
func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
certActions := []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
createOrUseCACertAndKey,
|
||||
createOrUseAPIServerCertAndKey,
|
||||
createOrUseAPIServerKubeletClientCertAndKey,
|
||||
createOrUseServiceAccountKeyAndPublicKey,
|
||||
createOrUseFrontProxyCACertAndKey,
|
||||
createOrUseFrontProxyClientCertAndKey,
|
||||
}
|
||||
|
||||
// If it's possible to detect the default IP, add it to the SANs as well. Otherwise, just go with the provided ones
|
||||
ip, err := netutil.ChooseBindAddress(net.ParseIP(config.API.AdvertiseAddress))
|
||||
if err == nil {
|
||||
config.API.AdvertiseAddress = ip.String()
|
||||
for _, action := range certActions {
|
||||
err := action(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = certphase.CreatePKIAssets(config); err != nil {
|
||||
return err
|
||||
fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", cfg.CertificatesDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createOrUseCACertAndKey create a new self signed CA, or use the existing one.
|
||||
func createOrUseCACertAndKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return createOrUseCertificateAuthorithy(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.CACertAndKeyBaseName,
|
||||
"CA",
|
||||
certphase.NewCACertAndKey,
|
||||
)
|
||||
}
|
||||
|
||||
// createOrUseAPIServerCertAndKey create a new CA certificate for apiserver, or use the existing one.
|
||||
// It assumes the CA certificates should exists into the CertificatesDir
|
||||
func createOrUseAPIServerCertAndKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return createOrUseSignedCertificate(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.CACertAndKeyBaseName,
|
||||
kubeadmconstants.APIServerCertAndKeyBaseName,
|
||||
"API server",
|
||||
func(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
return certphase.NewAPIServerCertAndKey(cfg, caCert, caKey)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// create a new CA certificate for kubelets calling apiserver, or use the existing one
|
||||
// It assumes the CA certificates should exists into the CertificatesDir
|
||||
func createOrUseAPIServerKubeletClientCertAndKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return createOrUseSignedCertificate(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.CACertAndKeyBaseName,
|
||||
kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
|
||||
"API server kubelet client",
|
||||
certphase.NewAPIServerKubeletClientCertAndKey,
|
||||
)
|
||||
}
|
||||
|
||||
// createOrUseServiceAccountKeyAndPublicKey create a new public/private key pairs for signing service account user, or use the existing one.
|
||||
func createOrUseServiceAccountKeyAndPublicKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return createOrUseKeyAndPublicKey(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.ServiceAccountKeyBaseName,
|
||||
"service account",
|
||||
certphase.NewServiceAccountSigningKey,
|
||||
)
|
||||
}
|
||||
|
||||
// createOrUseFrontProxyCACertAndKey create a new self signed front proxy CA, or use the existing one.
|
||||
func createOrUseFrontProxyCACertAndKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return createOrUseCertificateAuthorithy(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.FrontProxyCACertAndKeyBaseName,
|
||||
"front-proxy CA",
|
||||
certphase.NewFrontProxyCACertAndKey,
|
||||
)
|
||||
}
|
||||
|
||||
// createOrUseFrontProxyClientCertAndKey create a new certificate for proxy server client, or use the existing one.
|
||||
// It assumes the front proxy CA certificates should exists into the CertificatesDir
|
||||
func createOrUseFrontProxyClientCertAndKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return createOrUseSignedCertificate(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.FrontProxyCACertAndKeyBaseName,
|
||||
kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
|
||||
"front-proxy client",
|
||||
certphase.NewFrontProxyClientCertAndKey,
|
||||
)
|
||||
}
|
||||
|
||||
// createOrUseCertificateAuthorithy is a generic function that will create a new certificate Authorithy using the given newFunc,
|
||||
// assign file names according to the given baseName, or use the existing one already present in pkiDir.
|
||||
func createOrUseCertificateAuthorithy(pkiDir string, baseName string, UXName string, newFunc func() (*x509.Certificate, *rsa.PrivateKey, error)) error {
|
||||
|
||||
// If cert or key exists, we should try to load them
|
||||
if pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
|
||||
// Try to load .crt and .key from the PKI directory
|
||||
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading %s certificate: %v", UXName, err)
|
||||
}
|
||||
|
||||
// Check if the existing cert is a CA
|
||||
if !caCert.IsCA {
|
||||
return fmt.Errorf("certificate %s is not a CA", UXName)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Using the existing %s certificate and key.\n", UXName)
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
caCert, caKey, err := newFunc()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while generating %s certificate and key: %v", UXName, err)
|
||||
}
|
||||
|
||||
// Write .crt and .key files to disk
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, baseName, caCert, caKey); err != nil {
|
||||
return fmt.Errorf("failure while saving %s certificate and key: %v", UXName, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Generated %s certificate and key.\n", UXName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArgs(config *kubeadmapi.MasterConfiguration) error {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, validation.ValidateNetworking(&config.Networking, field.NewPath("networking"))...)
|
||||
allErrs = append(allErrs, validation.ValidateAbsolutePath(config.CertificatesDir, field.NewPath("cert-dir"))...)
|
||||
allErrs = append(allErrs, validation.ValidateAPIServerCertSANs(config.APIServerCertSANs, field.NewPath("cert-altnames"))...)
|
||||
allErrs = append(allErrs, validation.ValidateIPFromString(config.API.AdvertiseAddress, field.NewPath("apiserver-advertise-address"))...)
|
||||
// createOrUseSignedCertificate is a generic function that will create a new signed certificate using the given newFunc,
|
||||
// assign file names according to the given baseName, or use the existing one already present in pkiDir.
|
||||
func createOrUseSignedCertificate(pkiDir string, CABaseName string, baseName string, UXName string, newFunc func(*x509.Certificate, *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error)) error {
|
||||
|
||||
return allErrs.ToAggregate()
|
||||
// Checks if certificate authorithy exists in the PKI directory
|
||||
if !pkiutil.CertOrKeyExist(pkiDir, CABaseName) {
|
||||
return fmt.Errorf("couldn't load certificate authorithy for %s from certificate dir", UXName)
|
||||
}
|
||||
|
||||
// Try to load certificate authorithy .crt and .key from the PKI directory
|
||||
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, CABaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading certificate authorithy for %s: %v", UXName, err)
|
||||
}
|
||||
|
||||
// Make sure the loaded CA cert actually is a CA
|
||||
if !caCert.IsCA {
|
||||
return fmt.Errorf("certificate authorithy for %s is not a CA", UXName)
|
||||
}
|
||||
|
||||
// Checks if the signed certificate exists in the PKI directory
|
||||
if pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
// Try to load signed certificate .crt and .key from the PKI directory
|
||||
signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading %s certificate: %v", UXName, err)
|
||||
}
|
||||
|
||||
// Check if the existing cert is signed by the given CA
|
||||
if err := signedCert.CheckSignatureFrom(caCert); err != nil {
|
||||
return fmt.Errorf("certificate %s is not signed by corresponding CA", UXName)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Using the existing %s certificate and key.\n", UXName)
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
signedCert, signedKey, err := newFunc(caCert, caKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while generating %s key and certificate: %v", UXName, err)
|
||||
}
|
||||
|
||||
// Write .crt and .key files to disk
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, baseName, signedCert, signedKey); err != nil {
|
||||
return fmt.Errorf("failure while saving %s certificate and key: %v", UXName, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Generated %s certificate and key.\n", UXName)
|
||||
if pkiutil.HasServerAuth(signedCert) {
|
||||
fmt.Printf("[certificates] %s serving cert is signed for DNS names %v and IPs %v\n", UXName, signedCert.DNSNames, signedCert.IPAddresses)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createOrUseKeyAndPublicKey is a generic function that will create a new public/private key pairs using the given newFunc,
|
||||
// assign file names according to the given baseName, or use the existing one already present in pkiDir.
|
||||
func createOrUseKeyAndPublicKey(pkiDir string, baseName string, UXName string, newFunc func() (*rsa.PrivateKey, error)) error {
|
||||
|
||||
// Checks if the key exists in the PKI directory
|
||||
if pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
|
||||
// Try to load .key from the PKI directory
|
||||
_, err := pkiutil.TryLoadKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s key existed but they could not be loaded properly: %v", UXName, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Using the existing %s key.\n", UXName)
|
||||
} else {
|
||||
// The key does NOT exist, let's generate it now
|
||||
key, err := newFunc()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while generating %s key: %v", UXName, err)
|
||||
}
|
||||
|
||||
// Write .key and .pub files to disk
|
||||
if err = pkiutil.WriteKey(pkiDir, baseName, key); err != nil {
|
||||
return fmt.Errorf("failure while saving %s key: %v", UXName, err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WritePublicKey(pkiDir, baseName, &key.PublicKey); err != nil {
|
||||
return fmt.Errorf("failure while saving %s public key: %v", UXName, err)
|
||||
}
|
||||
fmt.Printf("[certificates] Generated %s key and public key.\n", UXName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
270
cmd/kubeadm/app/cmd/phases/certs_test.go
Normal file
270
cmd/kubeadm/app/cmd/phases/certs_test.go
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
Copyright 2017 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 (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
// required for triggering api machinery startup when running unit tests
|
||||
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
|
||||
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
)
|
||||
|
||||
func TestSubCmdCertsCreateFiles(t *testing.T) {
|
||||
|
||||
subCmds := newSubCmdCerts()
|
||||
|
||||
var tests = []struct {
|
||||
subCmds []string
|
||||
expectedFiles []string
|
||||
}{
|
||||
{
|
||||
subCmds: []string{"all"},
|
||||
expectedFiles: []string{
|
||||
kubeadmconstants.CACertName, kubeadmconstants.CAKeyName,
|
||||
kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName,
|
||||
kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName,
|
||||
kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName,
|
||||
kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName,
|
||||
kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName,
|
||||
},
|
||||
},
|
||||
{
|
||||
subCmds: []string{"ca"},
|
||||
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName},
|
||||
},
|
||||
{
|
||||
subCmds: []string{"ca", "apiserver"},
|
||||
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName},
|
||||
},
|
||||
{
|
||||
subCmds: []string{"ca", "apiserver-kubelet-client"},
|
||||
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName},
|
||||
},
|
||||
{
|
||||
subCmds: []string{"sa"},
|
||||
expectedFiles: []string{kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName},
|
||||
},
|
||||
{
|
||||
subCmds: []string{"front-proxy-ca"},
|
||||
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName},
|
||||
},
|
||||
{
|
||||
subCmds: []string{"front-proxy-ca", "front-proxy-client"},
|
||||
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName, kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Temporary folder for the test case
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// executes given sub commands
|
||||
for _, subCmdName := range test.subCmds {
|
||||
subCmd := getSubCmd(t, subCmdName, subCmds)
|
||||
subCmd.SetArgs([]string{fmt.Sprintf("--cert-dir=%s", tmpdir)})
|
||||
if err := subCmd.Execute(); err != nil {
|
||||
t.Fatalf("Could not execute subcommand: %s", subCmdName)
|
||||
}
|
||||
}
|
||||
|
||||
// verify expected files are there
|
||||
assertFilesCount(t, tmpdir, len(test.expectedFiles))
|
||||
for _, file := range test.expectedFiles {
|
||||
assertFileExists(t, tmpdir, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubCmdApiServerFlags(t *testing.T) {
|
||||
|
||||
subCmds := newSubCmdCerts()
|
||||
|
||||
// Temporary folder for the test case
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// creates ca cert
|
||||
subCmd := getSubCmd(t, "ca", subCmds)
|
||||
subCmd.SetArgs([]string{fmt.Sprintf("--cert-dir=%s", tmpdir)})
|
||||
if err := subCmd.Execute(); err != nil {
|
||||
t.Fatalf("Could not execute subcommand ca")
|
||||
}
|
||||
|
||||
// creates apiserver cert
|
||||
subCmd = getSubCmd(t, "apiserver", subCmds)
|
||||
subCmd.SetArgs([]string{
|
||||
fmt.Sprintf("--cert-dir=%s", tmpdir),
|
||||
"--apiserver-cert-extra-sans=foo,boo",
|
||||
"--service-cidr=10.0.0.0/24",
|
||||
"--service-dns-domain=mycluster.local",
|
||||
"--apiserver-advertise-address=1.2.3.4",
|
||||
})
|
||||
if err := subCmd.Execute(); err != nil {
|
||||
t.Fatalf("Could not execute subcommand apiserver")
|
||||
}
|
||||
|
||||
APIserverCert, err := pkiutil.TryLoadCertFromDisk(tmpdir, kubeadmconstants.APIServerCertAndKeyBaseName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error loading API server certificate: %v", err)
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't get the hostname: %v", err)
|
||||
}
|
||||
for i, name := range []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.mycluster.local"} {
|
||||
if APIserverCert.DNSNames[i] != name {
|
||||
t.Errorf("APIserverCert.DNSNames[%d] is %s instead of %s", i, APIserverCert.DNSNames[i], name)
|
||||
}
|
||||
}
|
||||
for i, ip := range []string{"10.0.0.1", "1.2.3.4"} {
|
||||
if APIserverCert.IPAddresses[i].String() != ip {
|
||||
t.Errorf("APIserverCert.IPAddresses[%d] is %s instead of %s", i, APIserverCert.IPAddresses[i], ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubCmdReadsConfig(t *testing.T) {
|
||||
|
||||
subCmds := newSubCmdCerts()
|
||||
|
||||
var tests = []struct {
|
||||
subCmds []string
|
||||
expectedFileCount int
|
||||
}{
|
||||
{
|
||||
subCmds: []string{"sa"},
|
||||
expectedFileCount: 2,
|
||||
},
|
||||
{
|
||||
subCmds: []string{"front-proxy-ca", "front-proxy-client"},
|
||||
expectedFileCount: 4,
|
||||
},
|
||||
{
|
||||
subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"},
|
||||
expectedFileCount: 6,
|
||||
},
|
||||
{
|
||||
subCmds: []string{"all"},
|
||||
expectedFileCount: 12,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Temporary folder for the test case
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
configPath := saveDummyCfg(t, tmpdir)
|
||||
|
||||
// executes given sub commands
|
||||
for _, subCmdName := range test.subCmds {
|
||||
subCmd := getSubCmd(t, subCmdName, subCmds)
|
||||
subCmd.SetArgs([]string{fmt.Sprintf("--config=%s", configPath)})
|
||||
if err := subCmd.Execute(); err != nil {
|
||||
t.Fatalf("Could not execute command: %s", subCmdName)
|
||||
}
|
||||
}
|
||||
|
||||
// verify expected files are there
|
||||
// NB. test.expectedFileCount + 1 because in this test case the tempdir where key/certificates
|
||||
// are saved contains also the dummy configuration file
|
||||
assertFilesCount(t, tmpdir, test.expectedFileCount+1)
|
||||
}
|
||||
}
|
||||
|
||||
func getSubCmd(t *testing.T, name string, subCmds []*cobra.Command) *cobra.Command {
|
||||
for _, subCmd := range subCmds {
|
||||
if subCmd.Name() == name {
|
||||
return subCmd
|
||||
}
|
||||
}
|
||||
t.Fatalf("Unable to find sub command %s", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertFilesCount(t *testing.T, dirName string, count int) {
|
||||
files, err := ioutil.ReadDir(dirName)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read files from tmpdir: %s", err)
|
||||
}
|
||||
|
||||
if len(files) != count {
|
||||
t.Errorf("dir does contains %d, %d expected", len(files), count)
|
||||
for _, f := range files {
|
||||
t.Error(f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertFileExists(t *testing.T, dirName string, fileName string) {
|
||||
path := path.Join(dirName, fileName)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
t.Errorf("file %s does not exist", fileName)
|
||||
}
|
||||
}
|
||||
|
||||
func saveDummyCfg(t *testing.T, dirName string) string {
|
||||
|
||||
path := path.Join(dirName, "dummyconfig.yaml")
|
||||
cfgTemplate := template.Must(template.New("init").Parse(dedent.Dedent(`
|
||||
apiVersion: kubeadm.k8s.io/v1alpha1
|
||||
kind: MasterConfiguration
|
||||
certificatesDir: {{.CertificatesDir}}
|
||||
`)))
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Errorf("error creating dummyconfig file %s: %v", path, err)
|
||||
}
|
||||
|
||||
templateData := struct {
|
||||
CertificatesDir string
|
||||
}{
|
||||
CertificatesDir: dirName,
|
||||
}
|
||||
|
||||
err = cfgTemplate.Execute(f, templateData)
|
||||
if err != nil {
|
||||
t.Errorf("error generating dummyconfig file %s: %v", path, err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
return path
|
||||
}
|
@ -37,10 +37,12 @@ const (
|
||||
APIServerCertAndKeyBaseName = "apiserver"
|
||||
APIServerCertName = "apiserver.crt"
|
||||
APIServerKeyName = "apiserver.key"
|
||||
APIServerCertCommonName = "kube-apiserver" //used as subject.commonname attribute (CN)
|
||||
|
||||
APIServerKubeletClientCertAndKeyBaseName = "apiserver-kubelet-client"
|
||||
APIServerKubeletClientCertName = "apiserver-kubelet-client.crt"
|
||||
APIServerKubeletClientKeyName = "apiserver-kubelet-client.key"
|
||||
APIServerKubeletClientCertCommonName = "kube-apiserver-kubelet-client" //used as subject.commonname attribute (CN)
|
||||
|
||||
ServiceAccountKeyBaseName = "sa"
|
||||
ServiceAccountPublicKeyName = "sa.pub"
|
||||
@ -53,6 +55,7 @@ const (
|
||||
FrontProxyClientCertAndKeyBaseName = "front-proxy-client"
|
||||
FrontProxyClientCertName = "front-proxy-client.crt"
|
||||
FrontProxyClientKeyName = "front-proxy-client.key"
|
||||
FrontProxyClientCertCommonName = "front-proxy-client" //used as subject.commonname attribute (CN)
|
||||
|
||||
AdminKubeConfigFileName = "admin.conf"
|
||||
KubeletKubeConfigFileName = "kubelet.conf"
|
||||
|
@ -15,7 +15,7 @@ go_test(
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -31,7 +31,6 @@ go_library(
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//pkg/registry/core/service/ipallocator:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||
],
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
setutil "k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
@ -32,257 +31,139 @@ import (
|
||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||
)
|
||||
|
||||
// TODO: Integration test cases
|
||||
// no files exist => create all four files
|
||||
// valid ca.{crt,key} exists => create apiserver.{crt,key}
|
||||
// valid ca.{crt,key} and apiserver.{crt,key} exists => do nothing
|
||||
// invalid ca.{crt,key} exists => error
|
||||
// only one of the .crt or .key file exists => error
|
||||
// NewCACertAndKey will generate a self signed CA.
|
||||
func NewCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.
|
||||
// It generates a self-signed CA certificate and a server certificate (signed by the CA)
|
||||
func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
pkiDir := cfg.CertificatesDir
|
||||
caCert, caKey, err := pkiutil.NewCertificateAuthority()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err)
|
||||
}
|
||||
|
||||
return caCert, caKey, nil
|
||||
}
|
||||
|
||||
// NewAPIServerCertAndKey generate CA certificate for apiserver, signed by the given CA.
|
||||
func NewAPIServerCertAndKey(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
altNames, err := getAltNames(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while composing altnames for API server: %v", err)
|
||||
}
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: kubeadmconstants.APIServerCertCommonName,
|
||||
AltNames: *altNames,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while creating API server key and certificate: %v", err)
|
||||
}
|
||||
|
||||
return apiCert, apiKey, nil
|
||||
}
|
||||
|
||||
// NewAPIServerKubeletClientCertAndKey generate CA certificate for the apiservers to connect to the kubelets securely, signed by the given CA.
|
||||
func NewAPIServerKubeletClientCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName,
|
||||
Organization: []string{kubeadmconstants.MastersGroup},
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while creating API server kubelet client key and certificate: %v", err)
|
||||
}
|
||||
|
||||
return apiClientCert, apiClientKey, nil
|
||||
}
|
||||
|
||||
// NewServiceAccountSigningKey generate public/private key pairs for signing service account tokens.
|
||||
func NewServiceAccountSigningKey() (*rsa.PrivateKey, error) {
|
||||
|
||||
// The key does NOT exist, let's generate it now
|
||||
saSigningKey, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure while creating service account token signing key: %v", err)
|
||||
}
|
||||
|
||||
return saSigningKey, nil
|
||||
}
|
||||
|
||||
// NewFrontProxyCACertAndKey generate a self signed front proxy CA.
|
||||
// Front proxy CA and client certs are used to secure a front proxy authenticator which is used to assert identity
|
||||
// without the client cert.
|
||||
// This is a separte CA, so that front proxy identities cannot hit the API and normal client certs cannot be used
|
||||
// as front proxies.
|
||||
func NewFrontProxyCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
frontProxyCACert, frontProxyCAKey, err := pkiutil.NewCertificateAuthority()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while generating front-proxy CA certificate and key: %v", err)
|
||||
}
|
||||
|
||||
return frontProxyCACert, frontProxyCAKey, nil
|
||||
}
|
||||
|
||||
// NewFrontProxyClientCertAndKey generate CA certificate for proxy server client, signed by the given front proxy CA.
|
||||
func NewFrontProxyClientCertAndKey(frontProxyCACert *x509.Certificate, frontProxyCAKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: kubeadmconstants.FrontProxyClientCertCommonName,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
frontProxyClientCert, frontProxyClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while creating front-proxy client key and certificate: %v", err)
|
||||
}
|
||||
|
||||
return frontProxyClientCert, frontProxyClientKey, nil
|
||||
}
|
||||
|
||||
// getAltNames builds an AltNames object for to be used when generating apiserver certificate
|
||||
func getAltNames(cfg *kubeadmapi.MasterConfiguration) (*certutil.AltNames, error) {
|
||||
|
||||
// host name
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get the hostname: %v", err)
|
||||
return nil, fmt.Errorf("couldn't get the hostname: %v", err)
|
||||
}
|
||||
|
||||
// advertise address
|
||||
advertiseAddress := net.ParseIP(cfg.API.AdvertiseAddress)
|
||||
if advertiseAddress == nil {
|
||||
return nil, fmt.Errorf("error parsing API AdvertiseAddress %v: is not a valid textual representation of an IP address", cfg.API.AdvertiseAddress)
|
||||
}
|
||||
|
||||
// internal IP address for the API server
|
||||
_, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
|
||||
return nil, fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
|
||||
}
|
||||
|
||||
// Build the list of SANs
|
||||
altNames := getAltNames(cfg.APIServerCertSANs, hostname, cfg.Networking.DNSDomain, svcSubnet)
|
||||
// Append the address the API Server is advertising
|
||||
altNames.IPs = append(altNames.IPs, net.ParseIP(cfg.API.AdvertiseAddress))
|
||||
|
||||
var caCert *x509.Certificate
|
||||
var caKey *rsa.PrivateKey
|
||||
// If at least one of them exists, we should try to load them
|
||||
// In the case that only one exists, there will most likely be an error anyway
|
||||
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.CACertAndKeyBaseName) {
|
||||
// Try to load ca.crt and ca.key from the PKI directory
|
||||
caCert, caKey, err = pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil || caCert == nil || caKey == nil {
|
||||
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly")
|
||||
}
|
||||
|
||||
// The certificate and key could be loaded, but the certificate is not a CA
|
||||
if !caCert.IsCA {
|
||||
return fmt.Errorf("certificate and key could be loaded but the certificate is not a CA")
|
||||
}
|
||||
|
||||
fmt.Println("[certificates] Using the existing CA certificate and key.")
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
caCert, caKey, err = pkiutil.NewCertificateAuthority()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while generating CA certificate and key [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.CACertAndKeyBaseName, caCert, caKey); err != nil {
|
||||
return fmt.Errorf("failure while saving CA certificate and key [%v]", err)
|
||||
}
|
||||
fmt.Println("[certificates] Generated CA certificate and key.")
|
||||
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get first IP address from the given CIDR (%s): %v", svcSubnet.String(), err)
|
||||
}
|
||||
|
||||
// If at least one of them exists, we should try to load them
|
||||
// In the case that only one exists, there will most likely be an error anyway
|
||||
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName) {
|
||||
// Try to load apiserver.crt and apiserver.key from the PKI directory
|
||||
apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName)
|
||||
if err != nil || apiCert == nil || apiKey == nil {
|
||||
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly")
|
||||
}
|
||||
|
||||
fmt.Println("[certificates] Using the existing API Server certificate and key.")
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
// TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageServerAuth flag
|
||||
config := certutil.Config{
|
||||
CommonName: "kube-apiserver",
|
||||
AltNames: altNames,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while creating API server key and certificate [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName, apiCert, apiKey); err != nil {
|
||||
return fmt.Errorf("failure while saving API server certificate and key [%v]", err)
|
||||
}
|
||||
fmt.Println("[certificates] Generated API server certificate and key.")
|
||||
fmt.Printf("[certificates] API Server serving cert is signed for DNS names %v and IPs %v\n", altNames.DNSNames, altNames.IPs)
|
||||
}
|
||||
|
||||
// If at least one of them exists, we should try to load them
|
||||
// In the case that only one exists, there will most likely be an error anyway
|
||||
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName) {
|
||||
// Try to load apiserver-kubelet-client.crt and apiserver-kubelet-client.key from the PKI directory
|
||||
apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName)
|
||||
if err != nil || apiCert == nil || apiKey == nil {
|
||||
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly")
|
||||
}
|
||||
|
||||
fmt.Println("[certificates] Using the existing API Server kubelet client certificate and key.")
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
// TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageClientAuth flag
|
||||
config := certutil.Config{
|
||||
CommonName: "kube-apiserver-kubelet-client",
|
||||
Organization: []string{kubeadmconstants.MastersGroup},
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while creating API server kubelet client key and certificate [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, apiClientCert, apiClientKey); err != nil {
|
||||
return fmt.Errorf("failure while saving API server kubelet client certificate and key [%v]", err)
|
||||
}
|
||||
fmt.Println("[certificates] Generated API server kubelet client certificate and key.")
|
||||
}
|
||||
|
||||
// If the key exists, we should try to load it
|
||||
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName) {
|
||||
// Try to load sa.key from the PKI directory
|
||||
_, err := pkiutil.TryLoadKeyFromDisk(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly [%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("[certificates] Using the existing service account token signing key.")
|
||||
} else {
|
||||
// The key does NOT exist, let's generate it now
|
||||
saTokenSigningKey, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while creating service account token signing key [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WriteKey(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName, saTokenSigningKey); err != nil {
|
||||
return fmt.Errorf("failure while saving service account token signing key [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WritePublicKey(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName, &saTokenSigningKey.PublicKey); err != nil {
|
||||
return fmt.Errorf("failure while saving service account token signing public key [%v]", err)
|
||||
}
|
||||
fmt.Println("[certificates] Generated service account token signing key and public key.")
|
||||
}
|
||||
|
||||
// front proxy CA and client certs are used to secure a front proxy authenticator which is used to assert identity
|
||||
// without the client cert, you cannot make use of the front proxy and the kube-aggregator uses this connection
|
||||
// so we generate and enable it unconditionally
|
||||
// This is a separte CA, so that front proxy identities cannot hit the API and normal client certs cannot be used
|
||||
// as front proxies.
|
||||
var frontProxyCACert *x509.Certificate
|
||||
var frontProxyCAKey *rsa.PrivateKey
|
||||
// If at least one of them exists, we should try to load them
|
||||
// In the case that only one exists, there will most likely be an error anyway
|
||||
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName) {
|
||||
// Try to load front-proxy-ca.crt and front-proxy-ca.key from the PKI directory
|
||||
frontProxyCACert, frontProxyCAKey, err = pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName)
|
||||
if err != nil || frontProxyCACert == nil || frontProxyCAKey == nil {
|
||||
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly")
|
||||
}
|
||||
|
||||
// The certificate and key could be loaded, but the certificate is not a CA
|
||||
if !frontProxyCACert.IsCA {
|
||||
return fmt.Errorf("certificate and key could be loaded but the certificate is not a CA")
|
||||
}
|
||||
|
||||
fmt.Println("[certificates] Using the existing front-proxy CA certificate and key.")
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
frontProxyCACert, frontProxyCAKey, err = pkiutil.NewCertificateAuthority()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while generating front-proxy CA certificate and key [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName, frontProxyCACert, frontProxyCAKey); err != nil {
|
||||
return fmt.Errorf("failure while saving front-proxy CA certificate and key [%v]", err)
|
||||
}
|
||||
fmt.Println("[certificates] Generated front-proxy CA certificate and key.")
|
||||
}
|
||||
|
||||
// At this point we have a front proxy CA signing key. We can use that create the front proxy client cert if
|
||||
// it doesn't already exist.
|
||||
// If at least one of them exists, we should try to load them
|
||||
// In the case that only one exists, there will most likely be an error anyway
|
||||
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.FrontProxyClientCertAndKeyBaseName) {
|
||||
// Try to load apiserver-kubelet-client.crt and apiserver-kubelet-client.key from the PKI directory
|
||||
apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.FrontProxyClientCertAndKeyBaseName)
|
||||
if err != nil || apiCert == nil || apiKey == nil {
|
||||
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly")
|
||||
}
|
||||
|
||||
fmt.Println("[certificates] Using the existing front-proxy client certificate and key.")
|
||||
} else {
|
||||
// The certificate and the key did NOT exist, let's generate them now
|
||||
// TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageClientAuth flag
|
||||
config := certutil.Config{
|
||||
CommonName: "front-proxy-client",
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while creating front-proxy client key and certificate [%v]", err)
|
||||
}
|
||||
|
||||
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.FrontProxyClientCertAndKeyBaseName, apiClientCert, apiClientKey); err != nil {
|
||||
return fmt.Errorf("failure while saving front-proxy client certificate and key [%v]", err)
|
||||
}
|
||||
fmt.Println("[certificates] Generated front-proxy client certificate and key.")
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", pkiDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAltNamesExist verifies that the cert is valid for all IPs and DNS names it should be valid for
|
||||
func checkAltNamesExist(IPs []net.IP, DNSNames []string, altNames certutil.AltNames) bool {
|
||||
dnsset := setutil.NewString(DNSNames...)
|
||||
|
||||
for _, dnsNameThatShouldExist := range altNames.DNSNames {
|
||||
if !dnsset.Has(dnsNameThatShouldExist) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, ipThatShouldExist := range altNames.IPs {
|
||||
found := false
|
||||
for _, ip := range IPs {
|
||||
if ip.Equal(ipThatShouldExist) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// getAltNames builds an AltNames object for the certutil to use when generating the certificates
|
||||
func getAltNames(cfgAltNames []string, hostname, dnsdomain string, svcSubnet *net.IPNet) certutil.AltNames {
|
||||
altNames := certutil.AltNames{
|
||||
// create AltNames with defaults DNSNames/IPs
|
||||
altNames := &certutil.AltNames{
|
||||
DNSNames: []string{
|
||||
hostname,
|
||||
"kubernetes",
|
||||
"kubernetes.default",
|
||||
"kubernetes.default.svc",
|
||||
fmt.Sprintf("kubernetes.default.svc.%s", dnsdomain),
|
||||
fmt.Sprintf("kubernetes.default.svc.%s", cfg.Networking.DNSDomain),
|
||||
},
|
||||
IPs: []net.IP{
|
||||
internalAPIServerVirtualIP,
|
||||
advertiseAddress,
|
||||
},
|
||||
}
|
||||
|
||||
// Populate IPs/DNSNames from AltNames
|
||||
for _, altname := range cfgAltNames {
|
||||
// adds additional SAN
|
||||
for _, altname := range cfg.APIServerCertSANs {
|
||||
if ip := net.ParseIP(altname); ip != nil {
|
||||
altNames.IPs = append(altNames.IPs, ip)
|
||||
} else if len(validation.IsDNS1123Subdomain(altname)) == 0 {
|
||||
@ -290,11 +171,5 @@ func getAltNames(cfgAltNames []string, hostname, dnsdomain string, svcSubnet *ne
|
||||
}
|
||||
}
|
||||
|
||||
// and lastly, extract the internal IP address for the API server
|
||||
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1)
|
||||
if err != nil {
|
||||
fmt.Printf("[certs] WARNING: Unable to get first IP address from the given CIDR (%s): %v\n", svcSubnet.String(), err)
|
||||
}
|
||||
altNames.IPs = append(altNames.IPs, internalAPIServerVirtualIP)
|
||||
return altNames
|
||||
return altNames, nil
|
||||
}
|
||||
|
@ -17,158 +17,151 @@ limitations under the License.
|
||||
package certs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func TestCreatePKIAssets(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
func TestNewCACertAndKey(t *testing.T) {
|
||||
caCert, _, err := NewCACertAndKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
t.Fatalf("failed call NewCACertAndKey: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
// CIDR too small
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/1"},
|
||||
CertificatesDir: fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir),
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
// CIDR invalid
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "invalid"},
|
||||
CertificatesDir: fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir),
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/24"},
|
||||
CertificatesDir: fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir),
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
assertIsCa(t, caCert)
|
||||
}
|
||||
|
||||
func TestNewAPIServerCertAndKey(t *testing.T) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't get the hostname: %v", err)
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := CreatePKIAssets(rt.cfg)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed CreatePKIAssets with an error:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
advertiseIP := "1.2.3.4"
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: advertiseIP},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
}
|
||||
caCert, caKey, err := NewCACertAndKey()
|
||||
|
||||
apiServerCert, _, err := NewAPIServerCertAndKey(cfg, caCert, caKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
assertIsSignedByCa(t, apiServerCert, caCert)
|
||||
assertHasServerAuth(t, apiServerCert)
|
||||
|
||||
for _, DNSName := range []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"} {
|
||||
assertHasDNSNames(t, apiServerCert, DNSName)
|
||||
}
|
||||
for _, IPAddress := range []string{"10.96.0.1", advertiseIP} {
|
||||
assertHasIPAddresses(t, apiServerCert, net.ParseIP(IPAddress))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAltNamesExist(t *testing.T) {
|
||||
var tests = []struct {
|
||||
IPs []net.IP
|
||||
DNSNames []string
|
||||
requiredAltNames certutil.AltNames
|
||||
succeed bool
|
||||
}{
|
||||
{
|
||||
// equal
|
||||
requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "baz"}},
|
||||
IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")},
|
||||
DNSNames: []string{"foo", "bar", "baz"},
|
||||
succeed: true,
|
||||
},
|
||||
{
|
||||
// the loaded cert has more ips than required, ok
|
||||
requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "baz"}},
|
||||
IPs: []net.IP{net.ParseIP("192.168.2.5"), net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")},
|
||||
DNSNames: []string{"a", "foo", "b", "bar", "baz"},
|
||||
succeed: true,
|
||||
},
|
||||
{
|
||||
// the loaded cert doesn't have all ips
|
||||
requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.2.5"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "baz"}},
|
||||
IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")},
|
||||
DNSNames: []string{"foo", "bar", "baz"},
|
||||
succeed: false,
|
||||
},
|
||||
{
|
||||
// the loaded cert doesn't have all ips
|
||||
requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "b", "baz"}},
|
||||
IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")},
|
||||
DNSNames: []string{"foo", "bar", "baz"},
|
||||
succeed: false,
|
||||
},
|
||||
func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) {
|
||||
caCert, caKey, err := NewCACertAndKey()
|
||||
|
||||
apiClientCert, _, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
succeeded := checkAltNamesExist(rt.IPs, rt.DNSNames, rt.requiredAltNames)
|
||||
if succeeded != rt.succeed {
|
||||
t.Errorf(
|
||||
"failed checkAltNamesExist:\n\texpected: %t\n\t actual: %t",
|
||||
rt.succeed,
|
||||
succeeded,
|
||||
)
|
||||
}
|
||||
assertIsSignedByCa(t, apiClientCert, caCert)
|
||||
assertHasClientAuth(t, apiClientCert)
|
||||
assertHasOrganization(t, apiClientCert, constants.MastersGroup)
|
||||
}
|
||||
|
||||
func TestNewNewServiceAccountSigningKey(t *testing.T) {
|
||||
|
||||
key, err := NewServiceAccountSigningKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of key: %v", err)
|
||||
}
|
||||
|
||||
if key.N.BitLen() < 2048 {
|
||||
t.Error("Service account signing key has less than 2048 bits size")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAltNames(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfgaltnames []string
|
||||
hostname string
|
||||
dnsdomain string
|
||||
servicecidr string
|
||||
expectedIPs []string
|
||||
expectedDNSNames []string
|
||||
}{
|
||||
{
|
||||
cfgaltnames: []string{"foo", "192.168.200.1", "bar.baz"},
|
||||
hostname: "my-node",
|
||||
dnsdomain: "cluster.external",
|
||||
servicecidr: "10.96.0.1/12",
|
||||
expectedIPs: []string{"192.168.200.1", "10.96.0.1"},
|
||||
expectedDNSNames: []string{"my-node", "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.external", "foo", "bar.baz"},
|
||||
},
|
||||
func TestNewFrontProxyCACertAndKey(t *testing.T) {
|
||||
frontProxyCACert, _, err := NewFrontProxyCACertAndKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
_, svcSubnet, _ := net.ParseCIDR(rt.servicecidr)
|
||||
actual := getAltNames(rt.cfgaltnames, rt.hostname, rt.dnsdomain, svcSubnet)
|
||||
for i := range actual.IPs {
|
||||
if rt.expectedIPs[i] != actual.IPs[i].String() {
|
||||
t.Errorf(
|
||||
"failed getAltNames:\n\texpected: %s\n\t actual: %s",
|
||||
rt.expectedIPs[i],
|
||||
actual.IPs[i].String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
for i := range actual.DNSNames {
|
||||
if rt.expectedDNSNames[i] != actual.DNSNames[i] {
|
||||
t.Errorf(
|
||||
"failed getAltNames:\n\texpected: %s\n\t actual: %s",
|
||||
rt.expectedDNSNames[i],
|
||||
actual.DNSNames[i],
|
||||
)
|
||||
}
|
||||
}
|
||||
assertIsCa(t, frontProxyCACert)
|
||||
}
|
||||
|
||||
func TestNewFrontProxyClientCertAndKey(t *testing.T) {
|
||||
frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey()
|
||||
|
||||
frontProxyClientCert, _, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
assertIsSignedByCa(t, frontProxyClientCert, frontProxyCACert)
|
||||
assertHasClientAuth(t, frontProxyClientCert)
|
||||
}
|
||||
|
||||
func assertIsCa(t *testing.T, cert *x509.Certificate) {
|
||||
if !cert.IsCA {
|
||||
t.Error("cert is not a valida CA")
|
||||
}
|
||||
}
|
||||
|
||||
func assertIsSignedByCa(t *testing.T, cert *x509.Certificate, ca *x509.Certificate) {
|
||||
if err := cert.CheckSignatureFrom(ca); err != nil {
|
||||
t.Error("cert is not signed by ca")
|
||||
}
|
||||
}
|
||||
|
||||
func assertHasClientAuth(t *testing.T, cert *x509.Certificate) {
|
||||
for i := range cert.ExtKeyUsage {
|
||||
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageClientAuth {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Error("cert is not a ClientAuth")
|
||||
}
|
||||
|
||||
func assertHasServerAuth(t *testing.T, cert *x509.Certificate) {
|
||||
for i := range cert.ExtKeyUsage {
|
||||
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Error("cert is not a ServerAuth")
|
||||
}
|
||||
|
||||
func assertHasOrganization(t *testing.T, cert *x509.Certificate, OU string) {
|
||||
for i := range cert.Subject.Organization {
|
||||
if cert.Subject.Organization[i] == OU {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("cert does not contain OU %s", OU)
|
||||
}
|
||||
|
||||
func assertHasDNSNames(t *testing.T, cert *x509.Certificate, DNSName string) {
|
||||
for i := range cert.DNSNames {
|
||||
if cert.DNSNames[i] == DNSName {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("cert does not contain DNSName %s", DNSName)
|
||||
}
|
||||
|
||||
func assertHasIPAddresses(t *testing.T, cert *x509.Certificate, IPAddress net.IP) {
|
||||
for i := range cert.IPAddresses {
|
||||
if cert.IPAddresses[i].Equal(IPAddress) {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("cert does not contain IPAddress %s", IPAddress)
|
||||
}
|
||||
|
@ -61,6 +61,16 @@ func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certu
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
// HasServerAuth returns true if the given certificate is a ServerAuth
|
||||
func HasServerAuth(cert *x509.Certificate) bool {
|
||||
for i := range cert.ExtKeyUsage {
|
||||
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
|
||||
if err := WriteKey(pkiPath, name, key); err != nil {
|
||||
return err
|
||||
|
@ -87,6 +87,45 @@ func TestNewCertAndKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasServerAuth(t *testing.T) {
|
||||
caCert, caKey, _ := NewCertificateAuthority()
|
||||
|
||||
var tests = []struct {
|
||||
config certutil.Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
config: certutil.Config{
|
||||
CommonName: "test",
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
config: certutil.Config{
|
||||
CommonName: "test",
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
cert, _, err := NewCertAndKey(caCert, caKey, rt.config)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create cert: %v", err)
|
||||
}
|
||||
actual := HasServerAuth(cert)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed HasServerAuth:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCertAndKey(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
|
@ -52,6 +52,7 @@ filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//cmd/kubeadm/app/util/config:all-srcs",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:all-srcs",
|
||||
"//cmd/kubeadm/app/util/token:all-srcs",
|
||||
],
|
||||
|
45
cmd/kubeadm/app/util/config/BUILD
Normal file
45
cmd/kubeadm/app/util/config/BUILD
Normal file
@ -0,0 +1,45 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["masterconfig.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["masterconfig_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
@ -18,17 +18,20 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
netutil "k8s.io/apimachinery/pkg/util/net"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
// Choose the right address for the API Server to advertise. If the advertise address is localhost or 0.0.0.0, the default interface's IP address is used
|
||||
// This is the same logic as the API Server uses
|
||||
@ -54,15 +57,6 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", kubeadmconstants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion)
|
||||
}
|
||||
|
||||
fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion)
|
||||
fmt.Printf("[init] Using Authorization modes: %v\n", cfg.AuthorizationModes)
|
||||
|
||||
// Warn about the limitations with the current cloudprovider solution.
|
||||
if cfg.CloudProvider != "" {
|
||||
fmt.Println("[init] WARNING: For cloudprovider integrations to work --cloud-provider must be set for all kubelets in the cluster.")
|
||||
fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)")
|
||||
}
|
||||
|
||||
if cfg.Token == "" {
|
||||
var err error
|
||||
cfg.Token, err = tokenutil.GenerateToken()
|
||||
@ -73,3 +67,19 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLoadMasterConfiguration tries to loads a Master configuration from the given file (if defined)
|
||||
func TryLoadMasterConfiguration(cfgPath string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
if cfgPath != "" {
|
||||
b, err := ioutil.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
|
||||
}
|
||||
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil {
|
||||
return fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user