Merge pull request #41417 from luxas/kubeadm_test_token

Automatic merge from submit-queue

kubeadm: Hook up kubeadm against the BootstrapSigner

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

This PR makes kubeadm able to use the BootstrapSigner. 
Depends on a few other PRs I've made, I'll rebase and fix this up after they've merged.

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

**Special notes for your reviewer**:

Example usage:
```console
lucas@THENINJA:~/luxas/kubernetes$ sudo ./kubeadm init --kubernetes-version v1.7.0-alpha.0.377-2a6414bc914d55
[sudo] password for lucas: 
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[init] Using Kubernetes version: v1.7.0-alpha.0.377-2a6414bc914d55
[init] Using Authorization mode: RBAC
[preflight] Running pre-flight checks
[preflight] Starting the kubelet service
[certificates] Generated CA certificate and key.
[certificates] Generated API server certificate and key.
[certificates] Generated API server kubelet client certificate and key.
[certificates] Generated service account token signing key.
[certificates] Generated service account token signing public key.
[certificates] Generated front-proxy CA certificate and key.
[certificates] Generated front-proxy client certificate and key.
[certificates] Valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[apiclient] Created API client, waiting for the control plane to become ready
[apiclient] All control plane components are healthy after 21.301384 seconds
[apiclient] Waiting for at least one node to register and become ready
[apiclient] First node is ready after 8.072688 seconds
[apiclient] Test deployment succeeded
[token-discovery] Using token: 67a96d.02405a1773564431
[apiconfig] Created RBAC rules
[addons] Created essential addon: kube-proxy
[addons] Created essential addon: kube-dns

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run:
export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
    http://kubernetes.io/docs/admin/addons/

You can now join any number of machines by running the following on each node:

kubeadm join --token 67a96d.02405a1773564431 192.168.1.115:6443

other-computer $ ./kubeadm join --token 67a96d.02405a1773564431 192.168.1.115:6443
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Skipping pre-flight checks
[preflight] Starting the kubelet service
[discovery] Trying to connect to API Server "192.168.1.115:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Cluster info signature and contents are valid, will use API Server "https://192.168.1.115:6443"
[discovery] Successfully established connection with API Server "192.168.1.115:6443"
[bootstrap] Detected server version: v1.7.0-alpha.0.377+2a6414bc914d55
[bootstrap] The server supports the Certificates API (certificates.k8s.io/v1beta1)
[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request
[csr] Received signed certificate from the API server, generating KubeConfig...
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"

Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.

# Wrong secret!
other-computer $ ./kubeadm join --token 67a96d.02405a1773564432 192.168.1.115:6443
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Skipping pre-flight checks
[preflight] Starting the kubelet service
[discovery] Trying to connect to API Server "192.168.1.115:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Failed to connect to API Server "192.168.1.115:6443": failed to verify JWS signature of received cluster info object, can't trust this API Server
[discovery] Trying to connect to API Server "192.168.1.115:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Failed to connect to API Server "192.168.1.115:6443": failed to verify JWS signature of received cluster info object, can't trust this API Server
^C

# Poor method to create a cluster-info KubeConfig (a KubeConfig file with no credentials), but...
$ printf "kind: Config\n$(sudo ./kubeadm alpha phas --client-name foo --server https://192.168.1.115:6443 --token foo | head -6)\n" > cluster-info.yaml
$ cat cluster-info.yaml
kind: Config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFM01ESXlPREl3TXpBek1Gb1hEVEkzTURJeU5qSXdNekF6TUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTmt0ClFlUVVrenlkQjhTMVM2Y2ZSQ0ZPUnNhdDk2Wi9Id3A3TGJiNkhrSFRub1dvbDhOOVpGUXNDcCtYbDNWbStTS1AKZWFLTTFZWWVDVmNFd0JXNUlWclIxMk51UzYzcjRqK1dHK2NTdjhUOFBpYUZjWXpLalRpODYvajlMYlJYNlFQWAovYmNWTzBZZDVDMVJ1cmRLK2pnRGprdTBwbUl5RDRoWHlEZE1vZk1laStPMytwRC9BeVh5anhyd0crOUFiNjNrCmV6U3BSVHZSZ1h4R2dOMGVQclhKanMwaktKKzkxY0NXZTZJWEZkQnJKbFJnQktuMy9TazRlVVdIUTg0OWJOZHgKdllFblNON1BPaitySktPVEpLMnFlUW9ua0t3WU5qUDBGbW1zNnduL0J0dWkvQW9hanhQNUR3WXdxNEk2SzcvdgplbUM4STEvdzFpSk9RS2dxQmdzQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNL1JQbTYzQTJVaGhPMVljTUNqSEJlUjROOHkKUzB0Q2RNdDRvK0NHRDJKTUt5NDJpNExmQTM2L2hvb01iM2tpUkVSWTRDaENrMGZ3VHpSMHc5Q21nZHlVSTVQSApEc0dIRWdkRHpTVXgyZ3lrWDBQU04zMjRXNCt1T0t6QVRLbm5mMUdiemo4cFA2Uk9QZDdCL09VNiswckhReGY2CnJ6cDRldHhWQjdQWVE0SWg5em1KcVY1QjBuaUZrUDBSYWNDYUxFTVI1NGZNWDk1VHM0amx1VFJrVnBtT1ZGNHAKemlzMlZlZmxLY3VHYTk1ME1CRGRZR2UvbGNXN3JpTkRIUGZZLzRybXIxWG9mUGZEY0Z0ZzVsbUNMWk8wMDljWQpNdGZBdjNBK2dYWjBUeExnU1BpYkxaajYrQU9lMnBiSkxCZkxOTmN6ODJMN1JjQ3RxS01NVHdxVnd0dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://192.168.1.115:6443
  name: kubernetes

lucas@THENINJA:~/luxas/kubernetes$ sudo ./kubeadm token list
TOKEN                     TTL         EXPIRES   USAGES                   DESCRIPTION
67a96d.02405a1773564431   <forever>   <never>   authentication,signing   The default bootstrap token generated by 'kubeadm init'.

# Any token with the authentication usage set works as the --tls-bootstrap-token arg here
other-computer $ ./kubeadm join --skip-preflight-checks --discovery-file cluster-info.yaml --tls-bootstrap-token 67a96d.02405a1773564431
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Skipping pre-flight checks
[preflight] Starting the kubelet service
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Synced cluster-info information from the API Server so we have got the latest information
[bootstrap] Detected server version: v1.7.0-alpha.0.377+2a6414bc914d55
[bootstrap] The server supports the Certificates API (certificates.k8s.io/v1beta1)
[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request
[csr] Received signed certificate from the API server, generating KubeConfig...
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"

Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.

# Delete the RoleBinding that exposes the cluster-info ConfigMap publicly. Now this ConfigMap will be private
lucas@THENINJA:~/luxas/kubernetes$ kubectl -n kube-public edit rolebindings kubeadm:bootstrap-signer-clusterinfo

# This breaks the token joining method
other-computer $ sudo ./kubeadm join --token 67a96d.02405a1773564431 192.168.1.115:6443
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Skipping pre-flight checks
[preflight] Starting the kubelet service
[discovery] Trying to connect to API Server "192.168.1.115:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Failed to request cluster info, will try again: [User "system:anonymous" cannot get configmaps in the namespace "kube-public". (get configmaps cluster-info)]
[discovery] Failed to request cluster info, will try again: [User "system:anonymous" cannot get configmaps in the namespace "kube-public". (get configmaps cluster-info)]
^C

# But we can still connect using the cluster-info file
other-computer $ sudo ./kubeadm join --skip-preflight-checks --discovery-file /k8s/cluster-info.yaml --tls-bootstrap-token 67a96d.02405a1773564431
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Skipping pre-flight checks
[preflight] Starting the kubelet service
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Could not access the cluster-info ConfigMap for refreshing the cluster-info information, but the TLS cert is valid so proceeding...
[discovery] The cluster-info ConfigMap isn't set up properly (no kubeconfig key in ConfigMap), but the TLS cert is valid so proceeding...
[bootstrap] Detected server version: v1.7.0-alpha.0.377+2a6414bc914d55
[bootstrap] The server supports the Certificates API (certificates.k8s.io/v1beta1)
[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request
[csr] Received signed certificate from the API server, generating KubeConfig...
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"

Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.

# What happens if the CA in the cluster-info file and the API Server's CA aren't equal?
# Generated new CA for the cluster-info file, a invalid one for connecting to the cluster
# The new cluster-info file is here:
lucas@THENINJA:~/luxas/kubernetes$ cat cluster-info.yaml
kind: Config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFM01ESXlPREUyTkRBME1Wb1hEVEkzTURJeU5qRTJOREEwTVZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS3VHCmR3MXQ5Nmlkd1YrVmwxQjRVSmZWdGpNZ0NTd1poMG00bmR5Q1JCR3FIRkpMTGhIWjREM2N2ckg1Tk44UmZHS0EKb1cwVjN3Q3R2THl4UFdnZkZMbGtrdERPWnBDQ01oYzd2alYxU2FKUE9MS1BIUUtEdm1CVWFNcTdrUzN5NEg1VApMcUp3bFBUUXNVVW5YNWM5V0pzS2JIcEx6MnJZbC9Pam4veGRtd1lQa3JUTTJwSitMS0RjUkxLTEpiQjhGc2pzCnZBQTg2QURjY3phMDd0WEgxL1MzeTN0UDJMTDN0UVgvZWJIYWNPcHluYnVaNlIwdFhKeUpsTTVlOHRHMzFhWHMKQTV3cGo1d2Z1RGU1amRuTHgxNnFtbG5ueGV3OGp0bk4zSDExYUp6VlErOWlSQUZkUTN4WmN4dWdmQVM2ZndqRwo0QnJFeGpUOUFaRlVQb0VkR09NQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJc0pKRFIzbWMxQ1lCd2ViSkRPNm1MdWkwTk4KS3BVdlBuazlMbWlnb2JmYVhjQWlnUlo2M1pIYTd4MXBHNGpKRG8zY3lxNWEybTAzZ245RFMrcEpKYTdpMmpXUQpaV1YvZ2ZRMEk4RGc0endXU3J0T056NHpTTXQ1cW5JZjVWRC95KzVVSmVRck1XSEVFS1VrdklSQzhuUmIvV1F2CmNRWEpiN1hMY0dtbWJyaXpDSUlDYmI4KzhmNDFUWTZnTmg5ZzduaVdGZlp2VG1jN05aMTNjQVJjajJ0UTAzeVMKbWVPcEc2REdMRENFWWYzRld0QmdleE5CcFlFYy9ydUNnUE9IcEdhelYya3JHdFFNLzI0OGQ2ZndwcVNQOGc4RgpVSHNWZWxiMExnNmgvZ3VSYlZ5SENlck5zTDBJdFFhdjlscmZmWkxQaVA5TzNLQ0pBWk9MbXhEOUhaaz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://192.168.1.115:6443
  name: kubernetes

# Try to join an API Server with the wrong CA
other-computer $ sudo ./kubeadm join --skip-preflight-checks --discovery-file /k8s/cluster-info.yaml --tls-bootstrap-token 67a96d.02405a1773564431
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Skipping pre-flight checks
[preflight] Starting the kubelet service
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.1.115:6443"
[discovery] Failed to validate the API Server's identity, will try again: [Get https://192.168.1.115:6443/api/v1/namespaces/kube-public/configmaps/cluster-info: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")]
[discovery] Failed to validate the API Server's identity, will try again: [Get https://192.168.1.115:6443/api/v1/namespaces/kube-public/configmaps/cluster-info: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")]
[discovery] Failed to validate the API Server's identity, will try again: [Get https://192.168.1.115:6443/api/v1/namespaces/kube-public/configmaps/cluster-info: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")]
[discovery] Failed to validate the API Server's identity, will try again: [Get https://192.168.1.115:6443/api/v1/namespaces/kube-public/configmaps/cluster-info: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")]
^C
```

**Release note**:

```release-note
```
@jbeda @mikedanese @justinsb @pires @dmmcquay @roberthbailey @dgoodwin
This commit is contained in:
Kubernetes Submit Queue 2017-03-04 05:54:16 -08:00 committed by GitHub
commit 7e37b895d7
50 changed files with 718 additions and 1413 deletions

View File

@ -20,7 +20,6 @@ import (
"fmt"
"os"
"path"
"runtime"
"strings"
)
@ -35,7 +34,6 @@ func SetEnvParams() *EnvParams {
"host_etcd_path": "/var/lib/etcd",
"hyperkube_image": "",
"repo_prefix": "gcr.io/google_containers",
"discovery_image": fmt.Sprintf("gcr.io/google_containers/kube-discovery-%s:%s", runtime.GOARCH, "1.0"),
"etcd_image": "",
}
@ -50,7 +48,6 @@ func SetEnvParams() *EnvParams {
HostEtcdPath: path.Clean(envParams["host_etcd_path"]),
HyperkubeImage: envParams["hyperkube_image"],
RepositoryPrefix: envParams["repo_prefix"],
DiscoveryImage: envParams["discovery_image"],
EtcdImage: envParams["etcd_image"],
}
}

View File

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

View File

@ -46,7 +46,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&MasterConfiguration{},
&NodeConfiguration{},
&ClusterInfo{},
)
return nil
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package kubeadm
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -25,7 +27,6 @@ type EnvParams struct {
HostEtcdPath string
HyperkubeImage string
RepositoryPrefix string
DiscoveryImage string
EtcdImage string
}
@ -33,13 +34,15 @@ type MasterConfiguration struct {
metav1.TypeMeta
API API
Discovery Discovery
Etcd Etcd
Networking Networking
KubernetesVersion string
CloudProvider string
AuthorizationMode string
Token string
TokenTTL time.Duration
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
// controller manager are managed by Kubernetes itself. This option is likely to
// become the default in the future.
@ -62,20 +65,6 @@ type API struct {
BindPort int32
}
type Discovery struct {
HTTPS *HTTPSDiscovery
File *FileDiscovery
Token *TokenDiscovery
}
type HTTPSDiscovery struct {
URL string
}
type FileDiscovery struct {
Path string
}
type TokenDiscovery struct {
ID string
Secret string
@ -106,11 +95,3 @@ type NodeConfiguration struct {
TLSBootstrapToken string
Token string
}
// ClusterInfo TODO add description
type ClusterInfo struct {
metav1.TypeMeta
// TODO(phase1+) this may become simply `api.Config`
CertificateAuthorities []string
Endpoints []string
}

View File

@ -18,6 +18,7 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",

View File

@ -20,14 +20,15 @@ import (
"net/url"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
const (
DefaultServiceDNSDomain = "cluster.local"
DefaultServicesSubnet = "10.96.0.0/12"
DefaultKubernetesVersion = "latest"
DefaultKubernetesVersion = "latest-1.6"
// This is only for clusters without internet, were the latest stable version can't be determined
DefaultKubernetesFallbackVersion = "v1.6.0-alpha.1"
DefaultKubernetesFallbackVersion = "v1.6.0-beta.1"
DefaultAPIBindPort = 6443
DefaultDiscoveryBindPort = 9898
DefaultAuthorizationMode = "RBAC"
@ -60,10 +61,6 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
obj.Networking.DNSDomain = DefaultServiceDNSDomain
}
if obj.Discovery.Token == nil && obj.Discovery.File == nil && obj.Discovery.HTTPS == nil {
obj.Discovery.Token = &TokenDiscovery{}
}
if obj.AuthorizationMode == "" {
obj.AuthorizationMode = DefaultAuthorizationMode
}
@ -71,6 +68,10 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
if obj.CertificatesDir == "" {
obj.CertificatesDir = DefaultCertificatesDir
}
if obj.TokenTTL == 0 {
obj.TokenTTL = constants.DefaultTokenDuration
}
}
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {

View File

@ -47,7 +47,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&MasterConfiguration{},
&NodeConfiguration{},
&ClusterInfo{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -24,13 +26,15 @@ type MasterConfiguration struct {
metav1.TypeMeta `json:",inline"`
API API `json:"api"`
Discovery Discovery `json:"discovery"`
Etcd Etcd `json:"etcd"`
Networking Networking `json:"networking"`
KubernetesVersion string `json:"kubernetesVersion"`
CloudProvider string `json:"cloudProvider"`
AuthorizationMode string `json:"authorizationMode"`
Token string `json:"token"`
TokenTTL time.Duration `json:"tokenTTL"`
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
// controller manager are managed by Kubernetes itself. This option is likely to
// become the default in the future.
@ -53,20 +57,6 @@ type API struct {
BindPort int32 `json:"bindPort"`
}
type Discovery struct {
HTTPS *HTTPSDiscovery `json:"https"`
File *FileDiscovery `json:"file"`
Token *TokenDiscovery `json:"token"`
}
type HTTPSDiscovery struct {
URL string `json:"url"`
}
type FileDiscovery struct {
Path string `json:"path"`
}
type TokenDiscovery struct {
ID string `json:"id"`
Secret string `json:"secret"`
@ -96,11 +86,3 @@ type NodeConfiguration struct {
TLSBootstrapToken string `json:"tlsBootstrapToken"`
Token string `json:"token"`
}
// ClusterInfo TODO add description
type ClusterInfo struct {
metav1.TypeMeta `json:",inline"`
// TODO(phase1+) this may become simply `api.Config`
CertificateAuthorities []string `json:"certificateAuthorities"`
Endpoints []string `json:"endpoints"`
}

View File

@ -56,6 +56,7 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList
allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...)
allErrs = append(allErrs, ValidateAPIServerCertSANs(c.APIServerCertSANs, field.NewPath("cert-altnames"))...)
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...)
return allErrs
}
@ -87,6 +88,14 @@ func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.
allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...)
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...)
allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, fldPath)...)
if len(c.DiscoveryToken) != 0 {
allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, fldPath)...)
}
if len(c.DiscoveryFile) != 0 {
allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, fldPath)...)
}
return allErrs
}

View File

@ -172,41 +172,21 @@ func TestValidateMasterConfiguration(t *testing.T) {
}{
{&kubeadm.MasterConfiguration{}, false},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
},
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/cert/dir",
}, true},
}, false},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
File: &kubeadm.FileDiscovery{Path: "foo"},
},
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/other/cert/dir",
}, true},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "abcdef",
Secret: "1234567890123456",
Addresses: []string{"foobar"},
},
},
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/yet/another/cert/dir",
Token: "abcdef.0123456789abcdef",
}, true},
}
for _, rt := range tests {

View File

@ -30,9 +30,7 @@ import (
"github.com/blang/semver"
)
var (
minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion)
)
var minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion)
func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
@ -73,23 +71,11 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)")
}
// Validate token if any, otherwise generate
if cfg.Discovery.Token != nil {
if cfg.Discovery.Token.ID != "" && cfg.Discovery.Token.Secret != "" {
fmt.Printf("[init] A token has been provided, validating [%s]\n", tokenutil.BearerToken(cfg.Discovery.Token))
if valid, err := tokenutil.ValidateToken(cfg.Discovery.Token); valid == false {
return err
}
} else {
fmt.Println("[init] A token has not been provided, generating one")
if err := tokenutil.GenerateToken(cfg.Discovery.Token); err != nil {
return err
}
}
// If there aren't any addresses specified, default to the first advertised address which can be user-provided or the default network interface's IP address
if len(cfg.Discovery.Token.Addresses) == 0 {
cfg.Discovery.Token.Addresses = []string{fmt.Sprintf("%s:%d", cfg.API.AdvertiseAddress, kubeadmapiext.DefaultDiscoveryBindPort)}
if cfg.Token == "" {
var err error
cfg.Token, err = tokenutil.GenerateToken()
if err != nil {
return fmt.Errorf("couldn't generate random token: %v", err)
}
}

View File

@ -31,7 +31,6 @@ import (
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons"
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
@ -40,7 +39,6 @@ import (
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
)
var (
@ -56,16 +54,14 @@ var (
You can now join any number of machines by running the following on each node:
kubeadm join --discovery %s
kubeadm join --token %s %s:%d
`)
)
// NewCmdInit returns "kubeadm init" command.
func NewCmdInit(out io.Writer) *cobra.Command {
versioned := &kubeadmapiext.MasterConfiguration{}
api.Scheme.Default(versioned)
cfg := kubeadmapi.MasterConfiguration{}
api.Scheme.Convert(versioned, &cfg, nil)
cfg := &kubeadmapiext.MasterConfiguration{}
api.Scheme.Default(cfg)
var cfgPath string
var skipPreFlight bool
@ -73,7 +69,11 @@ func NewCmdInit(out io.Writer) *cobra.Command {
Use: "init",
Short: "Run this in order to set up the Kubernetes master",
Run: func(cmd *cobra.Command, args []string) {
i, err := NewInit(cfgPath, &cfg, skipPreFlight)
api.Scheme.Default(cfg)
internalcfg := &kubeadmapi.MasterConfiguration{}
api.Scheme.Convert(cfg, internalcfg, nil)
i, err := NewInit(cfgPath, internalcfg, skipPreFlight)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(i.Validate())
kubeadmutil.CheckErr(i.Run(out))
@ -113,17 +113,20 @@ func NewCmdInit(out io.Writer) *cobra.Command {
`Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.`,
)
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file")
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
cmd.PersistentFlags().BoolVar(
&skipPreFlight, "skip-preflight-checks", skipPreFlight,
"Skip preflight checks normally run before modifying the system",
)
cmd.PersistentFlags().Var(
discovery.NewDiscoveryValue(&cfg.Discovery), "discovery",
"The discovery method kubeadm will use for connecting nodes to the master",
)
cmd.PersistentFlags().StringVar(
&cfg.Token, "token", cfg.Token,
"The token to use for establishing bidirectional trust between nodes and masters.")
cmd.PersistentFlags().DurationVar(
&cfg.TokenTTL, "token-ttl", cfg.TokenTTL,
"The duration before the bootstrap token is automatically deleted. 0 means 'never expires'.")
return cmd
}
@ -199,20 +202,13 @@ func (i *Init) Run(out io.Writer) error {
return err
}
// TODO: It's not great to have an exception for token here, but necessary because the apiserver doesn't handle this properly in the API yet
// but relies on files on disk for now, which is daunting.
if i.cfg.Discovery.Token != nil {
if err := tokenphase.CreateTokenAuthFile(i.cfg.CertificatesDir, tokenutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
return err
}
}
// PHASE 3: Bootstrap the control plane
if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil {
return err
}
client, err := kubemaster.CreateClientAndWaitForAPI(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName))
adminKubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)
client, err := kubemaster.CreateClientAndWaitForAPI(adminKubeConfigPath)
if err != nil {
return err
}
@ -225,22 +221,22 @@ func (i *Init) Run(out io.Writer) error {
if i.cfg.SelfHosted {
// Temporary control plane is up, now we create our self hosted control
// plane components and remove the static manifests:
fmt.Println("[init] Creating self-hosted control plane...")
fmt.Println("[self-hosted] Creating self-hosted control plane...")
if err := kubemaster.CreateSelfHostedControlPlane(i.cfg, client); err != nil {
return err
}
}
// PHASE 4: Set up the bootstrap tokens
if i.cfg.Discovery.Token != nil {
fmt.Printf("[token-discovery] Using token: %s\n", tokenutil.BearerToken(i.cfg.Discovery.Token))
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil {
return err
}
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Discovery.Token, false, kubeadmconstants.DefaultTokenDuration, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil {
return err
}
fmt.Printf("[token] Using token: %s\n", i.cfg.Token)
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil {
return err
}
if err := tokenphase.CreateBootstrapConfigMap(adminKubeConfigPath); err != nil {
return err
}
// PHASE 5: Install and deploy all addons, and configure things as necessary
@ -260,11 +256,6 @@ func (i *Init) Run(out io.Writer) error {
return err
}
fmt.Fprintf(out, initDoneMsgf, path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), generateJoinArgs(i.cfg))
fmt.Fprintf(out, initDoneMsgf, path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), i.cfg.Token, i.cfg.API.AdvertiseAddress, i.cfg.API.BindPort)
return nil
}
// generateJoinArgs generates kubeadm join arguments
func generateJoinArgs(cfg *kubeadmapi.MasterConfiguration) string {
return discovery.NewDiscoveryValue(&cfg.Discovery).String()
}

View File

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/renstrom/dedent"
@ -90,9 +91,11 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
`),
Run: func(cmd *cobra.Command, args []string) {
cfg.DiscoveryTokenAPIServers = args
api.Scheme.Default(cfg)
internalcfg := &kubeadmapi.NodeConfiguration{}
api.Scheme.Convert(cfg, internalcfg, nil)
j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(j.Validate())
@ -174,7 +177,19 @@ func (j *Join) Run(out io.Writer) error {
if err != nil {
return err
}
if err := kubenode.PerformTLSBootstrap(cfg); err != nil {
hostname, err := os.Hostname()
if err != nil {
return err
}
client, err := kubeconfigutil.KubeConfigToClientSet(cfg)
if err != nil {
return err
}
if err := kubenode.ValidateAPIServer(client); err != nil {
return err
}
if err := kubenode.PerformTLSBootstrap(cfg, hostname); err != nil {
return err
}

View File

@ -189,36 +189,37 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command {
// RunCreateToken generates a new bootstrap token and stores it as a secret on the server.
func RunCreateToken(out io.Writer, client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error {
td := &kubeadmapi.TokenDiscovery{}
var err error
if len(token) == 0 {
err = tokenutil.GenerateToken(td)
var err error
token, err = tokenutil.GenerateToken()
if err != nil {
return err
}
} else {
td.ID, td.Secret, err = tokenutil.ParseToken(token)
}
if err != nil {
return err
_, _, err := tokenutil.ParseToken(token)
if err != nil {
return err
}
}
// TODO: Validate usages here so we don't allow something unsupported
err = tokenphase.CreateNewToken(client, td, tokenDuration, usages, description)
err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, description)
if err != nil {
return err
}
fmt.Fprintln(out, tokenutil.BearerToken(td))
fmt.Fprintln(out, token)
return nil
}
// RunGenerateToken just generates a random token for the user
func RunGenerateToken(out io.Writer) error {
td := &kubeadmapi.TokenDiscovery{}
err := tokenutil.GenerateToken(td)
token, err := tokenutil.GenerateToken()
if err != nil {
return err
}
fmt.Fprintln(out, tokenutil.BearerToken(td))
fmt.Fprintln(out, token)
return nil
}

View File

@ -57,7 +57,7 @@ const (
SchedulerKubeConfigFileName = "scheduler.conf"
// Important: a "v"-prefix shouldn't exist here; semver doesn't allow that
MinimumControlPlaneVersion = "1.6.0-alpha.2"
MinimumControlPlaneVersion = "1.6.0-beta.1"
// Some well-known users and groups in the core Kubernetes authorization system
@ -72,6 +72,8 @@ const (
// APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation
APICallRetryInterval = 500 * time.Millisecond
// DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery
DiscoveryRetryInterval = 5 * time.Second
// Minimum amount of nodes the Service subnet should allow.
// We need at least ten, because the DNS service is always at the tenth cluster clusterIP
@ -85,15 +87,6 @@ const (
// It's copied over to kubeadm until it's merged in core: https://github.com/kubernetes/kubernetes/pull/39112
LabelNodeRoleMaster = "node-role.kubernetes.io/master"
// CSVTokenBootstrapUser is currently the user the bootstrap token in the .csv file
// TODO: This should change to something more official and supported
// TODO: Prefix with kubeadm prefix
CSVTokenBootstrapUser = "kubeadm-node-csr"
// CSVTokenBootstrapGroup specifies the group the tokens in the .csv file will belong to
CSVTokenBootstrapGroup = "kubeadm:kubelet-bootstrap"
// The file name of the tokens file that can be used for bootstrapping
CSVTokenFileName = "tokens.csv"
// MinExternalEtcdVersion indicates minimum external etcd version which kubeadm supports
MinExternalEtcdVersion = "3.0.14"

View File

@ -10,36 +10,24 @@ load(
go_library(
name = "go_default_library",
srcs = [
"discovery.go",
"flags.go",
],
srcs = ["discovery.go"],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/discovery/file:go_default_library",
"//cmd/kubeadm/app/discovery/https:go_default_library",
"//cmd/kubeadm/app/discovery/token:go_default_library",
"//cmd/kubeadm/app/node:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//vendor:github.com/spf13/pflag",
"//vendor:k8s.io/client-go/tools/clientcmd",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
],
)
go_test(
name = "go_default_test",
srcs = [
"discovery_test.go",
"flags_test.go",
],
srcs = ["discovery_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew",
],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
)
filegroup(

View File

@ -18,27 +18,47 @@ package discovery
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/https"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/token"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
)
// For identifies and executes the desired discovery mechanism.
func For(d *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
const TokenUser = "tls-bootstrap-token-user"
// For returns a KubeConfig object that can be used for doing the TLS Bootstrap with the right credentials
// Also, before returning anything, it makes sure it can trust the API Server
func For(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
// TODO: Print summary info about the CA certificate, along with the the checksum signature
// we also need an ability for the user to configure the client to validate received CA cert against a checksum
clusterinfo, err := GetValidatedClusterInfoObject(cfg)
if err != nil {
return nil, fmt.Errorf("couldn't validate the identity of the API Server: %v", err)
}
return kubeconfigutil.CreateWithToken(
clusterinfo.Server,
"kubernetes",
TokenUser,
clusterinfo.CertificateAuthorityData,
cfg.TLSBootstrapToken,
), nil
}
// GetValidatedClusterInfoObject returns a validated Cluster object that specifies where the cluster is and the CA cert to trust
func GetValidatedClusterInfoObject(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) {
switch {
case len(d.DiscoveryFile) != 0:
if isHTTPSURL(d.DiscoveryFile) {
return runHTTPSDiscovery(d.DiscoveryFile)
case len(cfg.DiscoveryFile) != 0:
if isHTTPSURL(cfg.DiscoveryFile) {
return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
}
return runFileDiscovery(d.DiscoveryFile)
case len(d.DiscoveryToken) != 0:
return runTokenDiscovery(d.DiscoveryToken, d.DiscoveryTokenAPIServers)
return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
case len(cfg.DiscoveryToken) != 0:
return token.RetrieveValidatedClusterInfo(cfg.DiscoveryToken, cfg.DiscoveryTokenAPIServers)
default:
return nil, fmt.Errorf("couldn't find a valid discovery configuration.")
}
@ -49,48 +69,3 @@ func isHTTPSURL(s string) bool {
u, err := url.Parse(s)
return err == nil && u.Scheme == "https"
}
// runFileDiscovery executes file-based discovery.
func runFileDiscovery(fd string) (*clientcmdapi.Config, error) {
return clientcmd.LoadFromFile(fd)
}
// runHTTPSDiscovery executes HTTPS-based discovery.
func runHTTPSDiscovery(hd string) (*clientcmdapi.Config, error) {
response, err := http.Get(hd)
if err != nil {
return nil, err
}
defer response.Body.Close()
kubeconfig, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
return clientcmd.Load(kubeconfig)
}
// runTokenDiscovery executes token-based discovery.
func runTokenDiscovery(td string, m []string) (*clientcmdapi.Config, error) {
id, secret, err := tokenutil.ParseToken(td)
if err != nil {
return nil, err
}
t := &kubeadmapi.TokenDiscovery{ID: id, Secret: secret, Addresses: m}
if valid, err := tokenutil.ValidateToken(t); valid == false {
return nil, err
}
clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(t)
if err != nil {
return nil, err
}
cfg, err := kubenode.EstablishMasterConnection(t, clusterInfo)
if err != nil {
return nil, err
}
return cfg, nil
}

View File

@ -11,7 +11,17 @@ go_library(
name = "go_default_library",
srcs = ["file.go"],
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
],
)
filegroup(

View File

@ -17,13 +17,114 @@ limitations under the License.
package file
import (
"net/url"
"fmt"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
)
func Parse(u *url.URL, c *kubeadm.Discovery) {
c.File = &kubeadm.FileDiscovery{
Path: u.Path,
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
func RetrieveValidatedClusterInfo(filepath string) (*clientcmdapi.Cluster, error) {
clusterinfo, err := clientcmd.LoadFromFile(filepath)
if err != nil {
return nil, err
}
return ValidateClusterInfo(clusterinfo)
}
// ValidateClusterInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
func ValidateClusterInfo(clusterinfo *clientcmdapi.Config) (*clientcmdapi.Cluster, error) {
err := validateClusterInfoKubeConfig(clusterinfo)
if err != nil {
return nil, err
}
// This is the cluster object we've got from the cluster-info KubeConfig file
defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo)
// Create a new kubeconfig object from the given, just copy over the server and the CA cert
// We do this in order to not pick up other possible misconfigurations in the clusterinfo file
configFromClusterInfo := kubeconfigutil.CreateBasic(
defaultCluster.Server,
"kubernetes",
"", // no user provided
defaultCluster.CertificateAuthorityData,
)
client, err := kubeconfigutil.KubeConfigToClientSet(configFromClusterInfo)
if err != nil {
return nil, err
}
fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", defaultCluster.Server)
var clusterinfoCM *v1.ConfigMap
wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) {
var err error
clusterinfoCM, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{})
if err != nil {
if apierrors.IsForbidden(err) {
// If the request is unauthorized, the cluster admin has not granted access to the cluster info configmap for unauthenicated users
// In that case, trust the cluster admin and do not refresh the cluster-info credentials
fmt.Printf("[discovery] Could not access the %s ConfigMap for refreshing the cluster-info information, but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo)
return true, nil
} else {
fmt.Printf("[discovery] Failed to validate the API Server's identity, will try again: [%v]\n", err)
return false, nil
}
}
return true, nil
})
// If we couldn't fetch the cluster-info ConfigMap, just return the cluster-info object the user provided
if clusterinfoCM == nil {
return defaultCluster, nil
}
// We somehow got hold of the ConfigMap, try to read some data from it. If we can't, fallback on the user-provided file
refreshedBaseKubeConfig, err := tryParseClusterInfoFromConfigMap(clusterinfoCM)
if err != nil {
fmt.Printf("[discovery] The %s ConfigMap isn't set up properly (%v), but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo, err)
return defaultCluster, nil
}
fmt.Println("[discovery] Synced cluster-info information from the API Server so we have got the latest information")
// In an HA world in the future, this will make more sense, because now we've got new information, possibly about new API Servers to talk to
return kubeconfigutil.GetClusterFromKubeConfig(refreshedBaseKubeConfig), nil
}
// tryParseClusterInfoFromConfigMap tries to parse a kubeconfig file from a ConfigMap key
func tryParseClusterInfoFromConfigMap(cm *v1.ConfigMap) (*clientcmdapi.Config, error) {
kubeConfigString, ok := cm.Data[bootstrapapi.KubeConfigKey]
if !ok || len(kubeConfigString) == 0 {
return nil, fmt.Errorf("no %s key in ConfigMap", bootstrapapi.KubeConfigKey)
}
parsedKubeConfig, err := clientcmd.Load([]byte(kubeConfigString))
if err != nil {
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s ConfigMap: %v", bootstrapapi.ConfigMapClusterInfo, err)
}
return parsedKubeConfig, nil
}
// validateClusterInfoKubeConfig makes sure the user-provided cluster-info KubeConfig file is valid
func validateClusterInfoKubeConfig(clusterinfo *clientcmdapi.Config) error {
if len(clusterinfo.Clusters) < 1 {
return fmt.Errorf("the provided cluster-info KubeConfig file must have at least one Cluster defined")
}
defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo)
if defaultCluster == nil {
return fmt.Errorf("the provided cluster-info KubeConfig file must have an unnamed Cluster or a CurrentContext that specifies a non-nil Cluster")
}
return nil
}

View File

@ -1,95 +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 discovery
import (
"fmt"
"net/url"
"strings"
"github.com/spf13/pflag"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/https"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/token"
)
type discoveryValue struct {
v *kubeadm.Discovery
}
func NewDiscoveryValue(d *kubeadm.Discovery) pflag.Value {
return &discoveryValue{
v: d,
}
}
func (d *discoveryValue) String() string {
switch {
case d.v.HTTPS != nil:
return d.v.HTTPS.URL
case d.v.File != nil:
return "file://" + d.v.File.Path
case d.v.Token != nil:
return fmt.Sprintf("token://%s.%s@%s", d.v.Token.ID, d.v.Token.Secret, strings.Join(d.v.Token.Addresses, ","))
default:
return "unknown"
}
}
func (d *discoveryValue) Set(s string) error {
var kd kubeadm.Discovery
if err := ParseURL(&kd, s); err != nil {
return err
}
*d.v = kd
return nil
}
func (d *discoveryValue) Type() string {
return "discovery"
}
func ParseURL(d *kubeadm.Discovery, s string) error {
u, err := url.Parse(s)
if err != nil {
return err
}
switch u.Scheme {
case "https":
https.Parse(u, d)
return nil
case "file":
file.Parse(u, d)
return nil
case "token":
// Make sure a valid RFC 3986 URL has been passed and parsed.
// See https://github.com/kubernetes/kubeadm/issues/95#issuecomment-270431296 for more details.
if !strings.Contains(s, "@") {
s := s + "@"
u, err = url.Parse(s)
if err != nil {
return err
}
}
token.Parse(u, d)
return nil
default:
return fmt.Errorf("unknown discovery scheme")
}
}

View File

@ -1,191 +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 discovery
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestNewDiscoveryValue(t *testing.T) {
tests := []struct {
d *discoveryValue
expect string
}{
{
d: &discoveryValue{
v: &kubeadm.Discovery{}},
expect: "unknown",
},
{
d: &discoveryValue{
v: &kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "notnil"},
},
},
expect: "notnil",
},
{
d: &discoveryValue{
v: &kubeadm.Discovery{
File: &kubeadm.FileDiscovery{Path: "notnil"},
},
},
expect: "file://notnil",
},
{
d: &discoveryValue{
v: &kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "foo",
Secret: "bar",
Addresses: []string{"foobar"},
},
},
}, expect: "token://foo.bar@foobar",
},
}
for _, rt := range tests {
actual := rt.d.String()
if actual != rt.expect {
t.Errorf(
"failed discoveryValue string:\n\texpected: %s\n\t actual: %s",
rt.expect,
actual,
)
}
}
}
func TestType(t *testing.T) {
tests := []struct {
d *discoveryValue
expect string
}{
{d: &discoveryValue{}, expect: "discovery"},
}
for _, rt := range tests {
actual := rt.d.Type()
if actual != rt.expect {
t.Errorf(
"failed discoveryValue type:\n\texpected: %s\n\t actual: %s",
rt.expect,
actual,
)
}
}
}
func TestSet(t *testing.T) {
tests := []struct {
d *discoveryValue
s string
expect bool
}{
{d: &discoveryValue{v: &kubeadm.Discovery{}}, s: "", expect: false},
{d: &discoveryValue{v: &kubeadm.Discovery{}}, s: "https://example.com", expect: true},
}
for _, rt := range tests {
actual := rt.d.Set(rt.s)
if (actual == nil) != rt.expect {
t.Errorf(
"failed discoveryValue set:\n\texpected: %t\n\t actual: %t",
rt.expect,
(actual == nil),
)
}
}
}
func TestParseURL(t *testing.T) {
cases := []struct {
url string
expect kubeadm.Discovery
expectErr bool
}{
{
url: "token://",
expect: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{},
},
},
{
url: "token://c05de9:ab224260fb3cd718",
expect: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "c05de9",
Secret: "ab224260fb3cd718",
},
},
},
{
url: "token://c05de9:ab224260fb3cd718@",
expect: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "c05de9",
Secret: "ab224260fb3cd718",
},
},
},
{
url: "token://c05de9:ab224260fb3cd718@192.168.0.1:6555,191.168.0.2:6443",
expect: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "c05de9",
Secret: "ab224260fb3cd718",
Addresses: []string{
"192.168.0.1:6555",
"191.168.0.2:6443",
},
},
},
},
{
url: "file:///foo/bar/baz",
expect: kubeadm.Discovery{
File: &kubeadm.FileDiscovery{
Path: "/foo/bar/baz",
},
},
},
{
url: "https://storage.googleapis.com/kubeadm-disco/clusters/217651295213",
expect: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{
URL: "https://storage.googleapis.com/kubeadm-disco/clusters/217651295213",
},
},
},
}
for _, c := range cases {
var d kubeadm.Discovery
if err := ParseURL(&d, c.url); err != nil {
if !c.expectErr {
t.Errorf("unexpected error parsing discovery url: %v", err)
}
continue
}
if !reflect.DeepEqual(d, c.expect) {
t.Errorf("expected discovery config to be equal but got:\n\tactual: %s\n\texpected: %s", spew.Sdump(d), spew.Sdump(c.expect))
}
}
}

View File

@ -11,7 +11,11 @@ go_library(
name = "go_default_library",
srcs = ["https.go"],
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
deps = [
"//cmd/kubeadm/app/discovery/file:go_default_library",
"//vendor:k8s.io/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
],
)
filegroup(

View File

@ -17,13 +17,32 @@ limitations under the License.
package https
import (
"net/url"
"io/ioutil"
"net/http"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
)
func Parse(u *url.URL, c *kubeadm.Discovery) {
c.HTTPS = &kubeadm.HTTPSDiscovery{
URL: u.String(),
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
func RetrieveValidatedClusterInfo(httpsURL string) (*clientcmdapi.Cluster, error) {
response, err := http.Get(httpsURL)
if err != nil {
return nil, err
}
defer response.Body.Close()
kubeconfig, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
clusterinfo, err := clientcmd.Load(kubeconfig)
if err != nil {
return nil, err
}
return file.ValidateClusterInfo(clusterinfo)
}

View File

@ -5,13 +5,25 @@ licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["token.go"],
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//pkg/controller/bootstrap:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
],
)
filegroup(
@ -26,3 +38,14 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["token_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
],
)

View File

@ -17,29 +17,117 @@ limitations under the License.
package token
import (
"net/url"
"strings"
"fmt"
"sync"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
"k8s.io/kubernetes/pkg/controller/bootstrap"
)
func Parse(u *url.URL, c *kubeadm.Discovery) {
var (
hosts []string
tokenID, token string
)
if u.Host != "" {
hosts = strings.Split(u.Host, ",")
const BootstrapUser = "token-bootstrap-client"
// RetrieveValidatedClusterInfo connects to the API Server and tries to fetch the cluster-info ConfigMap
// It then makes sure it can trust the API Server by looking at the JWS-signed tokens
func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers []string) (*clientcmdapi.Cluster, error) {
tokenId, tokenSecret, err := tokenutil.ParseToken(discoveryToken)
if err != nil {
return nil, err
}
if u.User != nil {
if p, ok := u.User.Password(); ok {
tokenID = u.User.Username()
token = p
// The function below runs for every endpoint, and all endpoints races with each other.
// The endpoint that wins the race and completes the task first gets its kubeconfig returned below
baseKubeConfig := runForEndpointsAndReturnFirst(tokenAPIServers, func(endpoint string) (*clientcmdapi.Config, error) {
bootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint)
clusterName := bootstrapConfig.Contexts[bootstrapConfig.CurrentContext].Cluster
client, err := kubeconfigutil.KubeConfigToClientSet(bootstrapConfig)
if err != nil {
return nil, err
}
}
c.Token = &kubeadm.TokenDiscovery{
ID: tokenID,
Secret: token,
Addresses: hosts,
}
fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", bootstrapConfig.Clusters[clusterName].Server)
var clusterinfo *v1.ConfigMap
wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) {
var err error
clusterinfo, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{})
if err != nil {
fmt.Printf("[discovery] Failed to request cluster info, will try again: [%s]\n", err)
return false, nil
}
return true, nil
})
kubeConfigString, ok := clusterinfo.Data[bootstrapapi.KubeConfigKey]
if !ok || len(kubeConfigString) == 0 {
return nil, fmt.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect", bootstrapapi.KubeConfigKey, bootstrapapi.ConfigMapClusterInfo)
}
detachedJWSToken, ok := clusterinfo.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenId]
if !ok || len(detachedJWSToken) == 0 {
return nil, fmt.Errorf("there is no JWS signed token in the %s ConfigMap. This token id %q is invalid for this cluster, can't connect", bootstrapapi.ConfigMapClusterInfo, tokenId)
}
if !bootstrap.DetachedTokenIsValid(detachedJWSToken, kubeConfigString, tokenId, tokenSecret) {
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server")
}
finalConfig, err := clientcmd.Load([]byte(kubeConfigString))
if err != nil {
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s configmap: %v", bootstrapapi.ConfigMapClusterInfo, err)
}
fmt.Printf("[discovery] Cluster info signature and contents are valid, will use API Server %q\n", bootstrapConfig.Clusters[clusterName].Server)
return finalConfig, nil
})
return kubeconfigutil.GetClusterFromKubeConfig(baseKubeConfig), nil
}
// buildInsecureBootstrapKubeConfig makes a KubeConfig object that connects insecurely to the API Server for bootstrapping purposes
func buildInsecureBootstrapKubeConfig(endpoint string) *clientcmdapi.Config {
masterEndpoint := fmt.Sprintf("https://%s", endpoint)
clusterName := "kubernetes"
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clusterName, BootstrapUser, []byte{})
bootstrapConfig.Clusters[clusterName].InsecureSkipTLSVerify = true
return bootstrapConfig
}
// runForEndpointsAndReturnFirst loops the endpoints slice and let's the endpoints race for connecting to the master
func runForEndpointsAndReturnFirst(endpoints []string, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) *clientcmdapi.Config {
stopChan := make(chan struct{})
var resultingKubeConfig *clientcmdapi.Config
var once sync.Once
var wg sync.WaitGroup
for _, endpoint := range endpoints {
wg.Add(1)
go func(apiEndpoint string) {
defer wg.Done()
wait.Until(func() {
fmt.Printf("[discovery] Trying to connect to API Server %q\n", apiEndpoint)
cfg, err := fetchKubeConfigFunc(apiEndpoint)
if err != nil {
fmt.Printf("[discovery] Failed to connect to API Server %q: %v\n", apiEndpoint, err)
return
}
fmt.Printf("[discovery] Successfully established connection with API Server %q\n", apiEndpoint)
// connection established, stop all wait threads
once.Do(func() {
close(stopChan)
resultingKubeConfig = cfg
})
}, constants.DiscoveryRetryInterval, stopChan)
}(endpoint)
}
wg.Wait()
return resultingKubeConfig
}

View File

@ -0,0 +1,61 @@
/*
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 token
import (
"strconv"
"testing"
"time"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
)
func TestRunForEndpointsAndReturnFirst(t *testing.T) {
tests := []struct {
endpoints []string
expectedEndpoint string
}{
{
endpoints: []string{"1", "2", "3"},
expectedEndpoint: "1",
},
{
endpoints: []string{"6", "5"},
expectedEndpoint: "5",
},
{
endpoints: []string{"10", "4"},
expectedEndpoint: "4",
},
}
for _, rt := range tests {
returnKubeConfig := runForEndpointsAndReturnFirst(rt.endpoints, func(endpoint string) (*clientcmdapi.Config, error) {
timeout, _ := strconv.Atoi(endpoint)
time.Sleep(time.Second * time.Duration(timeout))
return kubeconfigutil.CreateBasic(endpoint, "foo", "foo", []byte{}), nil
})
endpoint := returnKubeConfig.Clusters[returnKubeConfig.Contexts[returnKubeConfig.CurrentContext].Cluster].Server
if endpoint != rt.expectedEndpoint {
t.Errorf(
"failed TestRunForEndpointsAndReturnFirst:\n\texpected: %s\n\t actual: %s",
endpoint,
rt.expectedEndpoint,
)
}
}
}

View File

@ -12,7 +12,6 @@ go_library(
name = "go_default_library",
srcs = [
"apiclient.go",
"discovery.go",
"manifests.go",
"selfhosted.go",
"templates.go",
@ -24,6 +23,7 @@ go_library(
"//cmd/kubeadm/app/images:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//vendor:github.com/ghodss/yaml",
@ -37,7 +37,6 @@ go_library(
"//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/pkg/apis/extensions/v1beta1",
"//vendor:k8s.io/client-go/util/cert",
],
)

View File

@ -1,126 +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 master
import (
"crypto/x509"
"encoding/json"
"fmt"
"path"
"runtime"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kuberuntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
const (
kubeDiscoverySecretName = "clusterinfo"
kubeDiscoveryName = "kube-discovery"
)
// TODO: Remove this file as soon as jbeda's token discovery refactoring PR has merged
func encodeKubeDiscoverySecretData(dcfg *kubeadmapi.TokenDiscovery, apicfg kubeadmapi.API, caCert *x509.Certificate) map[string][]byte {
var (
data = map[string][]byte{}
tokenMap = map[string]string{}
)
tokenMap[dcfg.ID] = dcfg.Secret
data["endpoint-list.json"], _ = json.Marshal([]string{fmt.Sprintf("https://%s:%d", apicfg.AdvertiseAddress, apicfg.BindPort)})
data["token-map.json"], _ = json.Marshal(tokenMap)
data["ca.pem"] = certutil.EncodeCertPEM(caCert)
return data
}
func CreateDiscoveryDeploymentAndSecret(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error {
caCertificatePath := path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)
caCerts, err := certutil.CertsFromFile(caCertificatePath)
if err != nil {
return fmt.Errorf("couldn't load the CA certificate file %s: %v", caCertificatePath, err)
}
// 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]
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: kubeDiscoverySecretName},
Type: v1.SecretTypeOpaque,
Data: encodeKubeDiscoverySecretData(cfg.Discovery.Token, cfg.API, caCert),
}
if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err != nil {
return fmt.Errorf("failed to create %q secret [%v]", kubeDiscoverySecretName, err)
}
if err := createDiscoveryDeployment(client); err != nil {
return err
}
fmt.Println("[token-discovery] Created the kube-discovery deployment, waiting for it to become ready")
start := time.Now()
wait.PollInfinite(kubeadmconstants.APICallRetryInterval, func() (bool, error) {
d, err := client.Extensions().Deployments(metav1.NamespaceSystem).Get(kubeDiscoveryName, metav1.GetOptions{})
if err != nil {
return false, nil
}
if d.Status.AvailableReplicas < 1 {
return false, nil
}
return true, nil
})
fmt.Printf("[token-discovery] kube-discovery is ready after %f seconds\n", time.Since(start).Seconds())
return nil
}
func createDiscoveryDeployment(client *clientset.Clientset) error {
discoveryBytes, err := kubeadmutil.ParseTemplate(KubeDiscoveryDeployment, struct{ ImageRepository, Arch, MasterTaintKey string }{
ImageRepository: kubeadmapi.GlobalEnvParams.RepositoryPrefix,
Arch: runtime.GOARCH,
MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster,
})
if err != nil {
return fmt.Errorf("error when parsing kube-discovery template: %v", err)
}
discoveryDeployment := &extensions.Deployment{}
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), discoveryBytes, discoveryDeployment); err != nil {
return fmt.Errorf("unable to decode kube-discovery deployment %v", err)
}
// TODO: Set this in the yaml spec instead
discoveryDeployment.Spec.Template.Spec.Tolerations = []v1.Toleration{kubeadmconstants.MasterToleration}
if _, err := client.ExtensionsV1beta1().Deployments(metav1.NamespaceSystem).Create(discoveryDeployment); err != nil {
return fmt.Errorf("unable to create a new discovery deployment: %v", err)
}
return nil
}

View File

@ -32,6 +32,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/images"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
@ -301,20 +302,20 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
}
defaultArguments := map[string]string{
"insecure-port": "0",
"admission-control": kubeadmconstants.DefaultAdmissionControl,
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
"kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
"token-auth-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CSVTokenFileName),
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
"allow-privileged": "true",
"storage-backend": "etcd3",
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
"insecure-port": "0",
"admission-control": kubeadmconstants.DefaultAdmissionControl,
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
"kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
"allow-privileged": "true",
"experimental-bootstrap-token-auth": "true",
"storage-backend": "etcd3",
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
// add options to configure the front proxy. Without the generated client cert, this will never be useable
// so add it unconditionally with recommended values
"requestheader-username-headers": "X-Remote-User",
@ -379,8 +380,9 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted
"service-account-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
"cluster-signing-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"cluster-signing-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
"insecure-experimental-approve-all-kubelet-csrs-for-group": kubeadmconstants.CSVTokenBootstrapGroup,
"insecure-experimental-approve-all-kubelet-csrs-for-group": bootstrapapi.BootstrapGroup,
"use-service-account-credentials": "true",
"controllers": "*,bootstrapsigner,tokencleaner",
}
command = getComponentBaseCommand(controllerManager)

View File

@ -397,11 +397,11 @@ func TestGetAPIServerCommand(t *testing.T) {
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
"--token-auth-file=" + testCertsDir + "/tokens.csv",
fmt.Sprintf("--secure-port=%d", 123),
"--allow-privileged=true",
"--storage-backend=etcd3",
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
"--experimental-bootstrap-token-auth=true",
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
@ -429,11 +429,11 @@ func TestGetAPIServerCommand(t *testing.T) {
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
"--token-auth-file=" + testCertsDir + "/tokens.csv",
fmt.Sprintf("--secure-port=%d", 123),
"--allow-privileged=true",
"--storage-backend=etcd3",
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
"--experimental-bootstrap-token-auth=true",
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
@ -462,11 +462,11 @@ func TestGetAPIServerCommand(t *testing.T) {
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
"--token-auth-file=" + testCertsDir + "/tokens.csv",
fmt.Sprintf("--secure-port=%d", 123),
"--allow-privileged=true",
"--storage-backend=etcd3",
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
"--experimental-bootstrap-token-auth=true",
"--requestheader-username-headers=X-Remote-User",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
@ -509,8 +509,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers",
"--use-service-account-credentials=true",
"--controllers=*,bootstrapsigner,tokencleaner",
},
},
{
@ -527,8 +528,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers",
"--use-service-account-credentials=true",
"--controllers=*,bootstrapsigner,tokencleaner",
"--cloud-provider=foo",
},
},
@ -546,8 +548,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers",
"--use-service-account-credentials=true",
"--controllers=*,bootstrapsigner,tokencleaner",
"--allocate-node-cidrs=true",
"--cluster-cidr=bar",
},

View File

@ -39,62 +39,5 @@ spec:
- image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.0
name: dummy
hostNetwork: true
`
KubeDiscoveryDeployment = `
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
k8s-app: kube-discovery
name: kube-discovery
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: kube-discovery
template:
metadata:
labels:
k8s-app: kube-discovery
# TODO: I guess we can remove all these cluster-service labels...
kubernetes.io/cluster-service: "true"
spec:
containers:
- name: kube-discovery
image: {{ .ImageRepository }}/kube-discovery-{{ .Arch }}:1.0
imagePullPolicy: IfNotPresent
command:
- /usr/local/bin/kube-discovery
ports:
- containerPort: 9898
hostPort: 9898
name: http
volumeMounts:
- mountPath: /tmp/secret
name: clusterinfo
readOnly: true
hostNetwork: true
# TODO: Why doesn't the Decoder recognize this new field and decode it properly? Right now it's ignored
# tolerations:
# - key: {{ .MasterTaintKey }}
# effect: NoSchedule
securityContext:
seLinuxOptions:
type: spc_t
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/arch
operator: In
values:
- {{ .Arch }}
volumes:
- name: clusterinfo
secret:
defaultMode: 420
secretName: clusterinfo
`
)

View File

@ -11,22 +11,16 @@ load(
go_library(
name = "go_default_library",
srcs = [
"bootstrap.go",
"csr.go",
"discovery.go",
"validate.go",
],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/kubelet/util/csr:go_default_library",
"//vendor:github.com/square/go-jose",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/kubernetes",
"//vendor:k8s.io/client-go/pkg/apis/certificates",
"//vendor:k8s.io/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/pkg/apis/certificates/v1beta1",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
"//vendor:k8s.io/client-go/util/cert",
],
@ -34,14 +28,10 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"bootstrap_test.go",
"discovery_test.go",
],
srcs = ["validate_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/version",
"//vendor:k8s.io/client-go/discovery",

View File

@ -1,146 +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 node
import (
"fmt"
"os"
"sync"
"time"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/apis/certificates"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
)
// retryTimeout between the subsequent attempts to connect
// to an API endpoint
const retryTimeout = 5
type apiClient struct {
clientSet *clientset.Clientset
clientConfig *clientcmdapi.Config
}
// EstablishMasterConnection establishes a connection with exactly one of the provided API endpoints.
// The function builds a client for every endpoint and concurrently keeps trying to connect to any one
// of the provided endpoints. Blocks until at least one connection is established, then it stops the
// connection attempts for other endpoints and returns the valid client configuration, if any.
func EstablishMasterConnection(c *kubeadmapi.TokenDiscovery, clusterInfo *kubeadmapi.ClusterInfo) (*clientcmdapi.Config, error) {
hostName, err := os.Hostname()
if err != nil {
return nil, fmt.Errorf("failed to get node hostname [%v]", err)
}
// TODO(phase1+) https://github.com/kubernetes/kubernetes/issues/33641
nodeName := types.NodeName(hostName)
endpoints := clusterInfo.Endpoints
caCert := []byte(clusterInfo.CertificateAuthorities[0])
stopChan := make(chan struct{})
var clientConfig *clientcmdapi.Config
var once sync.Once
var wg sync.WaitGroup
for _, endpoint := range endpoints {
ac, err := createClients(caCert, endpoint, tokenutil.BearerToken(c), nodeName)
if err != nil {
fmt.Printf("[bootstrap] Warning: %s. Skipping endpoint %s\n", err, endpoint)
continue
}
wg.Add(1)
go func(apiEndpoint string) {
defer wg.Done()
wait.Until(func() {
fmt.Printf("[bootstrap] Trying to connect to endpoint %s\n", apiEndpoint)
err := checkAPIEndpoint(ac.clientSet, apiEndpoint)
if err != nil {
fmt.Printf("[bootstrap] Endpoint check failed [%v]\n", err)
return
}
fmt.Printf("[bootstrap] Successfully established connection with endpoint %q\n", apiEndpoint)
// connection established, stop all wait threads
once.Do(func() {
close(stopChan)
clientConfig = ac.clientConfig
})
}, retryTimeout*time.Second, stopChan)
}(endpoint)
}
wg.Wait()
if clientConfig == nil {
return nil, fmt.Errorf("failed to create bootstrap clients for any of the provided API endpoints")
}
return clientConfig, nil
}
// creates a set of clients for this endpoint
func createClients(caCert []byte, endpoint, token string, nodeName types.NodeName) (*apiClient, error) {
clientConfig := kubeconfigutil.CreateWithToken(
endpoint,
"kubernetes",
fmt.Sprintf("kubelet-%s", nodeName),
caCert,
token,
)
bootstrapClientConfig, err := clientcmd.NewDefaultClientConfig(*clientConfig, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create API client configuration [%v]", err)
}
clientSet, err := clientset.NewForConfig(bootstrapClientConfig)
if err != nil {
return nil, fmt.Errorf("failed to create clients for the API endpoint %q: [%v]", endpoint, err)
}
ac := &apiClient{
clientSet: clientSet,
clientConfig: clientConfig,
}
return ac, nil
}
// checks the connection requirements for a specific API endpoint
func checkAPIEndpoint(clientSet *clientset.Clientset, endpoint string) error {
// check general connectivity
version, err := clientSet.DiscoveryClient.ServerVersion()
if err != nil {
return fmt.Errorf("failed to connect to %q [%v]", endpoint, err)
}
fmt.Printf("[bootstrap] Detected server version: %s\n", version.String())
// check certificates API
serverGroups, err := clientSet.DiscoveryClient.ServerGroups()
if err != nil {
return fmt.Errorf("certificate API check failed: failed to retrieve a list of supported API objects [%v]", err)
}
for _, group := range serverGroups.Groups {
if group.Name == certificates.GroupName {
return nil
}
}
return fmt.Errorf("certificate API check failed: API version %s does not support certificates API, use v1.4.0 or newer",
version.String())
}

View File

@ -18,32 +18,23 @@ package node
import (
"fmt"
"os"
"k8s.io/apimachinery/pkg/types"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
certutil "k8s.io/client-go/util/cert"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/kubelet/util/csr"
)
// PerformTLSBootstrap executes a node certificate signing request.
func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
hostName, err := os.Hostname()
if err != nil {
return err
}
name := types.NodeName(hostName)
const CSRContextAndUser = "kubelet-csr"
rc, err := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return err
}
c, err := clientset.NewForConfig(rc)
// PerformTLSBootstrap executes a node certificate signing request.
func PerformTLSBootstrap(cfg *clientcmdapi.Config, hostName string) error {
client, err := kubeconfigutil.KubeConfigToClientSet(cfg)
if err != nil {
return err
}
fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request")
key, err := certutil.MakeEllipticPrivateKeyPEM()
@ -51,21 +42,20 @@ func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
return fmt.Errorf("failed to generate private key [%v]", err)
}
cert, err := csr.RequestNodeCertificate(c.Certificates().CertificateSigningRequests(), key, name)
cert, err := csr.RequestNodeCertificate(client.CertificatesV1beta1().CertificateSigningRequests(), key, types.NodeName(hostName))
if err != nil {
return fmt.Errorf("failed to request signed certificate from the API server [%v]", err)
}
fmt.Printf("[csr] Received signed certificate from the API server")
fmt.Println("[csr] Generating kubelet configuration")
fmt.Println("[csr] Received signed certificate from the API server, generating KubeConfig...")
cfg.AuthInfos["kubelet"] = &clientcmdapi.AuthInfo{
cfg.AuthInfos[CSRContextAndUser] = &clientcmdapi.AuthInfo{
ClientKeyData: key,
ClientCertificateData: cert,
}
cfg.Contexts["kubelet"] = &clientcmdapi.Context{
AuthInfo: "kubelet",
cfg.Contexts[CSRContextAndUser] = &clientcmdapi.Context{
AuthInfo: CSRContextAndUser,
Cluster: cfg.Contexts[cfg.CurrentContext].Cluster,
}
cfg.CurrentContext = "kubelet"
cfg.CurrentContext = CSRContextAndUser
return nil
}

View File

@ -1,87 +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 node
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
jose "github.com/square/go-jose"
"k8s.io/apimachinery/pkg/util/wait"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
// the amount of time to wait between each request to the discovery API
const discoveryRetryTimeout = 5 * time.Second
func RetrieveTrustedClusterInfo(d *kubeadmapi.TokenDiscovery) (*kubeadmapi.ClusterInfo, error) {
if len(d.Addresses) == 0 {
return nil, fmt.Errorf("the address is required to generate the requestURL")
}
requestURL := fmt.Sprintf("http://%s/cluster-info/v1/?token-id=%s", d.Addresses[0], d.ID)
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to construct an HTTP request [%v]", err)
}
fmt.Printf("[discovery] Created cluster info discovery client, requesting info from %q\n", requestURL)
var res *http.Response
wait.PollInfinite(discoveryRetryTimeout, func() (bool, error) {
res, err = http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("[discovery] Failed to request cluster info, will try again: [%s]\n", err)
return false, nil
}
return true, nil
})
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
object, err := jose.ParseSigned(buf.String())
if err != nil {
return nil, fmt.Errorf("failed to parse response as JWS object [%v]", err)
}
fmt.Println("[discovery] Cluster info object received, verifying signature using given token")
output, err := object.Verify([]byte(d.Secret))
if err != nil {
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object [%v]", err)
}
clusterInfo := kubeadmapi.ClusterInfo{}
if err := json.Unmarshal(output, &clusterInfo); err != nil {
return nil, fmt.Errorf("failed to decode received cluster info object [%v]", err)
}
if len(clusterInfo.CertificateAuthorities) == 0 || len(clusterInfo.Endpoints) == 0 {
return nil, fmt.Errorf("cluster info object is invalid - no endpoint(s) and/or root CA certificate(s) found")
}
// TODO(phase1+) print summary info about the CA certificate, along with the the checksum signature
// we also need an ability for the user to configure the client to validate received CA cert against a checksum
fmt.Printf("[discovery] Cluster info signature and contents are valid, will use API endpoints %v\n", clusterInfo.Endpoints)
return &clusterInfo, nil
}

View File

@ -1,99 +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 node
import (
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
type rawJsonWebSignatureFake struct {
Payload string `json:"payload,omitempty"`
Signatures string `json:"signatures,omitempty"`
Protected string `json:"protected,omitempty"`
Header string `json:"header,omitempty"`
Signature string `json:"signature,omitempty"`
}
func TestRetrieveTrustedClusterInfo(t *testing.T) {
j := rawJsonWebSignatureFake{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
default:
output, err := json.Marshal(j)
if err != nil {
t.Errorf("unexpected encoding error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(output)
}
}))
defer srv.Close()
pURL, err := url.Parse(srv.URL)
if err != nil {
t.Fatalf("encountered an error while trying to parse httptest server url: %v", err)
}
host, port, err := net.SplitHostPort(pURL.Host)
if err != nil {
t.Fatalf("encountered an error while trying to split host and port info from httptest server: %v", err)
}
iPort, err := strconv.Atoi(port)
if err != nil {
t.Fatalf("encountered an error while trying to convert string to int (httptest server port): %v", err)
}
tests := []struct {
h string
p int
payload string
expect bool
}{
{
h: host,
p: iPort,
payload: "",
expect: false,
},
{
h: host,
p: iPort,
payload: "foo",
expect: false,
},
}
for _, rt := range tests {
j.Payload = rt.payload
nc := &kubeadmapi.TokenDiscovery{Addresses: []string{rt.h + ":" + strconv.Itoa(rt.p)}}
_, actual := RetrieveTrustedClusterInfo(nc)
if (actual == nil) != rt.expect {
t.Errorf(
"failed createClients:\n\texpected: %t\n\t actual: %t",
rt.expect,
(actual == nil),
)
}
}
}

View File

@ -0,0 +1,51 @@
/*
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 node
import (
"fmt"
clientset "k8s.io/client-go/kubernetes"
certsapi "k8s.io/client-go/pkg/apis/certificates/v1beta1"
)
// ValidateAPIServer makes sure the server we're connecting to supports the Beta Certificates API
func ValidateAPIServer(client *clientset.Clientset) error {
version, err := client.DiscoveryClient.ServerVersion()
if err != nil {
return fmt.Errorf("failed to check server version: %v", err)
}
fmt.Printf("[bootstrap] Detected server version: %s\n", version.String())
// Check certificates API. If the server supports the version of the Certificates API we're using, we're good to go
serverGroups, err := client.DiscoveryClient.ServerGroups()
if err != nil {
return fmt.Errorf("certificate API check failed: failed to retrieve a list of supported API objects [%v]", err)
}
for _, group := range serverGroups.Groups {
if group.Name == certsapi.SchemeGroupVersion.Group {
for _, version := range group.Versions {
if version.Version == certsapi.SchemeGroupVersion.Version {
fmt.Printf("[bootstrap] The server supports the Certificates API (%s/%s)\n", certsapi.SchemeGroupVersion.Group, certsapi.SchemeGroupVersion.Version)
return nil
}
}
}
}
return fmt.Errorf("certificate API check failed: API server with version %s doesn't support Certificates API (%s/%s), use v1.6.0 or newer",
version.String(), certsapi.SchemeGroupVersion.Group, certsapi.SchemeGroupVersion.Version)
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016 The Kubernetes Authors.
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.
@ -27,150 +27,9 @@ import (
"k8s.io/client-go/discovery"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestEstablishMasterConnection(t *testing.T) {
srv := stubServer(t)
defer srv.Close()
tests := []struct {
c string
e string
expect bool
}{
{
c: "",
e: "",
expect: false,
},
{
c: "",
e: srv.URL,
expect: true,
},
{
c: "foo",
e: srv.URL,
expect: true,
},
}
for _, rt := range tests {
s := &kubeadmapi.TokenDiscovery{}
c := &kubeadmapi.ClusterInfo{Endpoints: []string{rt.e}, CertificateAuthorities: []string{rt.c}}
_, actual := EstablishMasterConnection(s, c)
if (actual == nil) != rt.expect {
t.Errorf(
"failed EstablishMasterConnection:\n\texpected: %t\n\t actual: %t",
rt.expect,
(actual == nil),
)
}
}
}
func TestEstablishMasterConnectionWithMultipleEndpoints(t *testing.T) {
// ref. https://github.com/kubernetes/kubernetes/issues/36988
srv := stubServer(t)
defer srv.Close()
s := &kubeadmapi.TokenDiscovery{}
c := &kubeadmapi.ClusterInfo{Endpoints: []string{srv.URL, srv.URL}, CertificateAuthorities: []string{"foo"}}
defer func() {
if r := recover(); r != nil {
t.Errorf("failed EstablishMasterConnectionWithMultipleEndpoints; got a panic.")
}
}()
EstablishMasterConnection(s, c)
}
func stubServer(t *testing.T) *httptest.Server {
expect := version.Info{
Major: "foo",
Minor: "bar",
GitCommit: "baz",
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var obj interface{}
switch req.URL.Path {
case "/api":
obj = &metav1.APIVersions{
Versions: []string{
"v1.4",
},
}
output, err := json.Marshal(obj)
if err != nil {
t.Fatalf("unexpected encoding error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(output)
case "/apis":
obj = &metav1.APIGroupList{
Groups: []metav1.APIGroup{
{
Name: "certificates.k8s.io",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "extensions/v1beta1"},
},
},
},
}
output, err := json.Marshal(obj)
if err != nil {
t.Fatalf("unexpected encoding error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(output)
default:
output, err := json.Marshal(expect)
if err != nil {
t.Errorf("unexpected encoding error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(output)
}
}))
return srv
}
func TestCreateClients(t *testing.T) {
tests := []struct {
e string
expect bool
}{
{
e: "",
expect: false,
},
{
e: "foo",
expect: true,
},
}
for _, rt := range tests {
_, actual := createClients(nil, rt.e, "", "")
if (actual == nil) != rt.expect {
t.Errorf(
"failed createClients:\n\texpected: %t\n\t actual: %t",
rt.expect,
(actual == nil),
)
}
}
}
func TestCheckAPIEndpoint(t *testing.T) {
func TestValidateAPIServer(t *testing.T) {
expect := version.Info{
Major: "foo",
Minor: "bar",
@ -191,7 +50,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
case "/api":
obj = &metav1.APIVersions{
Versions: []string{
"v1.4",
"v1.6.0",
},
}
output, err := json.Marshal(obj)
@ -222,7 +81,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
case "/api":
obj = &metav1.APIVersions{
Versions: []string{
"v1.4",
"v1.6.0",
},
}
output, err := json.Marshal(obj)
@ -239,7 +98,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
{
Name: "certificates.k8s.io",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "extensions/v1beta1"},
{GroupVersion: "certificates.k8s.io/v1beta1", Version: "v1beta1"},
},
},
},
@ -271,13 +130,13 @@ func TestCheckAPIEndpoint(t *testing.T) {
rc := &restclient.Config{Host: rt.s.URL}
c, err := discovery.NewDiscoveryClientForConfig(rc)
if err != nil {
t.Fatalf("encountered an error while trying to get New Discovery Client: %v", err)
t.Fatalf("encountered an error while trying to get the new discovery client: %v", err)
}
cs := &clientset.Clientset{DiscoveryClient: c}
actual := checkAPIEndpoint(cs, "")
actual := ValidateAPIServer(cs)
if (actual == nil) != rt.expect {
t.Errorf(
"failed runChecks:\n\texpected: %t\n\t actual: %t",
"failed TestValidateAPIServer:\n\texpected: %t\n\t actual: %t",
rt.expect,
(actual == nil),
)

View File

@ -16,6 +16,7 @@ go_library(
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/types",

View File

@ -24,33 +24,35 @@ import (
"k8s.io/client-go/pkg/api/v1"
rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
)
const (
// TODO: This role should eventually be a system:-prefixed, automatically bootstrapped ClusterRole
// KubeDNSClusterRoleName sets the name for the kube-dns ClusterRole
KubeDNSClusterRoleName = "kubeadm:kube-dns"
// KubeProxyClusterRoleName sets the name for the kube-proxy ClusterRole
KubeProxyClusterRoleName = "system:node-proxier"
// NodeBootstrapperClusterRoleName sets the name for the TLS Node Bootstrapper ClusterRole
NodeBootstrapperClusterRoleName = "system:node-bootstrapper"
// BootstrapSignerClusterRoleName sets the name for the ClusterRole that allows access to ConfigMaps in the kube-public ns
BootstrapSignerClusterRoleName = "system:bootstrap-signer-clusterinfo"
// Constants
clusterRoleKind = "ClusterRole"
roleKind = "Role"
serviceAccountKind = "ServiceAccount"
rbacAPIGroup = "rbac.authorization.k8s.io"
anonymousUser = "system:anonymous"
)
// TODO: Are there any unit tests that could be made for this file other than duplicating all values and logic in a separate file?
// CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster
func CreateRBACRules(clientset *clientset.Clientset) error {
// Create the ClusterRoles we need for our RBAC rules
if err := CreateClusterRoles(clientset); err != nil {
if err := CreateRoles(clientset); err != nil {
return err
}
if err := CreateRoleBindings(clientset); err != nil {
return err
}
// Create the CreateClusterRoleBindings we need for our RBAC rules
if err := CreateClusterRoleBindings(clientset); err != nil {
return err
}
@ -84,19 +86,53 @@ func CreateServiceAccounts(clientset *clientset.Clientset) error {
return nil
}
// CreateClusterRoles creates the ClusterRoles that aren't bootstrapped by the apiserver
func CreateClusterRoles(clientset *clientset.Clientset) error {
// TODO: Remove this ClusterRole when it's automatically bootstrapped in the apiserver
clusterRole := rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: KubeDNSClusterRoleName},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups("").Resources("endpoints", "services").RuleOrDie(),
// TODO: remove watch rule when https://github.com/kubernetes/kubernetes/pull/38816 gets merged
rbac.NewRule("get", "list", "watch").Groups("").Resources("configmaps").RuleOrDie(),
// CreateRoles creates namespaces RBAC Roles
func CreateRoles(clientset *clientset.Clientset) error {
roles := []rbac.Role{
{
ObjectMeta: metav1.ObjectMeta{
Name: BootstrapSignerClusterRoleName,
Namespace: metav1.NamespacePublic,
},
Rules: []rbac.PolicyRule{
rbac.NewRule("get").Groups("").Resources("configmaps").RuleOrDie(),
},
},
}
if _, err := clientset.Rbac().ClusterRoles().Create(&clusterRole); err != nil {
return err
for _, role := range roles {
if _, err := clientset.RbacV1beta1().Roles(metav1.NamespacePublic).Create(&role); err != nil {
return err
}
}
return nil
}
// CreateRoleBindings creates all namespaced and necessary bindings between bootstrapped & kubeadm-created ClusterRoles and subjects kubeadm is using
func CreateRoleBindings(clientset *clientset.Clientset) error {
roleBindings := []rbac.RoleBinding{
{
ObjectMeta: metav1.ObjectMeta{
Name: "kubeadm:bootstrap-signer-clusterinfo",
Namespace: metav1.NamespacePublic,
},
RoleRef: rbac.RoleRef{
APIGroup: rbacAPIGroup,
Kind: roleKind,
Name: BootstrapSignerClusterRoleName,
},
Subjects: []rbac.Subject{
{
Kind: "User",
Name: anonymousUser,
},
},
},
}
for _, roleBinding := range roleBindings {
if _, err := clientset.RbacV1beta1().RoleBindings(metav1.NamespacePublic).Create(&roleBinding); err != nil {
return err
}
}
return nil
}
@ -116,24 +152,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
Subjects: []rbac.Subject{
{
Kind: "Group",
Name: kubeadmconstants.CSVTokenBootstrapGroup,
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "kubeadm:kube-dns",
},
RoleRef: rbac.RoleRef{
APIGroup: rbacAPIGroup,
Kind: clusterRoleKind,
Name: KubeDNSClusterRoleName,
},
Subjects: []rbac.Subject{
{
Kind: serviceAccountKind,
Name: kubeadmconstants.KubeDNSServiceAccountName,
Namespace: metav1.NamespaceSystem,
Name: bootstrapapi.BootstrapGroup,
},
},
},
@ -157,7 +176,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
}
for _, clusterRoleBinding := range clusterRoleBindings {
if _, err := clientset.Rbac().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
if _, err := clientset.RbacV1beta1().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
return err
}
}

View File

@ -10,10 +10,7 @@ load(
go_test(
name = "go_default_test",
srcs = [
"bootstrap_test.go",
"csv_test.go",
],
srcs = ["bootstrap_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
@ -21,22 +18,18 @@ go_test(
go_library(
name = "go_default_library",
srcs = [
"bootstrap.go",
"csv.go",
],
srcs = ["bootstrap.go"],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/util/uuid",
"//vendor:k8s.io/client-go/kubernetes",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/tools/clientcmd/api",
],
)

View File

@ -24,7 +24,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
)
@ -32,26 +34,26 @@ import (
const tokenCreateRetries = 5
// CreateNewToken tries to create a token and fails if one with the same ID already exists
func CreateNewToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration, usages []string, description string) error {
return UpdateOrCreateToken(client, d, true, tokenDuration, usages, description)
func CreateNewToken(client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error {
return UpdateOrCreateToken(client, token, true, tokenDuration, usages, description)
}
// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist.
func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error {
// Let's make sure the token is valid
if valid, err := tokenutil.ValidateToken(d); !valid {
func UpdateOrCreateToken(client *clientset.Clientset, token string, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error {
tokenID, tokenSecret, err := tokenutil.ParseToken(token)
if err != nil {
return err
}
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, d.ID)
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID)
var lastErr error
for i := 0; i < tokenCreateRetries; i++ {
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
if err == nil {
if failIfExists {
return fmt.Errorf("a token with id %q already exists", d.ID)
return fmt.Errorf("a token with id %q already exists", tokenID)
}
// Secret with this ID already exists, update it:
secret.Data = encodeTokenSecretData(d, tokenDuration, usages, description)
secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description)
if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
return nil
} else {
@ -67,7 +69,7 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove
Name: secretName,
},
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
Data: encodeTokenSecretData(d, tokenDuration, usages, description),
Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description),
}
if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
return nil
@ -85,11 +87,47 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove
)
}
// CreateBootstrapConfigMap creates the public cluster-info ConfigMap
func CreateBootstrapConfigMap(file string) error {
adminConfig, err := clientcmd.LoadFromFile(file)
if err != nil {
return fmt.Errorf("failed to load admin kubeconfig [%v]", err)
}
client, err := kubeconfigutil.KubeConfigToClientSet(adminConfig)
if err != nil {
return err
}
adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster
// Copy the cluster from admin.conf to the bootstrap kubeconfig, contains the CA cert and the server URL
bootstrapConfig := &clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"": adminConfig.Clusters[adminCluster],
},
}
bootstrapBytes, err := clientcmd.Write(*bootstrapConfig)
if err != nil {
return err
}
bootstrapConfigMap := v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: bootstrapapi.ConfigMapClusterInfo},
Data: map[string]string{
bootstrapapi.KubeConfigKey: string(bootstrapBytes),
},
}
if _, err := client.CoreV1().ConfigMaps(metav1.NamespacePublic).Create(&bootstrapConfigMap); err != nil {
return err
}
return nil
}
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration, usages []string, description string) map[string][]byte {
func encodeTokenSecretData(tokenId, tokenSecret string, duration time.Duration, usages []string, description string) map[string][]byte {
data := map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(d.ID),
bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret),
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
}
if duration > 0 {

View File

@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) {
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
}
for _, rt := range tests {
actual := encodeTokenSecretData(rt.token, rt.t, []string{}, "")
actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, "")
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
t.Errorf(
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",

View File

@ -1,42 +0,0 @@
/*
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 token
import (
"bytes"
"fmt"
"os"
"path"
"k8s.io/apimachinery/pkg/util/uuid"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// CreateTokenAuthFile creates the CSV file that can be used for allowing users with tokens access to the API Server
func CreateTokenAuthFile(certsDir, bt string) error {
tokenAuthFilePath := path.Join(certsDir, kubeadmconstants.CSVTokenFileName)
if err := os.MkdirAll(certsDir, 0700); err != nil {
return fmt.Errorf("failed to create directory %q [%v]", certsDir, err)
}
serialized := []byte(fmt.Sprintf("%s,%s,%s,%s\n", bt, kubeadmconstants.CSVTokenBootstrapUser, uuid.NewUUID(), kubeadmconstants.CSVTokenBootstrapGroup))
// DumpReaderToFile create a file with mode 0600
if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), tokenAuthFilePath); err != nil {
return fmt.Errorf("failed to save token auth file (%q) [%v]", tokenAuthFilePath, err)
}
return nil
}

View File

@ -1,17 +0,0 @@
/*
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 token

View File

@ -99,3 +99,15 @@ func WriteToDisk(filename string, kubeconfig *clientcmdapi.Config) error {
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filename)
return nil
}
// GetClusterFromKubeConfig returns the default Cluster of the specified KubeConfig
func GetClusterFromKubeConfig(config *clientcmdapi.Config) *clientcmdapi.Cluster {
// If there is an unnamed cluster object, use it
if config.Clusters[""] != nil {
return config.Clusters[""]
}
if config.Contexts[config.CurrentContext] != nil {
return config.Clusters[config.Contexts[config.CurrentContext].Cluster]
}
return nil
}

View File

@ -21,7 +21,6 @@ import (
"encoding/hex"
"fmt"
"regexp"
"strings"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
@ -50,20 +49,18 @@ func randBytes(length int) (string, error) {
// GenerateToken generates a new token with a token ID that is valid as a
// Kubernetes DNS label.
// For more info, see kubernetes/pkg/util/validation/validation.go.
func GenerateToken(d *kubeadmapi.TokenDiscovery) error {
func GenerateToken() (string, error) {
tokenID, err := randBytes(TokenIDBytes)
if err != nil {
return err
return "", err
}
token, err := randBytes(TokenSecretBytes)
tokenSecret, err := randBytes(TokenSecretBytes)
if err != nil {
return err
return "", err
}
d.ID = strings.ToLower(tokenID)
d.Secret = strings.ToLower(token)
return nil
return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil
}
// ParseTokenID tries and parse a valid token ID from a string.

View File

@ -117,15 +117,19 @@ func TestValidateToken(t *testing.T) {
}
func TestGenerateToken(t *testing.T) {
td := &kubeadmapi.TokenDiscovery{}
if err := GenerateToken(td); err != nil {
token, err := GenerateToken()
if err != nil {
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
}
if len(td.ID) != 6 {
t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(td.ID))
tokenID, tokenSecret, err := ParseToken(token)
if err != nil {
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
}
if len(td.Secret) != 16 {
t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(td.Secret))
if len(tokenID) != 6 {
t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(tokenID))
}
if len(tokenSecret) != 16 {
t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(tokenSecret))
}
}

View File

@ -18,6 +18,7 @@ cmd/kube-discovery
cmd/kube-proxy
cmd/kubeadm
cmd/kubeadm/app/apis/kubeadm/install
cmd/kubeadm/app/discovery/https
cmd/kubeadm/app/phases/apiconfig
cmd/kubeadm/app/phases/certs
cmd/kubeadm/app/phases/kubeconfig

View File

@ -64,6 +64,7 @@ bench-pods
bench-quiet
bench-tasks
bench-workers
bind-addrsse
bind-address
bind-pods-burst
bind-pods-qps
@ -663,6 +664,7 @@ tls-cert-file
tls-private-key-file
tls-sni-cert-key
token-auth-file
token-ttl
to-version
to-version
ttl-keys-prefix