mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Merge pull request #39638 from luxas/kubeadm_refactor_gencerts
Automatic merge from submit-queue (batch tested with PRs 39199, 37273, 29183, 39638, 40199) Refactor/improve the kubeadm generation of certificates **What this PR does / why we need it**: Continues to refactor/improve kubeadm towards beta. **Special notes for your reviewer**: Modify the certs that are generated; generate on demand (if not exist) and only four files instead of eight previously. Basically implements what has been discussed so far in https://github.com/kubernetes/kubeadm/pull/100 **Release note**: ```release-note NONE ``` cc @mikedanese @pires @lukemarsden @errordeveloper @dgoodwin @roberthbailey
This commit is contained in:
commit
0275ca0490
@ -32,6 +32,7 @@ filegroup(
|
|||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:all-srcs",
|
"//cmd/kubeadm/app/apis/kubeadm:all-srcs",
|
||||||
"//cmd/kubeadm/app/cmd:all-srcs",
|
"//cmd/kubeadm/app/cmd:all-srcs",
|
||||||
|
"//cmd/kubeadm/app/constants:all-srcs",
|
||||||
"//cmd/kubeadm/app/discovery:all-srcs",
|
"//cmd/kubeadm/app/discovery:all-srcs",
|
||||||
"//cmd/kubeadm/app/images:all-srcs",
|
"//cmd/kubeadm/app/images:all-srcs",
|
||||||
"//cmd/kubeadm/app/master:all-srcs",
|
"//cmd/kubeadm/app/master:all-srcs",
|
||||||
|
@ -191,7 +191,7 @@ func (i *Init) Validate() 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
|
||||||
caCert, err := certphase.CreatePKIAssets(i.cfg, kubeadmapi.GlobalEnvParams.HostPKIPath)
|
err := certphase.CreatePKIAssets(i.cfg, kubeadmapi.GlobalEnvParams.HostPKIPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ func (i *Init) Run(out io.Writer) error {
|
|||||||
|
|
||||||
if i.cfg.Discovery.Token != nil {
|
if i.cfg.Discovery.Token != nil {
|
||||||
fmt.Printf("[token-discovery] Using token: %s\n", kubeadmutil.BearerToken(i.cfg.Discovery.Token))
|
fmt.Printf("[token-discovery] Using token: %s\n", kubeadmutil.BearerToken(i.cfg.Discovery.Token))
|
||||||
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client, caCert); err != nil {
|
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := kubeadmutil.UpdateOrCreateToken(client, i.cfg.Discovery.Token, kubeadmutil.DefaultTokenDuration); err != nil {
|
if err := kubeadmutil.UpdateOrCreateToken(client, i.cfg.Discovery.Token, kubeadmutil.DefaultTokenDuration); err != nil {
|
||||||
|
27
cmd/kubeadm/app/constants/BUILD
Normal file
27
cmd/kubeadm/app/constants/BUILD
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["constants.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
27
cmd/kubeadm/app/constants/constants.go
Normal file
27
cmd/kubeadm/app/constants/constants.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
CACertAndKeyBaseName = "ca"
|
||||||
|
CACertName = "ca.crt"
|
||||||
|
CAKeyName = "ca.key"
|
||||||
|
|
||||||
|
APIServerCertAndKeyBaseName = "apiserver"
|
||||||
|
APIServerCertName = "apiserver.crt"
|
||||||
|
APIServerKeyName = "apiserver.key"
|
||||||
|
)
|
@ -22,6 +22,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//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/constants:go_default_library",
|
||||||
"//cmd/kubeadm/app/images:go_default_library",
|
"//cmd/kubeadm/app/images:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||||
"//cmd/kubeadm/app/util:go_default_library",
|
"//cmd/kubeadm/app/util:go_default_library",
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
|
|
||||||
const apiCallRetryInterval = 500 * time.Millisecond
|
const apiCallRetryInterval = 500 * time.Millisecond
|
||||||
|
|
||||||
|
// TODO: This method shouldn't exist as a standalone function but be integrated into CreateClientFromFile
|
||||||
func createAPIClient(adminKubeconfig *clientcmdapi.Config) (*clientset.Clientset, error) {
|
func createAPIClient(adminKubeconfig *clientcmdapi.Config) (*clientset.Clientset, error) {
|
||||||
adminClientConfig, err := clientcmd.NewDefaultClientConfig(
|
adminClientConfig, err := clientcmd.NewDefaultClientConfig(
|
||||||
*adminKubeconfig,
|
*adminKubeconfig,
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -27,6 +28,7 @@ import (
|
|||||||
certutil "k8s.io/client-go/pkg/util/cert"
|
certutil "k8s.io/client-go/pkg/util/cert"
|
||||||
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"
|
||||||
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
@ -121,7 +123,17 @@ func newKubeDiscovery(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certific
|
|||||||
return kd
|
return kd
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateDiscoveryDeploymentAndSecret(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset, caCert *x509.Certificate) error {
|
func CreateDiscoveryDeploymentAndSecret(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error {
|
||||||
|
caCertificatePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmconstants.CACertName)
|
||||||
|
caCerts, err := certutil.CertsFromFile(caCertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't load the CA certificate file %s: %v", caCertificatePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one
|
||||||
|
// TODO: Support multiple certs here in order to be able to rotate certs
|
||||||
|
caCert := caCerts[0]
|
||||||
|
|
||||||
kd := newKubeDiscovery(cfg, caCert)
|
kd := newKubeDiscovery(cfg, caCert)
|
||||||
|
|
||||||
if _, err := client.Extensions().Deployments(api.NamespaceSystem).Create(kd.Deployment); err != nil {
|
if _, err := client.Extensions().Deployments(api.NamespaceSystem).Create(kd.Deployment); err != nil {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
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"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
api "k8s.io/kubernetes/pkg/api/v1"
|
api "k8s.io/kubernetes/pkg/api/v1"
|
||||||
@ -301,6 +302,10 @@ func getComponentBaseCommand(component string) []string {
|
|||||||
return []string{"kube-" + component}
|
return []string{"kube-" + component}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCertFilePath(certName string) string {
|
||||||
|
return path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, certName)
|
||||||
|
}
|
||||||
|
|
||||||
func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) []string {
|
func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) []string {
|
||||||
var command []string
|
var command []string
|
||||||
|
|
||||||
@ -313,10 +318,10 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
|
|||||||
"--insecure-bind-address=127.0.0.1",
|
"--insecure-bind-address=127.0.0.1",
|
||||||
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
||||||
"--service-cluster-ip-range="+cfg.Networking.ServiceSubnet,
|
"--service-cluster-ip-range="+cfg.Networking.ServiceSubnet,
|
||||||
"--service-account-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver-key.pem",
|
"--service-account-key-file="+getCertFilePath(kubeadmconstants.APIServerKeyName),
|
||||||
"--client-ca-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/ca.pem",
|
"--client-ca-file="+getCertFilePath(kubeadmconstants.CACertName),
|
||||||
"--tls-cert-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver.pem",
|
"--tls-cert-file="+getCertFilePath(kubeadmconstants.APIServerCertName),
|
||||||
"--tls-private-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver-key.pem",
|
"--tls-private-key-file="+getCertFilePath(kubeadmconstants.APIServerKeyName),
|
||||||
"--token-auth-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/tokens.csv",
|
"--token-auth-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/tokens.csv",
|
||||||
fmt.Sprintf("--secure-port=%d", cfg.API.Port),
|
fmt.Sprintf("--secure-port=%d", cfg.API.Port),
|
||||||
"--allow-privileged",
|
"--allow-privileged",
|
||||||
@ -400,10 +405,10 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted
|
|||||||
"--leader-elect",
|
"--leader-elect",
|
||||||
"--master=127.0.0.1:8080",
|
"--master=127.0.0.1:8080",
|
||||||
"--cluster-name="+DefaultClusterName,
|
"--cluster-name="+DefaultClusterName,
|
||||||
"--root-ca-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/ca.pem",
|
"--root-ca-file="+getCertFilePath(kubeadmconstants.CACertName),
|
||||||
"--service-account-private-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver-key.pem",
|
"--service-account-private-key-file="+getCertFilePath(kubeadmconstants.APIServerKeyName),
|
||||||
"--cluster-signing-cert-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/ca.pem",
|
"--cluster-signing-cert-file="+getCertFilePath(kubeadmconstants.CACertName),
|
||||||
"--cluster-signing-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/ca-key.pem",
|
"--cluster-signing-key-file="+getCertFilePath(kubeadmconstants.CAKeyName),
|
||||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group="+KubeletBootstrapGroup,
|
"--insecure-experimental-approve-all-kubelet-csrs-for-group="+KubeletBootstrapGroup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -372,10 +372,10 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--insecure-bind-address=127.0.0.1",
|
"--insecure-bind-address=127.0.0.1",
|
||||||
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
||||||
"--service-cluster-ip-range=bar",
|
"--service-cluster-ip-range=bar",
|
||||||
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.pem",
|
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
|
||||||
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged",
|
"--allow-privileged",
|
||||||
@ -392,10 +392,10 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--insecure-bind-address=127.0.0.1",
|
"--insecure-bind-address=127.0.0.1",
|
||||||
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
||||||
"--service-cluster-ip-range=bar",
|
"--service-cluster-ip-range=bar",
|
||||||
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.pem",
|
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
|
||||||
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged",
|
"--allow-privileged",
|
||||||
@ -414,10 +414,10 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--insecure-bind-address=127.0.0.1",
|
"--insecure-bind-address=127.0.0.1",
|
||||||
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
||||||
"--service-cluster-ip-range=bar",
|
"--service-cluster-ip-range=bar",
|
||||||
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.pem",
|
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
|
||||||
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged",
|
"--allow-privileged",
|
||||||
@ -438,10 +438,10 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--insecure-bind-address=127.0.0.1",
|
"--insecure-bind-address=127.0.0.1",
|
||||||
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
||||||
"--service-cluster-ip-range=bar",
|
"--service-cluster-ip-range=bar",
|
||||||
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--client-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.pem",
|
"--tls-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.crt",
|
||||||
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--tls-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
"--token-auth-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/tokens.csv",
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged",
|
"--allow-privileged",
|
||||||
@ -480,10 +480,10 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
|||||||
"--leader-elect",
|
"--leader-elect",
|
||||||
"--master=127.0.0.1:8080",
|
"--master=127.0.0.1:8080",
|
||||||
"--cluster-name=" + DefaultClusterName,
|
"--cluster-name=" + DefaultClusterName,
|
||||||
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca-key.pem",
|
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.key",
|
||||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -495,10 +495,10 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
|||||||
"--leader-elect",
|
"--leader-elect",
|
||||||
"--master=127.0.0.1:8080",
|
"--master=127.0.0.1:8080",
|
||||||
"--cluster-name=" + DefaultClusterName,
|
"--cluster-name=" + DefaultClusterName,
|
||||||
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca-key.pem",
|
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.key",
|
||||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
||||||
"--cloud-provider=foo",
|
"--cloud-provider=foo",
|
||||||
},
|
},
|
||||||
@ -511,10 +511,10 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
|||||||
"--leader-elect",
|
"--leader-elect",
|
||||||
"--master=127.0.0.1:8080",
|
"--master=127.0.0.1:8080",
|
||||||
"--cluster-name=" + DefaultClusterName,
|
"--cluster-name=" + DefaultClusterName,
|
||||||
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem",
|
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver.key",
|
||||||
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem",
|
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
|
||||||
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca-key.pem",
|
"--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.key",
|
||||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
||||||
"--allocate-node-cidrs=true",
|
"--allocate-node-cidrs=true",
|
||||||
"--cluster-cidr=bar",
|
"--cluster-cidr=bar",
|
||||||
|
@ -10,10 +10,7 @@ load(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = ["certs_test.go"],
|
||||||
"certs_test.go",
|
|
||||||
"pki_helpers_test.go",
|
|
||||||
],
|
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
@ -27,12 +24,14 @@ go_library(
|
|||||||
srcs = [
|
srcs = [
|
||||||
"certs.go",
|
"certs.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"pki_helpers.go",
|
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_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",
|
"//pkg/registry/core/service/ipallocator:go_default_library",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||||
"//vendor:k8s.io/client-go/pkg/util/cert",
|
"//vendor:k8s.io/client-go/pkg/util/cert",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -46,6 +45,9 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [":package-srcs"],
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//cmd/kubeadm/app/phases/certs/pkiutil:all-srcs",
|
||||||
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
@ -17,21 +17,30 @@ limitations under the License.
|
|||||||
package certs
|
package certs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
setutil "k8s.io/apimachinery/pkg/util/sets"
|
||||||
certutil "k8s.io/client-go/pkg/util/cert"
|
certutil "k8s.io/client-go/pkg/util/cert"
|
||||||
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"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
"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
|
||||||
|
|
||||||
// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.
|
// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.
|
||||||
// It first generates a self-signed CA certificate, a server certificate (signed by the CA) and a key for
|
// It generates a self-signed CA certificate and a server certificate (signed by the CA)
|
||||||
// signing service account tokens. It returns CA key and certificate, which is convenient for use with
|
func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiDir string) error {
|
||||||
// client config funcs.
|
|
||||||
func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiPath string) (*x509.Certificate, error) {
|
|
||||||
altNames := certutil.AltNames{}
|
altNames := certutil.AltNames{}
|
||||||
|
|
||||||
// First, define all domains this cert should be signed for
|
// First, define all domains this cert should be signed for
|
||||||
@ -43,7 +52,7 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiPath string) (*x509
|
|||||||
}
|
}
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't get the hostname: %v", err)
|
return fmt.Errorf("couldn't get the hostname: %v", err)
|
||||||
}
|
}
|
||||||
altNames.DNSNames = append(cfg.API.ExternalDNSNames, hostname)
|
altNames.DNSNames = append(cfg.API.ExternalDNSNames, hostname)
|
||||||
altNames.DNSNames = append(altNames.DNSNames, internalAPIServerFQDN...)
|
altNames.DNSNames = append(altNames.DNSNames, internalAPIServerFQDN...)
|
||||||
@ -53,50 +62,107 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiPath string) (*x509
|
|||||||
if ip := net.ParseIP(a); ip != nil {
|
if ip := net.ParseIP(a); ip != nil {
|
||||||
altNames.IPs = append(altNames.IPs, ip)
|
altNames.IPs = append(altNames.IPs, ip)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("could not parse ip %q", a)
|
return fmt.Errorf("could not parse ip %q", a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// and lastly, extract the internal IP address for the API server
|
// and lastly, extract the internal IP address for the API server
|
||||||
_, n, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
|
_, n, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
|
return fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
|
||||||
}
|
}
|
||||||
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(n, 1)
|
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(n, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to allocate IP address for the API server from the given CIDR (%q) [%v]", &cfg.Networking.ServiceSubnet, err)
|
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)
|
altNames.IPs = append(altNames.IPs, internalAPIServerVirtualIP)
|
||||||
|
|
||||||
caKey, caCert, err := newCertificateAuthority()
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failure while creating CA keys and certificate [%v]", err)
|
return fmt.Errorf("failure while generating CA certificate and key [%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeKeysAndCert(pkiPath, "ca", caKey, caCert); err != nil {
|
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.CACertAndKeyBaseName, caCert, caKey); err != nil {
|
||||||
return nil, fmt.Errorf("failure while saving CA keys and certificate [%v]", err)
|
return fmt.Errorf("failure while saving CA certificate and key [%v]", err)
|
||||||
|
}
|
||||||
|
fmt.Println("[certificates] Generated CA certificate and key.")
|
||||||
}
|
}
|
||||||
fmt.Println("[certificates] Generated Certificate Authority key and certificate.")
|
|
||||||
|
|
||||||
apiKey, apiCert, err := newServerKeyAndCert(caCert, caKey, altNames)
|
// 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 ca.crt and ca.key from the PKI directory
|
||||||
|
apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName)
|
||||||
|
if err != nil || apiCert == nil || apiKey == nil {
|
||||||
|
return fmt.Errorf("certificate and/or key existed but they could not be loaded properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("[certificates] Using the existing API Server certificate and key.")
|
||||||
|
} else {
|
||||||
|
// The certificate and the key did NOT exist, let's generate them now
|
||||||
|
// TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageServerAuth flag
|
||||||
|
config := certutil.Config{
|
||||||
|
CommonName: "kube-apiserver",
|
||||||
|
AltNames: altNames,
|
||||||
|
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
}
|
||||||
|
apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failure while creating API server keys and certificate [%v]", err)
|
return fmt.Errorf("failure while creating API server key and certificate [%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeKeysAndCert(pkiPath, "apiserver", apiKey, apiCert); err != nil {
|
if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName, apiCert, apiKey); err != nil {
|
||||||
return nil, fmt.Errorf("failure while saving API server keys and certificate [%v]", err)
|
return fmt.Errorf("failure while saving API server certificate and key [%v]", err)
|
||||||
|
}
|
||||||
|
fmt.Println("[certificates] Generated API server certificate and key.")
|
||||||
}
|
}
|
||||||
fmt.Println("[certificates] Generated API Server key and certificate")
|
|
||||||
|
|
||||||
// Generate a private key for service accounts
|
fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", pkiDir)
|
||||||
saKey, err := certutil.NewPrivateKey()
|
|
||||||
if err != nil {
|
return nil
|
||||||
return nil, fmt.Errorf("failure while creating service account signing keys [%v]", err)
|
|
||||||
}
|
}
|
||||||
if err := writeKeysAndCert(pkiPath, "sa", saKey, nil); err != nil {
|
|
||||||
return nil, fmt.Errorf("failure while saving service account signing keys [%v]", err)
|
// Verify 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
|
||||||
}
|
}
|
||||||
fmt.Println("[certificates] Generated Service Account signing keys")
|
}
|
||||||
fmt.Printf("[certificates] Created keys and certificates in %q\n", pkiPath)
|
|
||||||
return caCert, nil
|
for _, ipThatShouldExist := range altNames.IPs {
|
||||||
|
found := false
|
||||||
|
for _, ip := range IPs {
|
||||||
|
if ip.Equal(ipThatShouldExist) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,11 @@ package certs
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
certutil "k8s.io/client-go/pkg/util/cert"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ func TestCreatePKIAssets(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, rt := range tests {
|
for _, rt := range tests {
|
||||||
_, actual := CreatePKIAssets(rt.cfg, fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir))
|
actual := CreatePKIAssets(rt.cfg, fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir))
|
||||||
if (actual == nil) != rt.expected {
|
if (actual == nil) != rt.expected {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"failed CreatePKIAssets with an error:\n\texpected: %t\n\t actual: %t",
|
"failed CreatePKIAssets with an error:\n\texpected: %t\n\t actual: %t",
|
||||||
@ -75,3 +77,52 @@ func TestCreatePKIAssets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,12 +30,9 @@ package certs
|
|||||||
|
|
||||||
OUTPUTS:
|
OUTPUTS:
|
||||||
Files to PKIPath (default /etc/kubernetes/pki):
|
Files to PKIPath (default /etc/kubernetes/pki):
|
||||||
- apiserver-key.pem
|
- ca.crt
|
||||||
- apiserver-pub.pem
|
- ca.key
|
||||||
- apiserver.pem
|
- apiserver.crt
|
||||||
- ca-key.pem
|
- apiserver.key
|
||||||
- ca-pub.pem
|
|
||||||
- ca.pem
|
|
||||||
- sa-key.pem
|
|
||||||
- sa-pub.pem
|
|
||||||
*/
|
*/
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 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 certs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
certutil "k8s.io/client-go/pkg/util/cert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCertificateAuthority() (*rsa.PrivateKey, *x509.Certificate, error) {
|
|
||||||
key, err := certutil.NewPrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := certutil.Config{
|
|
||||||
CommonName: "kubernetes",
|
|
||||||
}
|
|
||||||
cert, err := certutil.NewSelfSignedCACert(config, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create self-signed certificate [%v]", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServerKeyAndCert(caCert *x509.Certificate, caKey *rsa.PrivateKey, altNames certutil.AltNames) (*rsa.PrivateKey, *x509.Certificate, error) {
|
|
||||||
key, err := certutil.NewPrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := certutil.Config{
|
|
||||||
CommonName: "kube-apiserver",
|
|
||||||
AltNames: altNames,
|
|
||||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
||||||
}
|
|
||||||
cert, err := certutil.NewSignedCert(config, key, caCert, caKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClientKeyAndCert(config *certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*rsa.PrivateKey, *x509.Certificate, error) {
|
|
||||||
key, err := certutil.NewPrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
|
||||||
}
|
|
||||||
// force usage to client usage
|
|
||||||
configCopy := *config
|
|
||||||
configCopy.Usages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
|
||||||
cert, err := certutil.NewSignedCert(configCopy, key, caCert, caKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeKeysAndCert(pkiPath string, name string, key *rsa.PrivateKey, cert *x509.Certificate) error {
|
|
||||||
publicKeyPath, privateKeyPath, certificatePath := pathsKeysCerts(pkiPath, name)
|
|
||||||
|
|
||||||
if key != nil {
|
|
||||||
if err := certutil.WriteKey(privateKeyPath, certutil.EncodePrivateKeyPEM(key)); err != nil {
|
|
||||||
return fmt.Errorf("unable to write private key file (%q) [%v]", privateKeyPath, err)
|
|
||||||
}
|
|
||||||
if pubKey, err := certutil.EncodePublicKeyPEM(&key.PublicKey); err == nil {
|
|
||||||
if err := certutil.WriteKey(publicKeyPath, pubKey); err != nil {
|
|
||||||
return fmt.Errorf("unable to write public key file (%q) [%v]", publicKeyPath, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unable to encode public key to PEM [%v]", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert != nil {
|
|
||||||
if err := certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert)); err != nil {
|
|
||||||
return fmt.Errorf("unable to write certificate file (%q) [%v]", certificatePath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathsKeysCerts(pkiPath, name string) (string, string, string) {
|
|
||||||
return path.Join(pkiPath, fmt.Sprintf("%s-pub.pem", name)),
|
|
||||||
path.Join(pkiPath, fmt.Sprintf("%s-key.pem", name)),
|
|
||||||
path.Join(pkiPath, fmt.Sprintf("%s.pem", name))
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 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 certs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
certutil "k8s.io/client-go/pkg/util/cert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewCertificateAuthority(t *testing.T) {
|
|
||||||
r, x, err := newCertificateAuthority()
|
|
||||||
|
|
||||||
if r == nil {
|
|
||||||
t.Errorf(
|
|
||||||
"failed newCertificateAuthority, rsa key == nil",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if x == nil {
|
|
||||||
t.Errorf(
|
|
||||||
"failed newCertificateAuthority, x509 cert == nil",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf(
|
|
||||||
"failed newCertificateAuthority with an error: %v",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewServerKeyAndCert(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
caKeySize int
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
// RSA key too small
|
|
||||||
caKeySize: 128,
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Should succeed
|
|
||||||
caKeySize: 2048,
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rt := range tests {
|
|
||||||
caKey, err := rsa.GenerateKey(rand.Reader, rt.caKeySize)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create rsa Private Key")
|
|
||||||
}
|
|
||||||
caCert := &x509.Certificate{}
|
|
||||||
altNames := certutil.AltNames{}
|
|
||||||
_, _, actual := newServerKeyAndCert(caCert, caKey, altNames)
|
|
||||||
if (actual == nil) != rt.expected {
|
|
||||||
t.Errorf(
|
|
||||||
"failed newServerKeyAndCert:\n\texpected: %t\n\t actual: %t",
|
|
||||||
rt.expected,
|
|
||||||
(actual == nil),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewClientKeyAndCert(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
caKeySize int
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
// RSA key too small
|
|
||||||
caKeySize: 128,
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
caKeySize: 2048,
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rt := range tests {
|
|
||||||
caKey, err := rsa.GenerateKey(rand.Reader, rt.caKeySize)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create rsa Private Key")
|
|
||||||
}
|
|
||||||
caCert := &x509.Certificate{}
|
|
||||||
config := &certutil.Config{
|
|
||||||
CommonName: "test",
|
|
||||||
Organization: []string{"test"},
|
|
||||||
}
|
|
||||||
_, _, actual := NewClientKeyAndCert(config, caCert, caKey)
|
|
||||||
if (actual == nil) != rt.expected {
|
|
||||||
t.Errorf(
|
|
||||||
"failed NewClientKeyAndCert:\n\texpected: %t\n\t actual: %t",
|
|
||||||
rt.expected,
|
|
||||||
(actual == nil),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteKeysAndCert(t *testing.T) {
|
|
||||||
tmpdir, err := ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create tmpdir")
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpdir)
|
|
||||||
|
|
||||||
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create rsa Private Key")
|
|
||||||
}
|
|
||||||
caCert := &x509.Certificate{}
|
|
||||||
actual := writeKeysAndCert(tmpdir, "foo", caKey, caCert)
|
|
||||||
if actual != nil {
|
|
||||||
t.Errorf(
|
|
||||||
"failed writeKeysAndCert with an error: %v",
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathsKeysCerts(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
pkiPath string
|
|
||||||
name string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
pkiPath: "foo",
|
|
||||||
name: "bar",
|
|
||||||
expected: []string{"foo/bar-pub.pem", "foo/bar-key.pem", "foo/bar.pem"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pkiPath: "bar",
|
|
||||||
name: "foo",
|
|
||||||
expected: []string{"bar/foo-pub.pem", "bar/foo-key.pem", "bar/foo.pem"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rt := range tests {
|
|
||||||
a, b, c := pathsKeysCerts(rt.pkiPath, rt.name)
|
|
||||||
all := []string{a, b, c}
|
|
||||||
for i := range all {
|
|
||||||
if all[i] != rt.expected[i] {
|
|
||||||
t.Errorf(
|
|
||||||
"failed pathsKeysCerts:\n\texpected: %s\n\t actual: %s",
|
|
||||||
rt.expected[i],
|
|
||||||
all[i],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
37
cmd/kubeadm/app/phases/certs/pkiutil/BUILD
Normal file
37
cmd/kubeadm/app/phases/certs/pkiutil/BUILD
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["pki_helpers_test.go"],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//vendor:k8s.io/client-go/pkg/util/cert"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["pki_helpers.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//vendor:k8s.io/client-go/pkg/util/cert"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
145
cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go
Normal file
145
cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 pkiutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
certutil "k8s.io/client-go/pkg/util/cert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: It should be able to generate different types of private keys, at least: RSA and ECDSA (and in the future maybe Ed25519 as well)
|
||||||
|
// TODO: See if it makes sense to move this package directly to pkg/util/cert
|
||||||
|
|
||||||
|
func NewCertificateAuthority() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||||
|
key, err := certutil.NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := certutil.Config{
|
||||||
|
CommonName: "kubernetes",
|
||||||
|
}
|
||||||
|
cert, err := certutil.NewSelfSignedCACert(config, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to create self-signed certificate [%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||||
|
key, err := certutil.NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := certutil.NewSignedCert(config, key, caCert, caKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
|
||||||
|
certificatePath, privateKeyPath := pathsForCertAndKey(pkiPath, name)
|
||||||
|
|
||||||
|
if cert == nil {
|
||||||
|
return fmt.Errorf("certificate cannot be nil when writing to file")
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return fmt.Errorf("private key cannot be nil when writing to file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := certutil.WriteKey(privateKeyPath, certutil.EncodePrivateKeyPEM(key)); err != nil {
|
||||||
|
return fmt.Errorf("unable to write private key to file %q: [%v]", privateKeyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert)); err != nil {
|
||||||
|
return fmt.Errorf("unable to write certificate to file %q: [%v]", certificatePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertOrKeyExist retuns a boolean whether the cert or the key exists
|
||||||
|
func CertOrKeyExist(pkiPath, name string) bool {
|
||||||
|
certificatePath, privateKeyPath := pathsForCertAndKey(pkiPath, name)
|
||||||
|
|
||||||
|
_, certErr := os.Stat(certificatePath)
|
||||||
|
_, keyErr := os.Stat(privateKeyPath)
|
||||||
|
if os.IsNotExist(certErr) && os.IsNotExist(keyErr) {
|
||||||
|
// The cert or the key did not exist
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both files exist or one of them
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryLoadCertAndKeyFromDisk tries to load a cert and a key from the disk and validates that they are valid
|
||||||
|
func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||||
|
certificatePath, privateKeyPath := pathsForCertAndKey(pkiPath, name)
|
||||||
|
|
||||||
|
certs, err := certutil.CertsFromFile(certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("couldn't load the certificate file %s: %v", certificatePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one
|
||||||
|
// TODO: Support multiple certs here in order to be able to rotate certs
|
||||||
|
cert := certs[0]
|
||||||
|
|
||||||
|
// Parse the private key from a file
|
||||||
|
privKey, err := certutil.PrivateKeyFromFile(privateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("couldn't load the private key file %s: %v", privateKeyPath, err)
|
||||||
|
}
|
||||||
|
var key *rsa.PrivateKey
|
||||||
|
switch k := privKey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
key = k
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
// TODO: Abstract rsa.PrivateKey away and make certutil.NewSignedCert accept a ecdsa.PrivateKey as well
|
||||||
|
// After that, we can support generating kubeconfig files from ecdsa private keys as well
|
||||||
|
return nil, nil, fmt.Errorf("the private key file %s isn't in RSA format", privateKeyPath)
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("the private key file %s isn't in RSA format", privateKeyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check so that the certificate is valid now
|
||||||
|
now := time.Now()
|
||||||
|
if now.Before(cert.NotBefore) {
|
||||||
|
return nil, nil, fmt.Errorf("the certificate is not valid yet")
|
||||||
|
}
|
||||||
|
if now.After(cert.NotAfter) {
|
||||||
|
return nil, nil, fmt.Errorf("the certificate is has expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathsForCertAndKey(pkiPath, name string) (string, string) {
|
||||||
|
return path.Join(pkiPath, fmt.Sprintf("%s.crt", name)), path.Join(pkiPath, fmt.Sprintf("%s.key", name))
|
||||||
|
}
|
109
cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go
Normal file
109
cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 pkiutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
certutil "k8s.io/client-go/pkg/util/cert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCertificateAuthority(t *testing.T) {
|
||||||
|
cert, key, err := NewCertificateAuthority()
|
||||||
|
|
||||||
|
if cert == nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed NewCertificateAuthority, cert == nil",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed NewCertificateAuthority, key == nil",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed NewCertificateAuthority with an error: %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCertAndKey(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
caKeySize int
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// RSA key too small
|
||||||
|
caKeySize: 128,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should succeed
|
||||||
|
caKeySize: 2048,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rt := range tests {
|
||||||
|
caKey, err := rsa.GenerateKey(rand.Reader, rt.caKeySize)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create rsa Private Key")
|
||||||
|
}
|
||||||
|
caCert := &x509.Certificate{}
|
||||||
|
config := certutil.Config{
|
||||||
|
CommonName: "test",
|
||||||
|
Organization: []string{"test"},
|
||||||
|
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
|
}
|
||||||
|
_, _, actual := NewCertAndKey(caCert, caKey, config)
|
||||||
|
if (actual == nil) != rt.expected {
|
||||||
|
t.Errorf(
|
||||||
|
"failed NewCertAndKey:\n\texpected: %t\n\t actual: %t",
|
||||||
|
rt.expected,
|
||||||
|
(actual == nil),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteCertAndKey(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create tmpdir")
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpdir)
|
||||||
|
|
||||||
|
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't create rsa Private Key")
|
||||||
|
}
|
||||||
|
caCert := &x509.Certificate{}
|
||||||
|
actual := WriteCertAndKey(tmpdir, "foo", caCert, caKey)
|
||||||
|
if actual != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed WriteCertAndKey with an error: %v",
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,8 @@ go_library(
|
|||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||||
"//pkg/client/unversioned/clientcmd:go_default_library",
|
"//pkg/client/unversioned/clientcmd:go_default_library",
|
||||||
"//vendor:k8s.io/client-go/pkg/util/cert",
|
"//vendor:k8s.io/client-go/pkg/util/cert",
|
||||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||||
|
@ -17,90 +17,85 @@ limitations under the License.
|
|||||||
package kubeconfig
|
package kubeconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"bytes"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
|
|
||||||
certutil "k8s.io/client-go/pkg/util/cert"
|
certutil "k8s.io/client-go/pkg/util/cert"
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
KubernetesDirPermissions = 0700
|
KubernetesDirPermissions = 0700
|
||||||
|
KubeConfigFilePermissions = 0600
|
||||||
AdminKubeConfigFileName = "admin.conf"
|
AdminKubeConfigFileName = "admin.conf"
|
||||||
AdminKubeConfigClientName = "kubernetes-admin"
|
AdminKubeConfigClientName = "kubernetes-admin"
|
||||||
KubeletKubeConfigFileName = "kubelet.conf"
|
KubeletKubeConfigFileName = "kubelet.conf"
|
||||||
KubeletKubeConfigClientName = "kubelet"
|
KubeletKubeConfigClientName = "kubelet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This function is called from the main init and does the work for the default phase behaviour
|
|
||||||
// TODO: Make an integration test for this function that runs after the certificates phase
|
// TODO: Make an integration test for this function that runs after the certificates phase
|
||||||
// and makes sure that those two phases work well together...
|
// and makes sure that those two phases work well together...
|
||||||
func CreateAdminAndKubeletKubeConfig(masterEndpoint, pkiDir, outDir string) error {
|
|
||||||
// Parse the certificate from a file
|
|
||||||
caCertPath := path.Join(pkiDir, "ca.pem")
|
|
||||||
caCerts, err := certutil.CertsFromFile(caCertPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't load the CA cert file %s: %v", caCertPath, err)
|
|
||||||
}
|
|
||||||
// We are only putting one certificate in the CA certificate pem file, so it's safe to just use the first one
|
|
||||||
caCert := caCerts[0]
|
|
||||||
|
|
||||||
// Parse the rsa private key from a file
|
// TODO: Integration test cases:
|
||||||
caKeyPath := path.Join(pkiDir, "ca-key.pem")
|
// /etc/kubernetes/{admin,kubelet}.conf don't exist => generate kubeconfig files
|
||||||
priv, err := certutil.PrivateKeyFromFile(caKeyPath)
|
// /etc/kubernetes/{admin,kubelet}.conf and certs in /etc/kubernetes/pki exist => don't touch anything as long as everything's valid
|
||||||
if err != nil {
|
// /etc/kubernetes/{admin,kubelet}.conf exist but the server URL is invalid in those files => error
|
||||||
return fmt.Errorf("couldn't load the CA private key file %s: %v", caKeyPath, err)
|
// /etc/kubernetes/{admin,kubelet}.conf exist but the CA cert doesn't match what's in the pki dir => error
|
||||||
}
|
// /etc/kubernetes/{admin,kubelet}.conf exist but not certs => certs will be generated and conflict with the kubeconfig files => error
|
||||||
var caKey *rsa.PrivateKey
|
|
||||||
switch k := priv.(type) {
|
// CreateAdminAndKubeletKubeConfig is called from the main init and does the work for the default phase behaviour
|
||||||
case *rsa.PrivateKey:
|
func CreateAdminAndKubeletKubeConfig(masterEndpoint, pkiDir, outDir string) error {
|
||||||
caKey = k
|
|
||||||
case *ecdsa.PrivateKey:
|
// Try to load ca.crt and ca.key from the PKI directory
|
||||||
// TODO: Abstract rsa.PrivateKey away and make certutil.NewSignedCert accept a ecdsa.PrivateKey as well
|
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||||
// After that, we can support generating kubeconfig files from ecdsa private keys as well
|
if err != nil || caCert == nil || caKey == nil {
|
||||||
return fmt.Errorf("the CA private key file %s isn't in RSA format", caKeyPath)
|
return fmt.Errorf("couldn't create a kubeconfig; the CA files couldn't be loaded: %v", err)
|
||||||
default:
|
|
||||||
return fmt.Errorf("the CA private key file %s isn't in RSA format", caKeyPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User admin should have full access to the cluster
|
// User admin should have full access to the cluster
|
||||||
adminCertConfig := &certutil.Config{
|
// TODO: Add test case that make sure this cert has the x509.ExtKeyUsageClientAuth flag
|
||||||
|
adminCertConfig := certutil.Config{
|
||||||
CommonName: AdminKubeConfigClientName,
|
CommonName: AdminKubeConfigClientName,
|
||||||
Organization: []string{"system:masters"},
|
Organization: []string{"system:masters"},
|
||||||
|
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
}
|
}
|
||||||
adminKubeConfigFilePath := path.Join(outDir, AdminKubeConfigFileName)
|
adminKubeConfigFilePath := filepath.Join(outDir, AdminKubeConfigFileName)
|
||||||
if err := createKubeConfigFileForClient(masterEndpoint, adminKubeConfigFilePath, adminCertConfig, caCert, caKey); err != nil {
|
if err := createKubeConfigFileForClient(masterEndpoint, adminKubeConfigFilePath, adminCertConfig, caCert, caKey); err != nil {
|
||||||
return fmt.Errorf("couldn't create config for %s: %v", AdminKubeConfigClientName, err)
|
return fmt.Errorf("couldn't create config for %s: %v", AdminKubeConfigClientName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The kubelet should have limited access to the cluster
|
// TODO: The kubelet should have limited access to the cluster. Right now, this gives kubelet basically root access
|
||||||
kubeletCertConfig := &certutil.Config{
|
// and we do need that in the bootstrap phase, but we should swap it out after the control plane is up
|
||||||
|
// TODO: Add test case that make sure this cert has the x509.ExtKeyUsageClientAuth flag
|
||||||
|
kubeletCertConfig := certutil.Config{
|
||||||
CommonName: KubeletKubeConfigClientName,
|
CommonName: KubeletKubeConfigClientName,
|
||||||
Organization: []string{"system:nodes"},
|
Organization: []string{"system:nodes"},
|
||||||
|
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
}
|
}
|
||||||
kubeletKubeConfigFilePath := path.Join(outDir, KubeletKubeConfigFileName)
|
kubeletKubeConfigFilePath := filepath.Join(outDir, KubeletKubeConfigFileName)
|
||||||
if err := createKubeConfigFileForClient(masterEndpoint, kubeletKubeConfigFilePath, kubeletCertConfig, caCert, caKey); err != nil {
|
if err := createKubeConfigFileForClient(masterEndpoint, kubeletKubeConfigFilePath, kubeletCertConfig, caCert, caKey); err != nil {
|
||||||
return fmt.Errorf("couldn't create config for %s: %v", KubeletKubeConfigClientName, err)
|
return fmt.Errorf("couldn't create a kubeconfig file for %s: %v", KubeletKubeConfigClientName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO make credentials for the controller manager and kube proxy
|
// TODO make credentials for the controller-manager, scheduler and kube-proxy
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createKubeConfigFileForClient(masterEndpoint, kubeConfigFilePath string, config *certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) error {
|
func createKubeConfigFileForClient(masterEndpoint, kubeConfigFilePath string, config certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) error {
|
||||||
key, cert, err := certphase.NewClientKeyAndCert(config, caCert, caKey)
|
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failure while creating %s client certificate [%v]", config.CommonName, err)
|
return fmt.Errorf("failure while creating %s client certificate [%v]", config.CommonName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeConfig := MakeClientConfigWithCerts(
|
kubeconfig := MakeClientConfigWithCerts(
|
||||||
masterEndpoint,
|
masterEndpoint,
|
||||||
"kubernetes",
|
"kubernetes",
|
||||||
config.CommonName,
|
config.CommonName,
|
||||||
@ -109,26 +104,68 @@ func createKubeConfigFileForClient(masterEndpoint, kubeConfigFilePath string, co
|
|||||||
certutil.EncodeCertPEM(cert),
|
certutil.EncodeCertPEM(cert),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Write it now to a file
|
// Write it now to a file if there already isn't a valid one
|
||||||
return WriteKubeconfigToDisk(kubeConfigFilePath, kubeConfig)
|
return writeKubeconfigToDiskIfNotExists(kubeConfigFilePath, kubeconfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteKubeconfigToDisk(filepath string, kubeconfig *clientcmdapi.Config) error {
|
func WriteKubeconfigToDisk(filename string, kubeconfig *clientcmdapi.Config) error {
|
||||||
// Make sure the dir exists or can be created
|
// Convert the KubeConfig object to a byte array
|
||||||
if err := os.MkdirAll(path.Dir(filepath), KubernetesDirPermissions); err != nil {
|
content, err := clientcmd.Write(*kubeconfig)
|
||||||
return fmt.Errorf("failed to create directory %q [%v]", path.Dir(filepath), err)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If err == nil, the file exists. Oops, we don't allow the file to exist already, fail.
|
// Create the directory if it does not exist
|
||||||
if _, err := os.Stat(filepath); err == nil {
|
dir := filepath.Dir(filename)
|
||||||
return fmt.Errorf("kubeconfig file %s already exists, but must not exist.", filepath)
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(dir, KubernetesDirPermissions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := clientcmd.WriteToFile(*kubeconfig, filepath); err != nil {
|
// No such kubeconfig file exists; write that kubeconfig down to disk then
|
||||||
return fmt.Errorf("failed to write to %q [%v]", filepath, err)
|
if err := ioutil.WriteFile(filename, content, KubeConfigFilePermissions); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filepath)
|
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeKubeconfigToDiskIfNotExists saves the KubeConfig struct to disk if there isn't any file at the given path
|
||||||
|
// If there already is a KubeConfig file at the given path; kubeadm tries to load it and check if the values in the
|
||||||
|
// existing and the expected config equals. If they do; kubeadm will just skip writing the file as it's up-to-date,
|
||||||
|
// but if a file exists but has old content or isn't a kubeconfig file, this function returns an error.
|
||||||
|
func writeKubeconfigToDiskIfNotExists(filename string, expectedConfig *clientcmdapi.Config) error {
|
||||||
|
// Check if the file exist, and if it doesn't, just write it to disk
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
return WriteKubeconfigToDisk(filename, expectedConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The kubeconfig already exists, let's check if it has got the same CA and server URL
|
||||||
|
currentConfig, err := clientcmd.LoadFromFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load kubeconfig that already exists on disk [%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedCtx := expectedConfig.CurrentContext
|
||||||
|
expectedCluster := expectedConfig.Contexts[expectedCtx].Cluster
|
||||||
|
currentCtx := currentConfig.CurrentContext
|
||||||
|
currentCluster := currentConfig.Contexts[currentCtx].Cluster
|
||||||
|
// If the current CA cert on disk doesn't match the expected CA cert, error out because we have a file, but it's stale
|
||||||
|
if !bytes.Equal(currentConfig.Clusters[currentCluster].CertificateAuthorityData, expectedConfig.Clusters[expectedCluster].CertificateAuthorityData) {
|
||||||
|
return fmt.Errorf("a kubeconfig file %q exists already but has got the wrong CA cert", filename)
|
||||||
|
}
|
||||||
|
// If the current API Server location on disk doesn't match the expected API server, error out because we have a file, but it's stale
|
||||||
|
if currentConfig.Clusters[currentCluster].Server != expectedConfig.Clusters[expectedCluster].Server {
|
||||||
|
return fmt.Errorf("a kubeconfig file %q exists already but has got the wrong API Server URL", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kubeadm doesn't validate the existing kubeconfig file more than this (kubeadm trusts the client certs to be valid)
|
||||||
|
// Basically, if we find a kubeconfig file with the same path; the same CA cert and the same server URL;
|
||||||
|
// kubeadm thinks those files are equal and doesn't bother writing a new file
|
||||||
|
fmt.Printf("[kubeconfig] Using existing up-to-date KubeConfig file: %q\n", filename)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,10 +319,7 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error {
|
|||||||
PortOpenCheck{port: 10252},
|
PortOpenCheck{port: 10252},
|
||||||
HTTPProxyCheck{Proto: "https", Host: cfg.API.AdvertiseAddresses[0], Port: int(cfg.API.Port)},
|
HTTPProxyCheck{Proto: "https", Host: cfg.API.AdvertiseAddresses[0], Port: int(cfg.API.Port)},
|
||||||
DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")},
|
DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")},
|
||||||
DirAvailableCheck{Path: kubeadmapi.GlobalEnvParams.HostPKIPath},
|
|
||||||
DirAvailableCheck{Path: "/var/lib/kubelet"},
|
DirAvailableCheck{Path: "/var/lib/kubelet"},
|
||||||
FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.AdminKubeConfigFileName)},
|
|
||||||
FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName)},
|
|
||||||
FileContentCheck{Path: bridgenf, Content: []byte{'1'}},
|
FileContentCheck{Path: bridgenf, Content: []byte{'1'}},
|
||||||
InPathCheck{executable: "ip", mandatory: true},
|
InPathCheck{executable: "ip", mandatory: true},
|
||||||
InPathCheck{executable: "iptables", mandatory: true},
|
InPathCheck{executable: "iptables", mandatory: true},
|
||||||
|
@ -28,6 +28,7 @@ cmd/kubeadm
|
|||||||
cmd/kubeadm
|
cmd/kubeadm
|
||||||
cmd/kubeadm/app/apis/kubeadm/install
|
cmd/kubeadm/app/apis/kubeadm/install
|
||||||
cmd/kubeadm/app/phases/apiconfig
|
cmd/kubeadm/app/phases/apiconfig
|
||||||
|
cmd/kubeadm/app/phases/certs
|
||||||
cmd/kubectl
|
cmd/kubectl
|
||||||
cmd/kubelet
|
cmd/kubelet
|
||||||
cmd/libs/go2idl/client-gen
|
cmd/libs/go2idl/client-gen
|
||||||
|
Loading…
Reference in New Issue
Block a user