Commit e11020fa authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #48196 from fabriziopandini/kubeadm-phase-certs2

Automatic merge from submit-queue kubeadm: Implementing the certificates phase fully **What this PR does / why we need it:** This contains implementation of certs phases in kubeadm, which is part of the wider effort of implementing phases in kubeadm, previously in alpha stage. The original proposal for this activity can be found [here](https://github.com/kubernetes/kubeadm/pull/156/files) and related comments. Kubeadm phase implementation checklist is defined [here](https://github.com/kubernetes/kubeadm/issues/267) Common implementation guidelines and principles for all phases are defined [here](https://docs.google.com/document/d/1VQMyFIVMfRGQPP3oCUpfjiWtOr3pLxp4g7cP-hXQFXc/edit?usp=sharing) This PR implements: - [x] kubeadm phase certs - [x] kubeadm phase certs all - [x] kubeadm phase certs ca - [x] kubeadm phase certs apiserver - [x] kubeadm phase certs apiserver-kubelet-client - [x] kubeadm phase certs sa - [x] kubeadm phase certs front-proxy-ca - [x] kubeadm phase certs front-proxy-client **Which issue this PR fixes:** none **Special notes for your reviewer:** This PR resubmits the work of #45617, and already includes @luxas reviews. Please note that: - the API - phase\certs.go - is now totally free by any UX concerns, and implements only the core logic for cert generation. - the UX - cmd\phase\certs.go - now takes charge of UX commands and kubeadm own's rules for placing certs in the certificate dir (e.g. create only if exists)
parents e2f6981a c2e9052a
...@@ -13,7 +13,6 @@ go_library( ...@@ -13,7 +13,6 @@ go_library(
srcs = [ srcs = [
"cmd.go", "cmd.go",
"completion.go", "completion.go",
"defaults.go",
"init.go", "init.go",
"join.go", "join.go",
"reset.go", "reset.go",
...@@ -31,13 +30,13 @@ go_library( ...@@ -31,13 +30,13 @@ go_library(
"//cmd/kubeadm/app/node:go_default_library", "//cmd/kubeadm/app/node:go_default_library",
"//cmd/kubeadm/app/phases/addons:go_default_library", "//cmd/kubeadm/app/phases/addons:go_default_library",
"//cmd/kubeadm/app/phases/apiconfig: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/controlplane:go_default_library",
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
"//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library",
"//cmd/kubeadm/app/phases/token:go_default_library", "//cmd/kubeadm/app/phases/token:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library",
"//cmd/kubeadm/app/util: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/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
...@@ -55,7 +54,6 @@ go_library( ...@@ -55,7 +54,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_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/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime: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/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library",
...@@ -66,7 +64,6 @@ go_library( ...@@ -66,7 +64,6 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"defaults_test.go",
"reset_test.go", "reset_test.go",
"token_test.go", "token_test.go",
], ],
......
...@@ -31,16 +31,17 @@ import ( ...@@ -31,16 +31,17 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "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" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons" addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons"
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig" 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" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token" tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight" "k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" 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/api"
"k8s.io/kubernetes/pkg/util/version" "k8s.io/kubernetes/pkg/util/version"
) )
...@@ -164,11 +165,20 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, ...@@ -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.) // 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 { if err != nil {
return nil, err 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 { if !skipPreFlight {
fmt.Println("[preflight] Running pre-flight checks") fmt.Println("[preflight] Running pre-flight checks")
...@@ -202,7 +212,7 @@ func (i *Init) Validate(cmd *cobra.Command) error { ...@@ -202,7 +212,7 @@ func (i *Init) Validate(cmd *cobra.Command) error {
func (i *Init) Run(out io.Writer) error { func (i *Init) Run(out io.Writer) error {
// PHASE 1: Generate certificates // PHASE 1: Generate certificates
err := certphase.CreatePKIAssets(i.cfg) err := cmdphases.CreatePKIAssets(i.cfg)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -5,6 +5,7 @@ licenses(["notice"]) ...@@ -5,6 +5,7 @@ licenses(["notice"])
load( load(
"@io_bazel_rules_go//go:def.bzl", "@io_bazel_rules_go//go:def.bzl",
"go_library", "go_library",
"go_test",
) )
go_library( go_library(
...@@ -21,16 +22,31 @@ go_library( ...@@ -21,16 +22,31 @@ go_library(
"//cmd/kubeadm/app/apis/kubeadm: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/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation: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:go_default_library",
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
"//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library",
"//cmd/kubeadm/app/util: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/kubeconfig:go_default_library",
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//vendor/github.com/spf13/cobra: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",
], ],
) )
......
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package phases
import (
"fmt"
"html/template"
"io/ioutil"
"os"
"path"
"testing"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
// required for triggering api machinery startup when running unit tests
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
)
func TestSubCmdCertsCreateFiles(t *testing.T) {
subCmds := newSubCmdCerts()
var tests = []struct {
subCmds []string
expectedFiles []string
}{
{
subCmds: []string{"all"},
expectedFiles: []string{
kubeadmconstants.CACertName, kubeadmconstants.CAKeyName,
kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName,
kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName,
kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName,
kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName,
kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName,
},
},
{
subCmds: []string{"ca"},
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName},
},
{
subCmds: []string{"ca", "apiserver"},
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName},
},
{
subCmds: []string{"ca", "apiserver-kubelet-client"},
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName},
},
{
subCmds: []string{"sa"},
expectedFiles: []string{kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName},
},
{
subCmds: []string{"front-proxy-ca"},
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName},
},
{
subCmds: []string{"front-proxy-ca", "front-proxy-client"},
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName, kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName},
},
}
for _, test := range tests {
// Temporary folder for the test case
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
// executes given sub commands
for _, subCmdName := range test.subCmds {
subCmd := getSubCmd(t, subCmdName, subCmds)
subCmd.SetArgs([]string{fmt.Sprintf("--cert-dir=%s", tmpdir)})
if err := subCmd.Execute(); err != nil {
t.Fatalf("Could not execute subcommand: %s", subCmdName)
}
}
// verify expected files are there
assertFilesCount(t, tmpdir, len(test.expectedFiles))
for _, file := range test.expectedFiles {
assertFileExists(t, tmpdir, file)
}
}
}
func TestSubCmdApiServerFlags(t *testing.T) {
subCmds := newSubCmdCerts()
// Temporary folder for the test case
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
// creates ca cert
subCmd := getSubCmd(t, "ca", subCmds)
subCmd.SetArgs([]string{fmt.Sprintf("--cert-dir=%s", tmpdir)})
if err := subCmd.Execute(); err != nil {
t.Fatalf("Could not execute subcommand ca")
}
// creates apiserver cert
subCmd = getSubCmd(t, "apiserver", subCmds)
subCmd.SetArgs([]string{
fmt.Sprintf("--cert-dir=%s", tmpdir),
"--apiserver-cert-extra-sans=foo,boo",
"--service-cidr=10.0.0.0/24",
"--service-dns-domain=mycluster.local",
"--apiserver-advertise-address=1.2.3.4",
})
if err := subCmd.Execute(); err != nil {
t.Fatalf("Could not execute subcommand apiserver")
}
APIserverCert, err := pkiutil.TryLoadCertFromDisk(tmpdir, kubeadmconstants.APIServerCertAndKeyBaseName)
if err != nil {
t.Fatalf("Error loading API server certificate: %v", err)
}
hostname, err := os.Hostname()
if err != nil {
t.Errorf("couldn't get the hostname: %v", err)
}
for i, name := range []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.mycluster.local"} {
if APIserverCert.DNSNames[i] != name {
t.Errorf("APIserverCert.DNSNames[%d] is %s instead of %s", i, APIserverCert.DNSNames[i], name)
}
}
for i, ip := range []string{"10.0.0.1", "1.2.3.4"} {
if APIserverCert.IPAddresses[i].String() != ip {
t.Errorf("APIserverCert.IPAddresses[%d] is %s instead of %s", i, APIserverCert.IPAddresses[i], ip)
}
}
}
func TestSubCmdReadsConfig(t *testing.T) {
subCmds := newSubCmdCerts()
var tests = []struct {
subCmds []string
expectedFileCount int
}{
{
subCmds: []string{"sa"},
expectedFileCount: 2,
},
{
subCmds: []string{"front-proxy-ca", "front-proxy-client"},
expectedFileCount: 4,
},
{
subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"},
expectedFileCount: 6,
},
{
subCmds: []string{"all"},
expectedFileCount: 12,
},
}
for _, test := range tests {
// Temporary folder for the test case
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
configPath := saveDummyCfg(t, tmpdir)
// executes given sub commands
for _, subCmdName := range test.subCmds {
subCmd := getSubCmd(t, subCmdName, subCmds)
subCmd.SetArgs([]string{fmt.Sprintf("--config=%s", configPath)})
if err := subCmd.Execute(); err != nil {
t.Fatalf("Could not execute command: %s", subCmdName)
}
}
// verify expected files are there
// NB. test.expectedFileCount + 1 because in this test case the tempdir where key/certificates
// are saved contains also the dummy configuration file
assertFilesCount(t, tmpdir, test.expectedFileCount+1)
}
}
func getSubCmd(t *testing.T, name string, subCmds []*cobra.Command) *cobra.Command {
for _, subCmd := range subCmds {
if subCmd.Name() == name {
return subCmd
}
}
t.Fatalf("Unable to find sub command %s", name)
return nil
}
func assertFilesCount(t *testing.T, dirName string, count int) {
files, err := ioutil.ReadDir(dirName)
if err != nil {
t.Fatalf("Couldn't read files from tmpdir: %s", err)
}
if len(files) != count {
t.Errorf("dir does contains %d, %d expected", len(files), count)
for _, f := range files {
t.Error(f.Name())
}
}
}
func assertFileExists(t *testing.T, dirName string, fileName string) {
path := path.Join(dirName, fileName)
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Errorf("file %s does not exist", fileName)
}
}
func saveDummyCfg(t *testing.T, dirName string) string {
path := path.Join(dirName, "dummyconfig.yaml")
cfgTemplate := template.Must(template.New("init").Parse(dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
certificatesDir: {{.CertificatesDir}}
`)))
f, err := os.Create(path)
if err != nil {
t.Errorf("error creating dummyconfig file %s: %v", path, err)
}
templateData := struct {
CertificatesDir string
}{
CertificatesDir: dirName,
}
err = cfgTemplate.Execute(f, templateData)
if err != nil {
t.Errorf("error generating dummyconfig file %s: %v", path, err)
}
f.Close()
return path
}
...@@ -37,10 +37,12 @@ const ( ...@@ -37,10 +37,12 @@ const (
APIServerCertAndKeyBaseName = "apiserver" APIServerCertAndKeyBaseName = "apiserver"
APIServerCertName = "apiserver.crt" APIServerCertName = "apiserver.crt"
APIServerKeyName = "apiserver.key" APIServerKeyName = "apiserver.key"
APIServerCertCommonName = "kube-apiserver" //used as subject.commonname attribute (CN)
APIServerKubeletClientCertAndKeyBaseName = "apiserver-kubelet-client" APIServerKubeletClientCertAndKeyBaseName = "apiserver-kubelet-client"
APIServerKubeletClientCertName = "apiserver-kubelet-client.crt" APIServerKubeletClientCertName = "apiserver-kubelet-client.crt"
APIServerKubeletClientKeyName = "apiserver-kubelet-client.key" APIServerKubeletClientKeyName = "apiserver-kubelet-client.key"
APIServerKubeletClientCertCommonName = "kube-apiserver-kubelet-client" //used as subject.commonname attribute (CN)
ServiceAccountKeyBaseName = "sa" ServiceAccountKeyBaseName = "sa"
ServiceAccountPublicKeyName = "sa.pub" ServiceAccountPublicKeyName = "sa.pub"
...@@ -53,6 +55,7 @@ const ( ...@@ -53,6 +55,7 @@ const (
FrontProxyClientCertAndKeyBaseName = "front-proxy-client" FrontProxyClientCertAndKeyBaseName = "front-proxy-client"
FrontProxyClientCertName = "front-proxy-client.crt" FrontProxyClientCertName = "front-proxy-client.crt"
FrontProxyClientKeyName = "front-proxy-client.key" FrontProxyClientKeyName = "front-proxy-client.key"
FrontProxyClientCertCommonName = "front-proxy-client" //used as subject.commonname attribute (CN)
AdminKubeConfigFileName = "admin.conf" AdminKubeConfigFileName = "admin.conf"
KubeletKubeConfigFileName = "kubelet.conf" KubeletKubeConfigFileName = "kubelet.conf"
......
...@@ -15,7 +15,7 @@ go_test( ...@@ -15,7 +15,7 @@ go_test(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//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( ...@@ -31,7 +31,6 @@ go_library(
"//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
"//pkg/registry/core/service/ipallocator: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/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library",
], ],
......
...@@ -17,158 +17,151 @@ limitations under the License. ...@@ -17,158 +17,151 @@ limitations under the License.
package certs package certs
import ( import (
"fmt" "crypto/x509"
"io/ioutil"
"net" "net"
"os" "os"
"testing" "testing"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
) )
func TestCreatePKIAssets(t *testing.T) { func TestNewCACertAndKey(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "") caCert, _, err := NewCACertAndKey()
if err != nil { 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,
},
}
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),
)
} }
assertIsCa(t, caCert)
}
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) { func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) {
var tests = []struct { caCert, caKey, err := NewCACertAndKey()
IPs []net.IP
DNSNames []string apiClientCert, _, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey)
requiredAltNames certutil.AltNames if err != nil {
succeed bool t.Fatalf("failed creation of cert and key: %v", err)
}{
{
// 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,
},
}
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) { func TestNewFrontProxyCACertAndKey(t *testing.T) {
var tests = []struct { frontProxyCACert, _, err := NewFrontProxyCACertAndKey()
cfgaltnames []string if err != nil {
hostname string t.Fatalf("failed creation of cert and key: %v", err)
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],
)
} }
assertIsCa(t, frontProxyCACert)
}
func TestNewFrontProxyClientCertAndKey(t *testing.T) {
frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey()
frontProxyClientCert, _, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey)
if err != nil {
t.Fatalf("failed creation of cert and key: %v", err)
}
assertIsSignedByCa(t, frontProxyClientCert, frontProxyCACert)
assertHasClientAuth(t, frontProxyClientCert)
}
func assertIsCa(t *testing.T, cert *x509.Certificate) {
if !cert.IsCA {
t.Error("cert is not a valida CA")
}
}
func assertIsSignedByCa(t *testing.T, cert *x509.Certificate, ca *x509.Certificate) {
if err := cert.CheckSignatureFrom(ca); err != nil {
t.Error("cert is not signed by ca")
}
}
func assertHasClientAuth(t *testing.T, cert *x509.Certificate) {
for i := range cert.ExtKeyUsage {
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageClientAuth {
return
}
}
t.Error("cert is not a ClientAuth")
}
func assertHasServerAuth(t *testing.T, cert *x509.Certificate) {
for i := range cert.ExtKeyUsage {
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth {
return
}
}
t.Error("cert is not a ServerAuth")
}
func assertHasOrganization(t *testing.T, cert *x509.Certificate, OU string) {
for i := range cert.Subject.Organization {
if cert.Subject.Organization[i] == OU {
return
}
}
t.Errorf("cert does not contain OU %s", OU)
}
func assertHasDNSNames(t *testing.T, cert *x509.Certificate, DNSName string) {
for i := range cert.DNSNames {
if cert.DNSNames[i] == DNSName {
return
}
}
t.Errorf("cert does not contain DNSName %s", DNSName)
}
func assertHasIPAddresses(t *testing.T, cert *x509.Certificate, IPAddress net.IP) {
for i := range cert.IPAddresses {
if cert.IPAddresses[i].Equal(IPAddress) {
return
} }
} }
t.Errorf("cert does not contain IPAddress %s", IPAddress)
} }
...@@ -61,6 +61,16 @@ func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certu ...@@ -61,6 +61,16 @@ func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certu
return cert, key, nil 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 { func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
if err := WriteKey(pkiPath, name, key); err != nil { if err := WriteKey(pkiPath, name, key); err != nil {
return err return err
......
...@@ -87,6 +87,45 @@ func TestNewCertAndKey(t *testing.T) { ...@@ -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) { func TestWriteCertAndKey(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "") tmpdir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
......
...@@ -52,6 +52,7 @@ filegroup( ...@@ -52,6 +52,7 @@ filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//cmd/kubeadm/app/util/config:all-srcs",
"//cmd/kubeadm/app/util/kubeconfig:all-srcs", "//cmd/kubeadm/app/util/kubeconfig:all-srcs",
"//cmd/kubeadm/app/util/token:all-srcs", "//cmd/kubeadm/app/util/token:all-srcs",
], ],
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["masterconfig.go"],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/api:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["masterconfig_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
...@@ -18,17 +18,20 @@ package cmd ...@@ -18,17 +18,20 @@ package cmd
import ( import (
"fmt" "fmt"
"io/ioutil"
"net" "net"
"k8s.io/apimachinery/pkg/runtime"
netutil "k8s.io/apimachinery/pkg/util/net" netutil "k8s.io/apimachinery/pkg/util/net"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/version" "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 // 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 // This is the same logic as the API Server uses
...@@ -54,15 +57,6 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { ...@@ -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) 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 == "" { if cfg.Token == "" {
var err error var err error
cfg.Token, err = tokenutil.GenerateToken() cfg.Token, err = tokenutil.GenerateToken()
...@@ -73,3 +67,19 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { ...@@ -73,3 +67,19 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
return nil 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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment