fully implement kubeadm-phase-certs - stash

pull/6/head
fabriziopandini 2017-07-08 14:58:11 +02:00
parent 4361b4d9be
commit c2e9052aea
15 changed files with 982 additions and 433 deletions

View File

@ -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",
],

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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.")
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.")
return cmd
}
// 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)
subCmds = append(subCmds, cmd)
}
// 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()
return subCmds
}
if err = certphase.CreatePKIAssets(config); err != nil {
// 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,
}
for _, action := range certActions {
err := action(cfg)
if 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
}

View 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
}

View File

@ -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"

View File

@ -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",
],

View File

@ -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
hostname, err := os.Hostname()
caCert, caKey, err := pkiutil.NewCertificateAuthority()
if err != nil {
return fmt.Errorf("couldn't get the hostname: %v", err)
return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err)
}
_, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
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 fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
return nil, nil, fmt.Errorf("failure while composing altnames for API server: %v", 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.")
}
// 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,
CommonName: kubeadmconstants.APIServerCertCommonName,
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)
return nil, nil, 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)
return apiCert, apiKey, nil
}
// 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")
}
// 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) {
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",
CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName,
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)
return nil, nil, 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.")
return apiClientCert, apiClientKey, nil
}
// 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)
}
// NewServiceAccountSigningKey generate public/private key pairs for signing service account tokens.
func NewServiceAccountSigningKey() (*rsa.PrivateKey, error) {
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()
saSigningKey, err := certutil.NewPrivateKey()
if err != nil {
return fmt.Errorf("failure while creating service account token signing key [%v]", err)
return nil, 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)
return saSigningKey, nil
}
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
// 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.
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")
}
func NewFrontProxyCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) {
// 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()
frontProxyCACert, frontProxyCAKey, err := pkiutil.NewCertificateAuthority()
if err != nil {
return fmt.Errorf("failure while generating front-proxy CA certificate and key [%v]", err)
return nil, nil, 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.")
return frontProxyCACert, frontProxyCAKey, nil
}
// 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")
}
// 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) {
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",
CommonName: kubeadmconstants.FrontProxyClientCertCommonName,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config)
frontProxyClientCert, frontProxyClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config)
if err != nil {
return fmt.Errorf("failure while creating front-proxy client key and certificate [%v]", err)
return nil, nil, 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.")
return frontProxyClientCert, frontProxyClientKey, nil
}
fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", pkiDir)
// getAltNames builds an AltNames object for to be used when generating apiserver certificate
func getAltNames(cfg *kubeadmapi.MasterConfiguration) (*certutil.AltNames, error) {
return nil
// host name
hostname, err := os.Hostname()
if err != nil {
return nil, fmt.Errorf("couldn't get the hostname: %v", err)
}
// 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
}
// 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)
}
for _, ipThatShouldExist := range altNames.IPs {
found := false
for _, ip := range IPs {
if ip.Equal(ipThatShouldExist) {
found = true
break
}
// internal IP address for the API server
_, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
if err != nil {
return nil, fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
}
if !found {
return false
}
}
return true
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)
}
// 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
}

View File

@ -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)
}
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),
)
func TestNewAPIServerCertAndKey(t *testing.T) {
hostname, err := os.Hostname()
if err != nil {
t.Errorf("couldn't get the hostname: %v", err)
}
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(),
)
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")
}
}
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],
)
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)
}

View File

@ -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

View File

@ -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 {

View File

@ -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",
],

View 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"],
)

View File

@ -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
}