Merge pull request #42018 from luxas/kubeadm_cert_phase

Automatic merge from submit-queue (batch tested with PRs 42365, 42429, 41770, 42018, 35055)

kubeadm: Add --cert-dir, --cert-altnames instead of --api-external-dns-names

**What this PR does / why we need it**:

 - For the beta kubeadm init UX, we need this change
 - Also adds the `kubeadm phase certs selfsign` command that makes the phase invokable independently

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

**Special notes for your reviewer**:

This PR depends on https://github.com/kubernetes/kubernetes/pull/41897

**Release note**:

```release-note
```
@dmmcquay @pires @jbeda @errordeveloper @mikedanese @deads2k @liggitt
This commit is contained in:
Kubernetes Submit Queue 2017-03-03 09:24:46 -08:00 committed by GitHub
commit 4728a0520f
25 changed files with 485 additions and 165 deletions

View File

@ -32,7 +32,6 @@ func SetEnvParams() *EnvParams {
envParams := map[string]string{
"kubernetes_dir": "/etc/kubernetes",
"host_pki_path": "/etc/kubernetes/pki",
"host_etcd_path": "/var/lib/etcd",
"hyperkube_image": "",
"repo_prefix": "gcr.io/google_containers",
@ -48,7 +47,6 @@ func SetEnvParams() *EnvParams {
return &EnvParams{
KubernetesDir: path.Clean(envParams["kubernetes_dir"]),
HostPKIPath: path.Clean(envParams["host_pki_path"]),
HostEtcdPath: path.Clean(envParams["host_etcd_path"]),
HyperkubeImage: envParams["hyperkube_image"],
RepositoryPrefix: envParams["repo_prefix"],

View File

@ -34,6 +34,8 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} {
obj.Networking.DNSDomain = "foo"
obj.AuthorizationMode = "foo"
obj.Discovery.Token = &kubeadm.TokenDiscovery{}
obj.CertificatesDir = "foo"
obj.APIServerCertSANs = []string{}
},
func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) {
c.FuzzNoCustom(obj)

View File

@ -22,7 +22,6 @@ import (
type EnvParams struct {
KubernetesDir string
HostPKIPath string
HostEtcdPath string
HyperkubeImage string
RepositoryPrefix string
@ -40,6 +39,7 @@ type MasterConfiguration struct {
KubernetesVersion string
CloudProvider string
AuthorizationMode string
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
// controller manager are managed by Kubernetes itself. This option is likely to
// become the default in the future.
@ -48,12 +48,18 @@ type MasterConfiguration struct {
APIServerExtraArgs map[string]string
ControllerManagerExtraArgs map[string]string
SchedulerExtraArgs map[string]string
// APIServerCertSANs sets extra Subject Alternative Names for the API Server signing cert
APIServerCertSANs []string
// CertificatesDir specifies where to store or look for all required certificates
CertificatesDir string
}
type API struct {
// AdvertiseAddress sets the address for the API server to advertise.
AdvertiseAddress string
ExternalDNSNames []string
BindPort int32
// BindPort sets the secure port for the API Server to bind to
BindPort int32
}
type Discovery struct {

View File

@ -32,6 +32,7 @@ const (
DefaultDiscoveryBindPort = 9898
DefaultAuthorizationMode = "RBAC"
DefaultCACertPath = "/etc/kubernetes/pki/ca.crt"
DefaultCertificatesDir = "/etc/kubernetes/pki"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
@ -66,6 +67,10 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
if obj.AuthorizationMode == "" {
obj.AuthorizationMode = DefaultAuthorizationMode
}
if obj.CertificatesDir == "" {
obj.CertificatesDir = DefaultCertificatesDir
}
}
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {

View File

@ -30,6 +30,7 @@ type MasterConfiguration struct {
KubernetesVersion string `json:"kubernetesVersion"`
CloudProvider string `json:"cloudProvider"`
AuthorizationMode string `json:"authorizationMode"`
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
// controller manager are managed by Kubernetes itself. This option is likely to
// become the default in the future.
@ -38,13 +39,18 @@ type MasterConfiguration struct {
APIServerExtraArgs map[string]string `json:"apiServerExtraArgs"`
ControllerManagerExtraArgs map[string]string `json:"controllerManagerExtraArgs"`
SchedulerExtraArgs map[string]string `json:"schedulerExtraArgs"`
// APIServerCertSANs sets extra Subject Alternative Names for the API Server signing cert
APIServerCertSANs []string `json:"apiServerCertSANs"`
// CertificatesDir specifies where to store or look for all required certificates
CertificatesDir string `json:"certificatesDir"`
}
type API struct {
// The address for the API server to advertise.
AdvertiseAddress string `json:"advertiseAddress"`
ExternalDNSNames []string `json:"externalDNSNames"`
BindPort int32 `json:"bindPort"`
// AdvertiseAddress sets the address for the API server to advertise.
AdvertiseAddress string `json:"advertiseAddress"`
// BindPort sets the secure port for the API Server to bind to
BindPort int32 `json:"bindPort"`
}
type Discovery struct {

View File

@ -27,8 +27,10 @@ go_library(
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/validation",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
],
)

View File

@ -21,13 +21,15 @@ import (
"net"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
apivalidation "k8s.io/kubernetes/pkg/api/validation"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
)
@ -49,9 +51,11 @@ var cloudproviders = []string{
func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateServiceSubnet(c.Networking.ServiceSubnet, field.NewPath("service subnet"))...)
allErrs = append(allErrs, ValidateCloudProvider(c.CloudProvider, field.NewPath("cloudprovider"))...)
allErrs = append(allErrs, ValidateAuthorizationMode(c.AuthorizationMode, field.NewPath("authorization-mode"))...)
allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...)
allErrs = append(allErrs, ValidateAPIServerCertSANs(c.APIServerCertSANs, field.NewPath("cert-altnames"))...)
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
return allErrs
}
@ -59,7 +63,7 @@ func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDiscovery(c, field.NewPath("discovery"))...)
if !path.IsAbs(c.CACertPath) || !strings.HasSuffix(c.CACertPath, ".crt") {
if !filepath.IsAbs(c.CACertPath) || !strings.HasSuffix(c.CACertPath, ".crt") {
allErrs = append(allErrs, field.Invalid(field.NewPath("ca-cert-path"), c.CACertPath, "the ca certificate path must be an absolute path"))
}
return allErrs
@ -142,35 +146,75 @@ func ValidateToken(t string, fldPath *field.Path) field.ErrorList {
id, secret, err := tokenutil.ParseToken(t)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, nil, err.Error()))
allErrs = append(allErrs, field.Invalid(fldPath, t, err.Error()))
}
if len(id) == 0 || len(secret) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "token must be of form '[a-z0-9]{6}.[a-z0-9]{16}'"))
allErrs = append(allErrs, field.Invalid(fldPath, t, "token must be of form '[a-z0-9]{6}.[a-z0-9]{16}'"))
}
return allErrs
}
func ValidateServiceSubnet(subnet string, fldPath *field.Path) field.ErrorList {
func ValidateAPIServerCertSANs(altnames []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, altname := range altnames {
if len(validation.IsDNS1123Subdomain(altname)) != 0 && net.ParseIP(altname) == nil {
allErrs = append(allErrs, field.Invalid(fldPath, altname, "altname is not a valid dns label or ip address"))
}
}
return allErrs
}
func ValidateIPFromString(ipaddr string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if net.ParseIP(ipaddr) == nil {
allErrs = append(allErrs, field.Invalid(fldPath, ipaddr, "ip address is not valid"))
}
return allErrs
}
func ValidateIPNetFromString(subnet string, minAddrs int64, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
_, svcSubnet, err := net.ParseCIDR(subnet)
if err != nil {
return field.ErrorList{field.Invalid(fldPath, nil, "couldn't parse the service subnet")}
allErrs = append(allErrs, field.Invalid(fldPath, subnet, "couldn't parse subnet"))
return allErrs
}
numAddresses := ipallocator.RangeSize(svcSubnet)
if numAddresses < constants.MinimumAddressesInServiceSubnet {
return field.ErrorList{field.Invalid(fldPath, nil, "service subnet is too small")}
if numAddresses < minAddrs {
allErrs = append(allErrs, field.Invalid(fldPath, subnet, "subnet is too small"))
}
return field.ErrorList{}
return allErrs
}
func ValidateNetworking(c *kubeadm.Networking, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateDNS1123Subdomain(c.DNSDomain, field.NewPath("dns-domain"))...)
allErrs = append(allErrs, ValidateIPNetFromString(c.ServiceSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("service-subnet"))...)
if len(c.PodSubnet) != 0 {
allErrs = append(allErrs, ValidateIPNetFromString(c.PodSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("pod-subnet"))...)
}
return allErrs
}
func ValidateAbsolutePath(path string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if !filepath.IsAbs(path) {
allErrs = append(allErrs, field.Invalid(fldPath, path, "path is not absolute"))
}
return allErrs
}
func ValidateCloudProvider(provider string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(provider) == 0 {
return field.ErrorList{}
return allErrs
}
for _, supported := range cloudproviders {
if provider == supported {
return field.ErrorList{}
return allErrs
}
}
return field.ErrorList{field.Invalid(fldPath, nil, "cloudprovider not supported")}
allErrs = append(allErrs, field.Invalid(fldPath, provider, "cloudprovider not supported"))
return allErrs
}

View File

@ -69,31 +69,6 @@ func TestValidateAuthorizationMode(t *testing.T) {
}
}
func TestValidateServiceSubnet(t *testing.T) {
var tests = []struct {
s string
f *field.Path
expected bool
}{
{"", nil, false},
{"this is not a cidr", nil, false}, // not a CIDR
{"10.0.0.1", nil, false}, // not a CIDR
{"10.96.0.1/29", nil, false}, // CIDR too small, only 8 addresses and we require at least 10
{"10.96.0.1/28", nil, true}, // a /28 subnet is ok because it can contain 16 addresses
{"10.96.0.1/12", nil, true}, // the default subnet should obviously pass as well
}
for _, rt := range tests {
actual := ValidateServiceSubnet(rt.s, rt.f)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateServiceSubnet:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateCloudProvider(t *testing.T) {
var tests = []struct {
s string
@ -118,6 +93,78 @@ func TestValidateCloudProvider(t *testing.T) {
}
}
func TestValidateAPIServerCertSANs(t *testing.T) {
var tests = []struct {
sans []string
expected bool
}{
{[]string{}, true}, // ok if not provided
{[]string{"1,2,,3"}, false}, // not a DNS label or IP
{[]string{"my-hostname", "???&?.garbage"}, false}, // not valid
{[]string{"my-hostname", "my.subdomain", "1.2.3.4"}, true}, // supported
{[]string{"my-hostname2", "my.other.subdomain", "10.0.0.10"}, true}, // supported
}
for _, rt := range tests {
actual := ValidateAPIServerCertSANs(rt.sans, nil)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateAPIServerCertSANs:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateIPFromString(t *testing.T) {
var tests = []struct {
ip string
expected bool
}{
{"", false}, // not valid
{"1234", false}, // not valid
{"1.2", false}, // not valid
{"1.2.3.4/16", false}, // not valid
{"1.2.3.4", true}, // valid
{"16.0.1.1", true}, // valid
}
for _, rt := range tests {
actual := ValidateIPFromString(rt.ip, nil)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateIPFromString:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateIPNetFromString(t *testing.T) {
var tests = []struct {
subnet string
minaddrs int64
expected bool
}{
{"", 0, false}, // not valid
{"1234", 0, false}, // not valid
{"abc", 0, false}, // not valid
{"1.2.3.4", 0, false}, // ip not valid
{"10.0.0.16/29", 10, false}, // valid, but too small. At least 10 addrs needed
{"10.0.0.16/12", 10, true}, // valid
}
for _, rt := range tests {
actual := ValidateIPNetFromString(rt.subnet, rt.minaddrs, nil)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateIPNetFromString:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateMasterConfiguration(t *testing.T) {
var tests = []struct {
s *kubeadm.MasterConfiguration
@ -131,7 +178,9 @@ func TestValidateMasterConfiguration(t *testing.T) {
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/cert/dir",
}, true},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
@ -140,7 +189,9 @@ func TestValidateMasterConfiguration(t *testing.T) {
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/other/cert/dir",
}, true},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
@ -153,7 +204,9 @@ func TestValidateMasterConfiguration(t *testing.T) {
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/yet/another/cert/dir",
}, true},
}
for _, rt := range tests {

View File

@ -88,10 +88,6 @@ func NewCmdInit(out io.Writer) *cobra.Command {
&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort,
"Port for the API Server to bind to",
)
cmd.PersistentFlags().StringSliceVar(
&cfg.API.ExternalDNSNames, "api-external-dns-names", cfg.API.ExternalDNSNames,
"The DNS names to advertise, in case you have configured them yourself",
)
cmd.PersistentFlags().StringVar(
&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet,
"Use alternative range of IP address for service VIPs",
@ -108,6 +104,14 @@ func NewCmdInit(out io.Writer) *cobra.Command {
&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion,
`Choose a specific Kubernetes version for the control plane`,
)
cmd.PersistentFlags().StringVar(
&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir,
`The path where to save and store the certificates`,
)
cmd.PersistentFlags().StringSliceVar(
&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", cfg.APIServerCertSANs,
`Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.`,
)
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file")
@ -179,7 +183,7 @@ func (i *Init) Validate() error {
func (i *Init) Run(out io.Writer) error {
// PHASE 1: Generate certificates
err := certphase.CreatePKIAssets(i.cfg, kubeadmapi.GlobalEnvParams.HostPKIPath)
err := certphase.CreatePKIAssets(i.cfg)
if err != nil {
return err
}
@ -190,7 +194,7 @@ func (i *Init) Run(out io.Writer) error {
// so we'll pick the first one, there is much of chance to have an empty
// slice by the time this gets called
masterEndpoint := fmt.Sprintf("https://%s:%d", i.cfg.API.AdvertiseAddress, i.cfg.API.BindPort)
err = kubeconfigphase.CreateInitKubeConfigFiles(masterEndpoint, kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmapi.GlobalEnvParams.KubernetesDir)
err = kubeconfigphase.CreateInitKubeConfigFiles(masterEndpoint, i.cfg.CertificatesDir, kubeadmapi.GlobalEnvParams.KubernetesDir)
if err != nil {
return err
}
@ -198,7 +202,7 @@ func (i *Init) Run(out io.Writer) error {
// TODO: It's not great to have an exception for token here, but necessary because the apiserver doesn't handle this properly in the API yet
// but relies on files on disk for now, which is daunting.
if i.cfg.Discovery.Token != nil {
if err := tokenphase.CreateTokenAuthFile(tokenutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
if err := tokenphase.CreateTokenAuthFile(i.cfg.CertificatesDir, tokenutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
return err
}
}

View File

@ -10,15 +10,22 @@ load(
go_library(
name = "go_default_library",
srcs = [
"certs.go",
"kubeconfig.go",
"phase.go",
],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/constants:go_default_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/phases/certs:go_default_library",
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//vendor:github.com/spf13/cobra",
"//vendor:k8s.io/apimachinery/pkg/util/net",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/client-go/pkg/api",
],
)

View File

@ -0,0 +1,99 @@
/*
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"
"net"
"github.com/spf13/cobra"
netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/pkg/api"
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"
certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
func NewCmdCerts() *cobra.Command {
cmd := &cobra.Command{
Use: "certs",
Aliases: []string{"certificates"},
Short: "Generate certificates for a Kubernetes cluster.",
RunE: subCmdRunE("certs"),
}
cmd.AddCommand(NewCmdSelfSign())
return cmd
}
func NewCmdSelfSign() *cobra.Command {
// TODO: Move this into a dedicated Certificates Phase API object
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) {
// 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)
},
}
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.")
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)
}
// 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()
}
err = certphase.CreatePKIAssets(config)
if err != nil {
return err
}
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"))...)
return allErrs.ToAggregate()
}

View File

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
@ -74,7 +74,7 @@ func NewCmdClientCerts(out io.Writer) *cobra.Command {
}
func addCommonFlags(cmd *cobra.Command, config *kubeconfigphase.BuildConfigProperties) {
cmd.Flags().StringVar(&config.CertDir, "cert-dir", kubeadmconstants.DefaultCertDir, "The path to the directory where the certificates are.")
cmd.Flags().StringVar(&config.CertDir, "cert-dir", kubeadmapiext.DefaultCertificatesDir, "The path to the directory where the certificates are.")
cmd.Flags().StringVar(&config.ClientName, "client-name", "", "The name of the client for which the KubeConfig file will be generated.")
cmd.Flags().StringVar(&config.APIServer, "server", "", "The location of the api server.")
}

View File

@ -30,6 +30,7 @@ func NewCmdPhase(out io.Writer) *cobra.Command {
RunE: subCmdRunE("phase"),
}
cmd.AddCommand(NewCmdKubeConfig(out))
cmd.AddCommand(NewCmdCerts())
return cmd
}

View File

@ -27,6 +27,7 @@ import (
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
@ -36,11 +37,12 @@ import (
// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(out io.Writer) *cobra.Command {
var skipPreFlight, removeNode bool
var certsDir string
cmd := &cobra.Command{
Use: "reset",
Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'.",
Run: func(cmd *cobra.Command, args []string) {
r, err := NewReset(skipPreFlight, removeNode)
r, err := NewReset(skipPreFlight, removeNode, certsDir)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(r.Run(out))
},
@ -56,14 +58,20 @@ func NewCmdReset(out io.Writer) *cobra.Command {
"Remove this node from the pool of nodes in this cluster",
)
cmd.PersistentFlags().StringVar(
&certsDir, "cert-dir", kubeadmapiext.DefaultCertificatesDir,
"The path to the directory where the certificates are stored. If specified, clean this directory.",
)
return cmd
}
type Reset struct {
removeNode bool
certsDir string
}
func NewReset(skipPreFlight, removeNode bool) (*Reset, error) {
func NewReset(skipPreFlight, removeNode bool, certsDir string) (*Reset, error) {
if !skipPreFlight {
fmt.Println("[preflight] Running pre-flight checks")
@ -76,6 +84,7 @@ func NewReset(skipPreFlight, removeNode bool) (*Reset, error) {
return &Reset{
removeNode: removeNode,
certsDir: certsDir,
}, nil
}
@ -137,7 +146,7 @@ func (r *Reset) Run(out io.Writer) error {
}
// Remove contents from the config and pki directories
resetConfigDir(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmapi.GlobalEnvParams.HostPKIPath)
resetConfigDir(kubeadmapi.GlobalEnvParams.KubernetesDir, r.certsDir)
return nil
}

View File

@ -56,8 +56,6 @@ const (
ControllerManagerKubeConfigFileName = "controller-manager.conf"
SchedulerKubeConfigFileName = "scheduler.conf"
DefaultCertDir = "/etc/kubernetes/pki"
// Important: a "v"-prefix shouldn't exist here; semver doesn't allow that
MinimumControlPlaneVersion = "1.6.0-alpha.2"

View File

@ -60,7 +60,7 @@ func encodeKubeDiscoverySecretData(dcfg *kubeadmapi.TokenDiscovery, apicfg kubea
}
func CreateDiscoveryDeploymentAndSecret(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error {
caCertificatePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmconstants.CACertName)
caCertificatePath := path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)
caCerts, err := certutil.CertsFromFile(caCertificatePath)
if err != nil {
return fmt.Errorf("couldn't load the CA certificate file %s: %v", caCertificatePath, err)

View File

@ -292,10 +292,6 @@ func getComponentBaseCommand(component string) []string {
return []string{"kube-" + component}
}
func getCertFilePath(certName string) string {
return path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, certName)
}
func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) []string {
var command []string
@ -308,13 +304,13 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
"insecure-port": "0",
"admission-control": kubeadmconstants.DefaultAdmissionControl,
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
"service-account-key-file": getCertFilePath(kubeadmconstants.ServiceAccountPublicKeyName),
"client-ca-file": getCertFilePath(kubeadmconstants.CACertName),
"tls-cert-file": getCertFilePath(kubeadmconstants.APIServerCertName),
"tls-private-key-file": getCertFilePath(kubeadmconstants.APIServerKeyName),
"kubelet-client-certificate": getCertFilePath(kubeadmconstants.APIServerKubeletClientCertName),
"kubelet-client-key": getCertFilePath(kubeadmconstants.APIServerKubeletClientKeyName),
"token-auth-file": path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmconstants.CSVTokenFileName),
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
"kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
"token-auth-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CSVTokenFileName),
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
"allow-privileged": "true",
"storage-backend": "etcd3",
@ -324,7 +320,7 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
"requestheader-username-headers": "X-Remote-User",
"requestheader-group-headers": "X-Remote-Group",
"requestheader-extra-headers-prefix": "X-Remote-Extra-",
"requestheader-client-ca-file": getCertFilePath(kubeadmconstants.FrontProxyCACertName),
"requestheader-client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
"requestheader-allowed-names": "front-proxy-client",
}
@ -379,10 +375,10 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted
"address": "127.0.0.1",
"leader-elect": "true",
"kubeconfig": path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName),
"root-ca-file": getCertFilePath(kubeadmconstants.CACertName),
"service-account-private-key-file": getCertFilePath(kubeadmconstants.ServiceAccountPrivateKeyName),
"cluster-signing-cert-file": getCertFilePath(kubeadmconstants.CACertName),
"cluster-signing-key-file": getCertFilePath(kubeadmconstants.CAKeyName),
"root-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"service-account-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
"cluster-signing-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"cluster-signing-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
"insecure-experimental-approve-all-kubelet-csrs-for-group": kubeadmconstants.CSVTokenBootstrapGroup,
"use-service-account-credentials": "true",
}

View File

@ -30,6 +30,8 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
const testCertsDir = "/var/lib/certs"
func TestWriteStaticPodManifests(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
@ -380,21 +382,22 @@ func TestGetAPIServerCommand(t *testing.T) {
}{
{
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123, AdvertiseAddress: "1.2.3.4"},
Networking: kubeadm.Networking{ServiceSubnet: "bar"},
API: kubeadm.API{BindPort: 123, AdvertiseAddress: "1.2.3.4"},
Networking: kubeadm.Networking{ServiceSubnet: "bar"},
CertificatesDir: testCertsDir,
},
expected: []string{
"kube-apiserver",
"--insecure-port=0",
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds",
"--service-cluster-ip-range=bar",
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.pub",
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
"--kubelet-client-certificate=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-kubelet-client.key",
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
"--service-account-key-file=" + testCertsDir + "/sa.pub",
"--client-ca-file=" + testCertsDir + "/ca.crt",
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
"--token-auth-file=" + testCertsDir + "/tokens.csv",
fmt.Sprintf("--secure-port=%d", 123),
"--allow-privileged=true",
"--storage-backend=etcd3",
@ -402,7 +405,7 @@ func TestGetAPIServerCommand(t *testing.T) {
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
"--requestheader-client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/front-proxy-ca.crt",
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
"--requestheader-allowed-names=front-proxy-client",
"--authorization-mode=RBAC",
"--advertise-address=1.2.3.4",
@ -411,21 +414,22 @@ func TestGetAPIServerCommand(t *testing.T) {
},
{
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
Networking: kubeadm.Networking{ServiceSubnet: "bar"},
API: kubeadm.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
Networking: kubeadm.Networking{ServiceSubnet: "bar"},
CertificatesDir: testCertsDir,
},
expected: []string{
"kube-apiserver",
"--insecure-port=0",
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds",
"--service-cluster-ip-range=bar",
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.pub",
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
"--kubelet-client-certificate=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-kubelet-client.key",
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
"--service-account-key-file=" + testCertsDir + "/sa.pub",
"--client-ca-file=" + testCertsDir + "/ca.crt",
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
"--token-auth-file=" + testCertsDir + "/tokens.csv",
fmt.Sprintf("--secure-port=%d", 123),
"--allow-privileged=true",
"--storage-backend=etcd3",
@ -433,7 +437,7 @@ func TestGetAPIServerCommand(t *testing.T) {
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
"--requestheader-client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/front-proxy-ca.crt",
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
"--requestheader-allowed-names=front-proxy-client",
"--authorization-mode=RBAC",
"--advertise-address=4.3.2.1",
@ -442,22 +446,23 @@ func TestGetAPIServerCommand(t *testing.T) {
},
{
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
Networking: kubeadm.Networking{ServiceSubnet: "bar"},
Etcd: kubeadm.Etcd{CertFile: "fiz", KeyFile: "faz"},
API: kubeadm.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
Networking: kubeadm.Networking{ServiceSubnet: "bar"},
Etcd: kubeadm.Etcd{CertFile: "fiz", KeyFile: "faz"},
CertificatesDir: testCertsDir,
},
expected: []string{
"kube-apiserver",
"--insecure-port=0",
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds",
"--service-cluster-ip-range=bar",
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.pub",
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
"--kubelet-client-certificate=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-kubelet-client.key",
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
"--service-account-key-file=" + testCertsDir + "/sa.pub",
"--client-ca-file=" + testCertsDir + "/ca.crt",
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
"--token-auth-file=" + testCertsDir + "/tokens.csv",
fmt.Sprintf("--secure-port=%d", 123),
"--allow-privileged=true",
"--storage-backend=etcd3",
@ -465,7 +470,7 @@ func TestGetAPIServerCommand(t *testing.T) {
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
"--requestheader-client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/front-proxy-ca.crt",
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
"--requestheader-allowed-names=front-proxy-client",
"--authorization-mode=RBAC",
"--advertise-address=4.3.2.1",
@ -492,47 +497,55 @@ func TestGetControllerManagerCommand(t *testing.T) {
expected []string
}{
{
cfg: &kubeadmapi.MasterConfiguration{},
cfg: &kubeadmapi.MasterConfiguration{
CertificatesDir: testCertsDir,
},
expected: []string{
"kube-controller-manager",
"--address=127.0.0.1",
"--leader-elect=true",
"--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.key",
"--root-ca-file=" + testCertsDir + "/ca.crt",
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
"--use-service-account-credentials=true",
},
},
{
cfg: &kubeadmapi.MasterConfiguration{CloudProvider: "foo"},
cfg: &kubeadmapi.MasterConfiguration{
CloudProvider: "foo",
CertificatesDir: testCertsDir,
},
expected: []string{
"kube-controller-manager",
"--address=127.0.0.1",
"--leader-elect=true",
"--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.key",
"--root-ca-file=" + testCertsDir + "/ca.crt",
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
"--use-service-account-credentials=true",
"--cloud-provider=foo",
},
},
{
cfg: &kubeadmapi.MasterConfiguration{Networking: kubeadm.Networking{PodSubnet: "bar"}},
cfg: &kubeadmapi.MasterConfiguration{
Networking: kubeadm.Networking{PodSubnet: "bar"},
CertificatesDir: testCertsDir,
},
expected: []string{
"kube-controller-manager",
"--address=127.0.0.1",
"--leader-elect=true",
"--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.key",
"--root-ca-file=" + testCertsDir + "/ca.crt",
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
"--use-service-account-credentials=true",
"--allocate-node-cidrs=true",

View File

@ -32,6 +32,7 @@ go_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",
"//vendor:k8s.io/apimachinery/pkg/util/validation",
"//vendor:k8s.io/client-go/util/cert",
],
)

View File

@ -24,6 +24,7 @@ import (
"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"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -40,34 +41,22 @@ import (
// 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, pkiDir string) error {
altNames := certutil.AltNames{}
// First, define all domains this cert should be signed for
internalAPIServerFQDN := []string{
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
fmt.Sprintf("kubernetes.default.svc.%s", cfg.Networking.DNSDomain),
}
func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error {
pkiDir := cfg.CertificatesDir
hostname, err := os.Hostname()
if err != nil {
return fmt.Errorf("couldn't get the hostname: %v", err)
}
altNames.DNSNames = append(cfg.API.ExternalDNSNames, hostname)
altNames.DNSNames = append(altNames.DNSNames, internalAPIServerFQDN...)
// and lastly, extract the internal IP address for the API server
_, n, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
_, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
if err != nil {
return fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
}
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(n, 1)
if err != nil {
return fmt.Errorf("unable to allocate IP address for the API server from the given CIDR (%q) [%v]", &cfg.Networking.ServiceSubnet, err)
}
altNames.IPs = append(altNames.IPs, internalAPIServerVirtualIP, net.ParseIP(cfg.API.AdvertiseAddress))
// 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
@ -126,6 +115,7 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiDir string) error {
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
@ -158,9 +148,9 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiDir string) error {
}
// If the key exists, we should try to load it
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.ServiceAccountPrivateKeyName) {
if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName) {
// Try to load sa.key from the PKI directory
_, err := pkiutil.TryLoadKeyFromDisk(pkiDir, kubeadmconstants.ServiceAccountPrivateKeyName)
_, 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)
}
@ -176,12 +166,11 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiDir string) error {
if err = pkiutil.WriteKey(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName, saTokenSigningKey); err != nil {
return fmt.Errorf("failure while saving service account token signing key [%v]", err)
}
fmt.Println("[certificates] Generated service account token signing key.")
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 public key.")
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
@ -254,7 +243,7 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiDir string) error {
return nil
}
// Verify that the cert is valid for all IPs and DNS names it should be valid for
// 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...)
@ -279,3 +268,33 @@ func checkAltNamesExist(IPs []net.IP, DNSNames []string, altNames certutil.AltNa
}
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{
DNSNames: []string{
hostname,
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
fmt.Sprintf("kubernetes.default.svc.%s", dnsdomain),
},
}
// Populate IPs/DNSNames from AltNames
for _, altname := range cfgAltNames {
if ip := net.ParseIP(altname); ip != nil {
altNames.IPs = append(altNames.IPs, ip)
} else if len(validation.IsDNS1123Subdomain(altname)) == 0 {
altNames.DNSNames = append(altNames.DNSNames, altname)
}
}
// 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
}

View File

@ -45,29 +45,32 @@ func TestCreatePKIAssets(t *testing.T) {
{
// CIDR too small
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/1"},
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"},
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"},
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,
},
}
for _, rt := range tests {
actual := CreatePKIAssets(rt.cfg, fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir))
actual := CreatePKIAssets(rt.cfg)
if (actual == nil) != rt.expected {
t.Errorf(
"failed CreatePKIAssets with an error:\n\texpected: %t\n\t actual: %t",
@ -126,3 +129,46 @@ func TestCheckAltNamesExist(t *testing.T) {
}
}
}
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"},
},
}
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],
)
}
}
}
}

View File

@ -22,16 +22,25 @@ package certs
INPUTS:
From MasterConfiguration
.API.ExternalDNSNames is needed for knowing which DNS names the certs should be signed for
.API.AdvertiseAddress is an optional parameter that can be passed for an extra addition to the SAN IPs
.APIServerCertSANs is needed for knowing which DNS names and IPs the API Server serving cert should be valid for
.Networking.DNSDomain is needed for knowing which DNS name the internal kubernetes service has
.Networking.ServiceSubnet is needed for knowing which IP the internal kubernetes service is going to point to
The PKIPath is required for knowing where all certificates should be stored
.CertificatesDir is required for knowing where all certificates should be stored
OUTPUTS:
Files to PKIPath (default /etc/kubernetes/pki):
Files to .CertificatesDir (default /etc/kubernetes/pki):
- ca.crt
- ca.key
- apiserver.crt
- apiserver.key
- apiserver-kubelet-client.crt
- apiserver-kubelet-client.key
- sa.pub
- sa.key
- front-proxy-ca.crt
- front-proxy-ca.key
- front-proxy-client.crt
- front-proxy-client.key
*/

View File

@ -23,16 +23,15 @@ import (
"path"
"k8s.io/apimachinery/pkg/util/uuid"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// CreateTokenAuthFile creates the CSV file that can be used for allowing users with tokens access to the API Server
func CreateTokenAuthFile(bt string) error {
tokenAuthFilePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmconstants.CSVTokenFileName)
if err := os.MkdirAll(kubeadmapi.GlobalEnvParams.HostPKIPath, 0700); err != nil {
return fmt.Errorf("failed to create directory %q [%v]", kubeadmapi.GlobalEnvParams.HostPKIPath, err)
func CreateTokenAuthFile(certsDir, bt string) error {
tokenAuthFilePath := path.Join(certsDir, kubeadmconstants.CSVTokenFileName)
if err := os.MkdirAll(certsDir, 0700); err != nil {
return fmt.Errorf("failed to create directory %q [%v]", certsDir, err)
}
serialized := []byte(fmt.Sprintf("%s,%s,%s,%s\n", bt, kubeadmconstants.CSVTokenBootstrapUser, uuid.NewUUID(), kubeadmconstants.CSVTokenBootstrapGroup))
// DumpReaderToFile create a file with mode 0600

View File

@ -540,7 +540,7 @@ func RunJoinNodeChecks(cfg *kubeadmapi.NodeConfiguration) error {
PortOpenCheck{port: 10250},
DirAvailableCheck{Path: filepath.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")},
DirAvailableCheck{Path: "/var/lib/kubelet"},
FileAvailableCheck{Path: filepath.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmconstants.CACertName)},
FileAvailableCheck{Path: cfg.CACertPath},
FileAvailableCheck{Path: filepath.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)},
FileContentCheck{Path: bridgenf, Content: []byte{'1'}},
InPathCheck{executable: "ip", mandatory: true},

View File

@ -20,6 +20,7 @@ apiserver-advertise-address
apiserver-arg-overrides
apiserver-arg-overrides
apiserver-bind-port
apiserver-cert-extra-sans
apiserver-count
apiserver-count
apiserver-count
@ -72,6 +73,7 @@ build-only
build-tag
ca-cert-path
cadvisor-port
cert-altnames
cert-dir
certificate-authority
cgroup-driver
@ -163,6 +165,7 @@ discovery-file
discovery-port
discovery-token
dns-bind-address
dns-domain
dns-port
dns-provider
dns-provider-config