mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
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:
commit
7e37b895d7
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +34,6 @@ func SetEnvParams() *EnvParams {
|
|||||||
"host_etcd_path": "/var/lib/etcd",
|
"host_etcd_path": "/var/lib/etcd",
|
||||||
"hyperkube_image": "",
|
"hyperkube_image": "",
|
||||||
"repo_prefix": "gcr.io/google_containers",
|
"repo_prefix": "gcr.io/google_containers",
|
||||||
"discovery_image": fmt.Sprintf("gcr.io/google_containers/kube-discovery-%s:%s", runtime.GOARCH, "1.0"),
|
|
||||||
"etcd_image": "",
|
"etcd_image": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +48,6 @@ func SetEnvParams() *EnvParams {
|
|||||||
HostEtcdPath: path.Clean(envParams["host_etcd_path"]),
|
HostEtcdPath: path.Clean(envParams["host_etcd_path"]),
|
||||||
HyperkubeImage: envParams["hyperkube_image"],
|
HyperkubeImage: envParams["hyperkube_image"],
|
||||||
RepositoryPrefix: envParams["repo_prefix"],
|
RepositoryPrefix: envParams["repo_prefix"],
|
||||||
DiscoveryImage: envParams["discovery_image"],
|
|
||||||
EtcdImage: envParams["etcd_image"],
|
EtcdImage: envParams["etcd_image"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,9 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} {
|
|||||||
obj.Networking.ServiceSubnet = "foo"
|
obj.Networking.ServiceSubnet = "foo"
|
||||||
obj.Networking.DNSDomain = "foo"
|
obj.Networking.DNSDomain = "foo"
|
||||||
obj.AuthorizationMode = "foo"
|
obj.AuthorizationMode = "foo"
|
||||||
obj.Discovery.Token = &kubeadm.TokenDiscovery{}
|
|
||||||
obj.CertificatesDir = "foo"
|
obj.CertificatesDir = "foo"
|
||||||
obj.APIServerCertSANs = []string{}
|
obj.APIServerCertSANs = []string{}
|
||||||
|
obj.Token = "foo"
|
||||||
},
|
},
|
||||||
func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) {
|
func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) {
|
||||||
c.FuzzNoCustom(obj)
|
c.FuzzNoCustom(obj)
|
||||||
|
@ -46,7 +46,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
&MasterConfiguration{},
|
&MasterConfiguration{},
|
||||||
&NodeConfiguration{},
|
&NodeConfiguration{},
|
||||||
&ClusterInfo{},
|
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package kubeadm
|
package kubeadm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,7 +27,6 @@ type EnvParams struct {
|
|||||||
HostEtcdPath string
|
HostEtcdPath string
|
||||||
HyperkubeImage string
|
HyperkubeImage string
|
||||||
RepositoryPrefix string
|
RepositoryPrefix string
|
||||||
DiscoveryImage string
|
|
||||||
EtcdImage string
|
EtcdImage string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,13 +34,15 @@ type MasterConfiguration struct {
|
|||||||
metav1.TypeMeta
|
metav1.TypeMeta
|
||||||
|
|
||||||
API API
|
API API
|
||||||
Discovery Discovery
|
|
||||||
Etcd Etcd
|
Etcd Etcd
|
||||||
Networking Networking
|
Networking Networking
|
||||||
KubernetesVersion string
|
KubernetesVersion string
|
||||||
CloudProvider string
|
CloudProvider string
|
||||||
AuthorizationMode string
|
AuthorizationMode string
|
||||||
|
|
||||||
|
Token string
|
||||||
|
TokenTTL time.Duration
|
||||||
|
|
||||||
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
|
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
|
||||||
// controller manager are managed by Kubernetes itself. This option is likely to
|
// controller manager are managed by Kubernetes itself. This option is likely to
|
||||||
// become the default in the future.
|
// become the default in the future.
|
||||||
@ -62,20 +65,6 @@ type API struct {
|
|||||||
BindPort int32
|
BindPort int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Discovery struct {
|
|
||||||
HTTPS *HTTPSDiscovery
|
|
||||||
File *FileDiscovery
|
|
||||||
Token *TokenDiscovery
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPSDiscovery struct {
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileDiscovery struct {
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenDiscovery struct {
|
type TokenDiscovery struct {
|
||||||
ID string
|
ID string
|
||||||
Secret string
|
Secret string
|
||||||
@ -106,11 +95,3 @@ type NodeConfiguration struct {
|
|||||||
TLSBootstrapToken string
|
TLSBootstrapToken string
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClusterInfo TODO add description
|
|
||||||
type ClusterInfo struct {
|
|
||||||
metav1.TypeMeta
|
|
||||||
// TODO(phase1+) this may become simply `api.Config`
|
|
||||||
CertificateAuthorities []string
|
|
||||||
Endpoints []string
|
|
||||||
}
|
|
||||||
|
@ -18,6 +18,7 @@ go_library(
|
|||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||||
|
@ -20,14 +20,15 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultServiceDNSDomain = "cluster.local"
|
DefaultServiceDNSDomain = "cluster.local"
|
||||||
DefaultServicesSubnet = "10.96.0.0/12"
|
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
|
// 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
|
DefaultAPIBindPort = 6443
|
||||||
DefaultDiscoveryBindPort = 9898
|
DefaultDiscoveryBindPort = 9898
|
||||||
DefaultAuthorizationMode = "RBAC"
|
DefaultAuthorizationMode = "RBAC"
|
||||||
@ -60,10 +61,6 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
|
|||||||
obj.Networking.DNSDomain = DefaultServiceDNSDomain
|
obj.Networking.DNSDomain = DefaultServiceDNSDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Discovery.Token == nil && obj.Discovery.File == nil && obj.Discovery.HTTPS == nil {
|
|
||||||
obj.Discovery.Token = &TokenDiscovery{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.AuthorizationMode == "" {
|
if obj.AuthorizationMode == "" {
|
||||||
obj.AuthorizationMode = DefaultAuthorizationMode
|
obj.AuthorizationMode = DefaultAuthorizationMode
|
||||||
}
|
}
|
||||||
@ -71,6 +68,10 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
|
|||||||
if obj.CertificatesDir == "" {
|
if obj.CertificatesDir == "" {
|
||||||
obj.CertificatesDir = DefaultCertificatesDir
|
obj.CertificatesDir = DefaultCertificatesDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if obj.TokenTTL == 0 {
|
||||||
|
obj.TokenTTL = constants.DefaultTokenDuration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
|
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
|
||||||
|
@ -47,7 +47,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
&MasterConfiguration{},
|
&MasterConfiguration{},
|
||||||
&NodeConfiguration{},
|
&NodeConfiguration{},
|
||||||
&ClusterInfo{},
|
|
||||||
)
|
)
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
return nil
|
return nil
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,13 +26,15 @@ type MasterConfiguration struct {
|
|||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
API API `json:"api"`
|
API API `json:"api"`
|
||||||
Discovery Discovery `json:"discovery"`
|
|
||||||
Etcd Etcd `json:"etcd"`
|
Etcd Etcd `json:"etcd"`
|
||||||
Networking Networking `json:"networking"`
|
Networking Networking `json:"networking"`
|
||||||
KubernetesVersion string `json:"kubernetesVersion"`
|
KubernetesVersion string `json:"kubernetesVersion"`
|
||||||
CloudProvider string `json:"cloudProvider"`
|
CloudProvider string `json:"cloudProvider"`
|
||||||
AuthorizationMode string `json:"authorizationMode"`
|
AuthorizationMode string `json:"authorizationMode"`
|
||||||
|
|
||||||
|
Token string `json:"token"`
|
||||||
|
TokenTTL time.Duration `json:"tokenTTL"`
|
||||||
|
|
||||||
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
|
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
|
||||||
// controller manager are managed by Kubernetes itself. This option is likely to
|
// controller manager are managed by Kubernetes itself. This option is likely to
|
||||||
// become the default in the future.
|
// become the default in the future.
|
||||||
@ -53,20 +57,6 @@ type API struct {
|
|||||||
BindPort int32 `json:"bindPort"`
|
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 {
|
type TokenDiscovery struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
@ -96,11 +86,3 @@ type NodeConfiguration struct {
|
|||||||
TLSBootstrapToken string `json:"tlsBootstrapToken"`
|
TLSBootstrapToken string `json:"tlsBootstrapToken"`
|
||||||
Token string `json:"token"`
|
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"`
|
|
||||||
}
|
|
||||||
|
@ -56,6 +56,7 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList
|
|||||||
allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...)
|
allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...)
|
||||||
allErrs = append(allErrs, ValidateAPIServerCertSANs(c.APIServerCertSANs, field.NewPath("cert-altnames"))...)
|
allErrs = append(allErrs, ValidateAPIServerCertSANs(c.APIServerCertSANs, field.NewPath("cert-altnames"))...)
|
||||||
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
|
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
|
||||||
|
allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +88,14 @@ func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.
|
|||||||
allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...)
|
allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...)
|
||||||
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...)
|
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...)
|
||||||
allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, 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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,41 +172,21 @@ func TestValidateMasterConfiguration(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{&kubeadm.MasterConfiguration{}, false},
|
{&kubeadm.MasterConfiguration{}, false},
|
||||||
{&kubeadm.MasterConfiguration{
|
{&kubeadm.MasterConfiguration{
|
||||||
Discovery: kubeadm.Discovery{
|
|
||||||
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
|
|
||||||
},
|
|
||||||
AuthorizationMode: "RBAC",
|
AuthorizationMode: "RBAC",
|
||||||
Networking: kubeadm.Networking{
|
Networking: kubeadm.Networking{
|
||||||
ServiceSubnet: "10.96.0.1/12",
|
ServiceSubnet: "10.96.0.1/12",
|
||||||
DNSDomain: "cluster.local",
|
DNSDomain: "cluster.local",
|
||||||
},
|
},
|
||||||
CertificatesDir: "/some/cert/dir",
|
CertificatesDir: "/some/cert/dir",
|
||||||
}, true},
|
}, false},
|
||||||
{&kubeadm.MasterConfiguration{
|
{&kubeadm.MasterConfiguration{
|
||||||
Discovery: kubeadm.Discovery{
|
|
||||||
File: &kubeadm.FileDiscovery{Path: "foo"},
|
|
||||||
},
|
|
||||||
AuthorizationMode: "RBAC",
|
AuthorizationMode: "RBAC",
|
||||||
Networking: kubeadm.Networking{
|
Networking: kubeadm.Networking{
|
||||||
ServiceSubnet: "10.96.0.1/12",
|
ServiceSubnet: "10.96.0.1/12",
|
||||||
DNSDomain: "cluster.local",
|
DNSDomain: "cluster.local",
|
||||||
},
|
},
|
||||||
CertificatesDir: "/some/other/cert/dir",
|
CertificatesDir: "/some/other/cert/dir",
|
||||||
}, true},
|
Token: "abcdef.0123456789abcdef",
|
||||||
{&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",
|
|
||||||
}, true},
|
}, true},
|
||||||
}
|
}
|
||||||
for _, rt := range tests {
|
for _, rt := range tests {
|
||||||
|
@ -30,9 +30,7 @@ import (
|
|||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion)
|
||||||
minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion)
|
|
||||||
)
|
|
||||||
|
|
||||||
func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
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)")
|
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.Token == "" {
|
||||||
if cfg.Discovery.Token != nil {
|
var err error
|
||||||
if cfg.Discovery.Token.ID != "" && cfg.Discovery.Token.Secret != "" {
|
cfg.Token, err = tokenutil.GenerateToken()
|
||||||
fmt.Printf("[init] A token has been provided, validating [%s]\n", tokenutil.BearerToken(cfg.Discovery.Token))
|
if err != nil {
|
||||||
if valid, err := tokenutil.ValidateToken(cfg.Discovery.Token); valid == false {
|
return fmt.Errorf("couldn't generate random token: %v", err)
|
||||||
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)}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import (
|
|||||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
|
|
||||||
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
|
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
|
||||||
addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons"
|
addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons"
|
||||||
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
|
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
|
||||||
@ -40,7 +39,6 @@ import (
|
|||||||
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
|
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -56,16 +54,14 @@ var (
|
|||||||
|
|
||||||
You can now join any number of machines by running the following on each node:
|
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.
|
// NewCmdInit returns "kubeadm init" command.
|
||||||
func NewCmdInit(out io.Writer) *cobra.Command {
|
func NewCmdInit(out io.Writer) *cobra.Command {
|
||||||
versioned := &kubeadmapiext.MasterConfiguration{}
|
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||||
api.Scheme.Default(versioned)
|
api.Scheme.Default(cfg)
|
||||||
cfg := kubeadmapi.MasterConfiguration{}
|
|
||||||
api.Scheme.Convert(versioned, &cfg, nil)
|
|
||||||
|
|
||||||
var cfgPath string
|
var cfgPath string
|
||||||
var skipPreFlight bool
|
var skipPreFlight bool
|
||||||
@ -73,7 +69,11 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
|||||||
Use: "init",
|
Use: "init",
|
||||||
Short: "Run this in order to set up the Kubernetes master",
|
Short: "Run this in order to set up the Kubernetes master",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
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(err)
|
||||||
kubeadmutil.CheckErr(i.Validate())
|
kubeadmutil.CheckErr(i.Validate())
|
||||||
kubeadmutil.CheckErr(i.Run(out))
|
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.`,
|
`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(
|
cmd.PersistentFlags().BoolVar(
|
||||||
&skipPreFlight, "skip-preflight-checks", skipPreFlight,
|
&skipPreFlight, "skip-preflight-checks", skipPreFlight,
|
||||||
"Skip preflight checks normally run before modifying the system",
|
"Skip preflight checks normally run before modifying the system",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.PersistentFlags().Var(
|
cmd.PersistentFlags().StringVar(
|
||||||
discovery.NewDiscoveryValue(&cfg.Discovery), "discovery",
|
&cfg.Token, "token", cfg.Token,
|
||||||
"The discovery method kubeadm will use for connecting nodes to the master",
|
"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
|
return cmd
|
||||||
}
|
}
|
||||||
@ -199,20 +202,13 @@ func (i *Init) Run(out io.Writer) error {
|
|||||||
return err
|
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
|
// PHASE 3: Bootstrap the control plane
|
||||||
if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil {
|
if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -225,22 +221,22 @@ func (i *Init) Run(out io.Writer) error {
|
|||||||
if i.cfg.SelfHosted {
|
if i.cfg.SelfHosted {
|
||||||
// Temporary control plane is up, now we create our self hosted control
|
// Temporary control plane is up, now we create our self hosted control
|
||||||
// plane components and remove the static manifests:
|
// 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 {
|
if err := kubemaster.CreateSelfHostedControlPlane(i.cfg, client); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PHASE 4: Set up the bootstrap tokens
|
// PHASE 4: Set up the bootstrap tokens
|
||||||
if i.cfg.Discovery.Token != nil {
|
fmt.Printf("[token] Using token: %s\n", i.cfg.Token)
|
||||||
fmt.Printf("[token-discovery] Using token: %s\n", tokenutil.BearerToken(i.cfg.Discovery.Token))
|
|
||||||
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil {
|
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
|
||||||
return err
|
if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL, kubeadmconstants.DefaultTokenUsages, tokenDescription); 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
|
if err := tokenphase.CreateBootstrapConfigMap(adminKubeConfigPath); err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PHASE 5: Install and deploy all addons, and configure things as necessary
|
// 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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateJoinArgs generates kubeadm join arguments
|
|
||||||
func generateJoinArgs(cfg *kubeadmapi.MasterConfiguration) string {
|
|
||||||
return discovery.NewDiscoveryValue(&cfg.Discovery).String()
|
|
||||||
}
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/renstrom/dedent"
|
"github.com/renstrom/dedent"
|
||||||
@ -90,9 +91,11 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
|
|||||||
`),
|
`),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cfg.DiscoveryTokenAPIServers = args
|
cfg.DiscoveryTokenAPIServers = args
|
||||||
|
|
||||||
api.Scheme.Default(cfg)
|
api.Scheme.Default(cfg)
|
||||||
internalcfg := &kubeadmapi.NodeConfiguration{}
|
internalcfg := &kubeadmapi.NodeConfiguration{}
|
||||||
api.Scheme.Convert(cfg, internalcfg, nil)
|
api.Scheme.Convert(cfg, internalcfg, nil)
|
||||||
|
|
||||||
j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight)
|
j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
kubeadmutil.CheckErr(j.Validate())
|
kubeadmutil.CheckErr(j.Validate())
|
||||||
@ -174,7 +177,19 @@ func (j *Join) Run(out io.Writer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
// 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 {
|
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 {
|
if len(token) == 0 {
|
||||||
err = tokenutil.GenerateToken(td)
|
var err error
|
||||||
|
token, err = tokenutil.GenerateToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
td.ID, td.Secret, err = tokenutil.ParseToken(token)
|
_, _, err := tokenutil.ParseToken(token)
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate usages here so we don't allow something unsupported
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(out, tokenutil.BearerToken(td))
|
fmt.Fprintln(out, token)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunGenerateToken just generates a random token for the user
|
// RunGenerateToken just generates a random token for the user
|
||||||
func RunGenerateToken(out io.Writer) error {
|
func RunGenerateToken(out io.Writer) error {
|
||||||
td := &kubeadmapi.TokenDiscovery{}
|
token, err := tokenutil.GenerateToken()
|
||||||
err := tokenutil.GenerateToken(td)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(out, tokenutil.BearerToken(td))
|
fmt.Fprintln(out, token)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ const (
|
|||||||
SchedulerKubeConfigFileName = "scheduler.conf"
|
SchedulerKubeConfigFileName = "scheduler.conf"
|
||||||
|
|
||||||
// Important: a "v"-prefix shouldn't exist here; semver doesn't allow that
|
// 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
|
// 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 defines how long kubeadm should wait before retrying a failed API operation
|
||||||
APICallRetryInterval = 500 * time.Millisecond
|
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.
|
// 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
|
// 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
|
// 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"
|
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 indicates minimum external etcd version which kubeadm supports
|
||||||
MinExternalEtcdVersion = "3.0.14"
|
MinExternalEtcdVersion = "3.0.14"
|
||||||
|
|
||||||
|
@ -10,36 +10,24 @@ load(
|
|||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = ["discovery.go"],
|
||||||
"discovery.go",
|
|
||||||
"flags.go",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/discovery/file:go_default_library",
|
"//cmd/kubeadm/app/discovery/file:go_default_library",
|
||||||
"//cmd/kubeadm/app/discovery/https:go_default_library",
|
"//cmd/kubeadm/app/discovery/https:go_default_library",
|
||||||
"//cmd/kubeadm/app/discovery/token:go_default_library",
|
"//cmd/kubeadm/app/discovery/token:go_default_library",
|
||||||
"//cmd/kubeadm/app/node:go_default_library",
|
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
|
||||||
"//vendor:github.com/spf13/pflag",
|
|
||||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
|
||||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = ["discovery_test.go"],
|
||||||
"discovery_test.go",
|
|
||||||
"flags_test.go",
|
|
||||||
],
|
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
|
||||||
"//vendor:github.com/davecgh/go-spew/spew",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
@ -18,27 +18,47 @@ package discovery
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node"
|
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
|
||||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
"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.
|
const TokenUser = "tls-bootstrap-token-user"
|
||||||
func For(d *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
|
|
||||||
|
// 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 {
|
switch {
|
||||||
case len(d.DiscoveryFile) != 0:
|
case len(cfg.DiscoveryFile) != 0:
|
||||||
if isHTTPSURL(d.DiscoveryFile) {
|
if isHTTPSURL(cfg.DiscoveryFile) {
|
||||||
return runHTTPSDiscovery(d.DiscoveryFile)
|
return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
|
||||||
}
|
}
|
||||||
return runFileDiscovery(d.DiscoveryFile)
|
return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
|
||||||
case len(d.DiscoveryToken) != 0:
|
case len(cfg.DiscoveryToken) != 0:
|
||||||
return runTokenDiscovery(d.DiscoveryToken, d.DiscoveryTokenAPIServers)
|
return token.RetrieveValidatedClusterInfo(cfg.DiscoveryToken, cfg.DiscoveryTokenAPIServers)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("couldn't find a valid discovery configuration.")
|
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)
|
u, err := url.Parse(s)
|
||||||
return err == nil && u.Scheme == "https"
|
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
|
|
||||||
}
|
|
||||||
|
@ -11,7 +11,17 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["file.go"],
|
srcs = ["file.go"],
|
||||||
tags = ["automanaged"],
|
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(
|
filegroup(
|
||||||
|
@ -17,13 +17,114 @@ limitations under the License.
|
|||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
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) {
|
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
|
||||||
c.File = &kubeadm.FileDiscovery{
|
// securely to the API Server using the provided CA cert and
|
||||||
Path: u.Path,
|
// 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
|
||||||
}
|
}
|
||||||
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,7 +11,11 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["https.go"],
|
srcs = ["https.go"],
|
||||||
tags = ["automanaged"],
|
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(
|
filegroup(
|
||||||
|
@ -17,13 +17,32 @@ limitations under the License.
|
|||||||
package https
|
package https
|
||||||
|
|
||||||
import (
|
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) {
|
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
|
||||||
c.HTTPS = &kubeadm.HTTPSDiscovery{
|
// securely to the API Server using the provided CA cert and
|
||||||
URL: u.String(),
|
// 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)
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,25 @@ licenses(["notice"])
|
|||||||
load(
|
load(
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
"go_library",
|
"go_library",
|
||||||
|
"go_test",
|
||||||
)
|
)
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["token.go"],
|
srcs = ["token.go"],
|
||||||
tags = ["automanaged"],
|
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(
|
filegroup(
|
||||||
@ -26,3 +38,14 @@ filegroup(
|
|||||||
srcs = [":package-srcs"],
|
srcs = [":package-srcs"],
|
||||||
tags = ["automanaged"],
|
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",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@ -17,29 +17,117 @@ limitations under the License.
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"fmt"
|
||||||
"strings"
|
"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) {
|
const BootstrapUser = "token-bootstrap-client"
|
||||||
var (
|
|
||||||
hosts []string
|
// RetrieveValidatedClusterInfo connects to the API Server and tries to fetch the cluster-info ConfigMap
|
||||||
tokenID, token string
|
// 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) {
|
||||||
if u.Host != "" {
|
|
||||||
hosts = strings.Split(u.Host, ",")
|
tokenId, tokenSecret, err := tokenutil.ParseToken(discoveryToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if u.User != nil {
|
|
||||||
if p, ok := u.User.Password(); ok {
|
// The function below runs for every endpoint, and all endpoints races with each other.
|
||||||
tokenID = u.User.Username()
|
// The endpoint that wins the race and completes the task first gets its kubeconfig returned below
|
||||||
token = p
|
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{
|
fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", bootstrapConfig.Clusters[clusterName].Server)
|
||||||
ID: tokenID,
|
|
||||||
Secret: token,
|
var clusterinfo *v1.ConfigMap
|
||||||
Addresses: hosts,
|
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
|
||||||
}
|
}
|
||||||
|
61
cmd/kubeadm/app/discovery/token/token_test.go
Normal file
61
cmd/kubeadm/app/discovery/token/token_test.go
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"apiclient.go",
|
"apiclient.go",
|
||||||
"discovery.go",
|
|
||||||
"manifests.go",
|
"manifests.go",
|
||||||
"selfhosted.go",
|
"selfhosted.go",
|
||||||
"templates.go",
|
"templates.go",
|
||||||
@ -24,6 +23,7 @@ go_library(
|
|||||||
"//cmd/kubeadm/app/images:go_default_library",
|
"//cmd/kubeadm/app/images:go_default_library",
|
||||||
"//cmd/kubeadm/app/util:go_default_library",
|
"//cmd/kubeadm/app/util:go_default_library",
|
||||||
"//cmd/kubeadm/app/util/kubeconfig: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/kubeapiserver/authorizer/modes:go_default_library",
|
||||||
"//pkg/kubectl/cmd/util:go_default_library",
|
"//pkg/kubectl/cmd/util:go_default_library",
|
||||||
"//vendor:github.com/ghodss/yaml",
|
"//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",
|
||||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||||
"//vendor:k8s.io/client-go/pkg/apis/extensions/v1beta1",
|
"//vendor:k8s.io/client-go/pkg/apis/extensions/v1beta1",
|
||||||
"//vendor:k8s.io/client-go/util/cert",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -32,6 +32,7 @@ import (
|
|||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||||
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
)
|
)
|
||||||
@ -301,20 +302,20 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultArguments := map[string]string{
|
defaultArguments := map[string]string{
|
||||||
"insecure-port": "0",
|
"insecure-port": "0",
|
||||||
"admission-control": kubeadmconstants.DefaultAdmissionControl,
|
"admission-control": kubeadmconstants.DefaultAdmissionControl,
|
||||||
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
|
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
|
||||||
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
|
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
|
||||||
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||||
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
|
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
|
||||||
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
|
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
|
||||||
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
|
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
|
||||||
"kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
|
"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),
|
||||||
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
|
"allow-privileged": "true",
|
||||||
"allow-privileged": "true",
|
"experimental-bootstrap-token-auth": "true",
|
||||||
"storage-backend": "etcd3",
|
"storage-backend": "etcd3",
|
||||||
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
|
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
|
||||||
// add options to configure the front proxy. Without the generated client cert, this will never be useable
|
// add options to configure the front proxy. Without the generated client cert, this will never be useable
|
||||||
// so add it unconditionally with recommended values
|
// so add it unconditionally with recommended values
|
||||||
"requestheader-username-headers": "X-Remote-User",
|
"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),
|
"service-account-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
|
||||||
"cluster-signing-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
"cluster-signing-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||||
"cluster-signing-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
|
"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",
|
"use-service-account-credentials": "true",
|
||||||
|
"controllers": "*,bootstrapsigner,tokencleaner",
|
||||||
}
|
}
|
||||||
|
|
||||||
command = getComponentBaseCommand(controllerManager)
|
command = getComponentBaseCommand(controllerManager)
|
||||||
|
@ -397,11 +397,11 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||||
"--token-auth-file=" + testCertsDir + "/tokens.csv",
|
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged=true",
|
"--allow-privileged=true",
|
||||||
"--storage-backend=etcd3",
|
"--storage-backend=etcd3",
|
||||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||||
|
"--experimental-bootstrap-token-auth=true",
|
||||||
"--requestheader-username-headers=X-Remote-User",
|
"--requestheader-username-headers=X-Remote-User",
|
||||||
"--requestheader-group-headers=X-Remote-Group",
|
"--requestheader-group-headers=X-Remote-Group",
|
||||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||||
@ -429,11 +429,11 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||||
"--token-auth-file=" + testCertsDir + "/tokens.csv",
|
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged=true",
|
"--allow-privileged=true",
|
||||||
"--storage-backend=etcd3",
|
"--storage-backend=etcd3",
|
||||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||||
|
"--experimental-bootstrap-token-auth=true",
|
||||||
"--requestheader-username-headers=X-Remote-User",
|
"--requestheader-username-headers=X-Remote-User",
|
||||||
"--requestheader-group-headers=X-Remote-Group",
|
"--requestheader-group-headers=X-Remote-Group",
|
||||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||||
@ -462,11 +462,11 @@ func TestGetAPIServerCommand(t *testing.T) {
|
|||||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||||
"--token-auth-file=" + testCertsDir + "/tokens.csv",
|
|
||||||
fmt.Sprintf("--secure-port=%d", 123),
|
fmt.Sprintf("--secure-port=%d", 123),
|
||||||
"--allow-privileged=true",
|
"--allow-privileged=true",
|
||||||
"--storage-backend=etcd3",
|
"--storage-backend=etcd3",
|
||||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||||
|
"--experimental-bootstrap-token-auth=true",
|
||||||
"--requestheader-username-headers=X-Remote-User",
|
"--requestheader-username-headers=X-Remote-User",
|
||||||
"--requestheader-group-headers=X-Remote-Group",
|
"--requestheader-group-headers=X-Remote-Group",
|
||||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||||
@ -509,8 +509,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
|||||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
"--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",
|
"--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",
|
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
"--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",
|
"--use-service-account-credentials=true",
|
||||||
|
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||||
"--cloud-provider=foo",
|
"--cloud-provider=foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -546,8 +548,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
|||||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
"--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",
|
"--use-service-account-credentials=true",
|
||||||
|
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||||
"--allocate-node-cidrs=true",
|
"--allocate-node-cidrs=true",
|
||||||
"--cluster-cidr=bar",
|
"--cluster-cidr=bar",
|
||||||
},
|
},
|
||||||
|
@ -39,62 +39,5 @@ spec:
|
|||||||
- image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.0
|
- image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.0
|
||||||
name: dummy
|
name: dummy
|
||||||
hostNetwork: true
|
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
|
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
@ -11,22 +11,16 @@ load(
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"bootstrap.go",
|
|
||||||
"csr.go",
|
"csr.go",
|
||||||
"discovery.go",
|
"validate.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
|
||||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
|
||||||
"//pkg/kubelet/util/csr: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/types",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
|
||||||
"//vendor:k8s.io/client-go/kubernetes",
|
"//vendor:k8s.io/client-go/kubernetes",
|
||||||
"//vendor:k8s.io/client-go/pkg/apis/certificates",
|
"//vendor:k8s.io/client-go/pkg/apis/certificates/v1beta1",
|
||||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
|
||||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||||
"//vendor:k8s.io/client-go/util/cert",
|
"//vendor:k8s.io/client-go/util/cert",
|
||||||
],
|
],
|
||||||
@ -34,14 +28,10 @@ go_library(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = ["validate_test.go"],
|
||||||
"bootstrap_test.go",
|
|
||||||
"discovery_test.go",
|
|
||||||
],
|
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/version",
|
"//vendor:k8s.io/apimachinery/pkg/version",
|
||||||
"//vendor:k8s.io/client-go/discovery",
|
"//vendor:k8s.io/client-go/discovery",
|
||||||
|
@ -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())
|
|
||||||
}
|
|
@ -18,32 +18,23 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"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"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
|
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/csr"
|
"k8s.io/kubernetes/pkg/kubelet/util/csr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PerformTLSBootstrap executes a node certificate signing request.
|
const CSRContextAndUser = "kubelet-csr"
|
||||||
func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
|
|
||||||
hostName, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := types.NodeName(hostName)
|
|
||||||
|
|
||||||
rc, err := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{}).ClientConfig()
|
// PerformTLSBootstrap executes a node certificate signing request.
|
||||||
if err != nil {
|
func PerformTLSBootstrap(cfg *clientcmdapi.Config, hostName string) error {
|
||||||
return err
|
client, err := kubeconfigutil.KubeConfigToClientSet(cfg)
|
||||||
}
|
|
||||||
c, err := clientset.NewForConfig(rc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request")
|
fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request")
|
||||||
|
|
||||||
key, err := certutil.MakeEllipticPrivateKeyPEM()
|
key, err := certutil.MakeEllipticPrivateKeyPEM()
|
||||||
@ -51,21 +42,20 @@ func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
|
|||||||
return fmt.Errorf("failed to generate private key [%v]", err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to request signed certificate from the API server [%v]", err)
|
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] Received signed certificate from the API server, generating KubeConfig...")
|
||||||
fmt.Println("[csr] Generating kubelet configuration")
|
|
||||||
|
|
||||||
cfg.AuthInfos["kubelet"] = &clientcmdapi.AuthInfo{
|
cfg.AuthInfos[CSRContextAndUser] = &clientcmdapi.AuthInfo{
|
||||||
ClientKeyData: key,
|
ClientKeyData: key,
|
||||||
ClientCertificateData: cert,
|
ClientCertificateData: cert,
|
||||||
}
|
}
|
||||||
cfg.Contexts["kubelet"] = &clientcmdapi.Context{
|
cfg.Contexts[CSRContextAndUser] = &clientcmdapi.Context{
|
||||||
AuthInfo: "kubelet",
|
AuthInfo: CSRContextAndUser,
|
||||||
Cluster: cfg.Contexts[cfg.CurrentContext].Cluster,
|
Cluster: cfg.Contexts[cfg.CurrentContext].Cluster,
|
||||||
}
|
}
|
||||||
cfg.CurrentContext = "kubelet"
|
cfg.CurrentContext = CSRContextAndUser
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
51
cmd/kubeadm/app/node/validate.go
Normal file
51
cmd/kubeadm/app/node/validate.go
Normal 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)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 The Kubernetes Authors.
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -27,150 +27,9 @@ import (
|
|||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEstablishMasterConnection(t *testing.T) {
|
func TestValidateAPIServer(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) {
|
|
||||||
expect := version.Info{
|
expect := version.Info{
|
||||||
Major: "foo",
|
Major: "foo",
|
||||||
Minor: "bar",
|
Minor: "bar",
|
||||||
@ -191,7 +50,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
|||||||
case "/api":
|
case "/api":
|
||||||
obj = &metav1.APIVersions{
|
obj = &metav1.APIVersions{
|
||||||
Versions: []string{
|
Versions: []string{
|
||||||
"v1.4",
|
"v1.6.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
output, err := json.Marshal(obj)
|
output, err := json.Marshal(obj)
|
||||||
@ -222,7 +81,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
|||||||
case "/api":
|
case "/api":
|
||||||
obj = &metav1.APIVersions{
|
obj = &metav1.APIVersions{
|
||||||
Versions: []string{
|
Versions: []string{
|
||||||
"v1.4",
|
"v1.6.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
output, err := json.Marshal(obj)
|
output, err := json.Marshal(obj)
|
||||||
@ -239,7 +98,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "certificates.k8s.io",
|
Name: "certificates.k8s.io",
|
||||||
Versions: []metav1.GroupVersionForDiscovery{
|
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}
|
rc := &restclient.Config{Host: rt.s.URL}
|
||||||
c, err := discovery.NewDiscoveryClientForConfig(rc)
|
c, err := discovery.NewDiscoveryClientForConfig(rc)
|
||||||
if err != nil {
|
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}
|
cs := &clientset.Clientset{DiscoveryClient: c}
|
||||||
actual := checkAPIEndpoint(cs, "")
|
actual := ValidateAPIServer(cs)
|
||||||
if (actual == nil) != rt.expect {
|
if (actual == nil) != rt.expect {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"failed runChecks:\n\texpected: %t\n\t actual: %t",
|
"failed TestValidateAPIServer:\n\texpected: %t\n\t actual: %t",
|
||||||
rt.expect,
|
rt.expect,
|
||||||
(actual == nil),
|
(actual == nil),
|
||||||
)
|
)
|
@ -16,6 +16,7 @@ go_library(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
"//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/api/errors",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||||
|
@ -24,33 +24,35 @@ import (
|
|||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1"
|
rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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 sets the name for the kube-proxy ClusterRole
|
||||||
KubeProxyClusterRoleName = "system:node-proxier"
|
KubeProxyClusterRoleName = "system:node-proxier"
|
||||||
// NodeBootstrapperClusterRoleName sets the name for the TLS Node Bootstrapper ClusterRole
|
// NodeBootstrapperClusterRoleName sets the name for the TLS Node Bootstrapper ClusterRole
|
||||||
NodeBootstrapperClusterRoleName = "system:node-bootstrapper"
|
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
|
// Constants
|
||||||
clusterRoleKind = "ClusterRole"
|
clusterRoleKind = "ClusterRole"
|
||||||
|
roleKind = "Role"
|
||||||
serviceAccountKind = "ServiceAccount"
|
serviceAccountKind = "ServiceAccount"
|
||||||
rbacAPIGroup = "rbac.authorization.k8s.io"
|
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?
|
// 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
|
// CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster
|
||||||
func CreateRBACRules(clientset *clientset.Clientset) error {
|
func CreateRBACRules(clientset *clientset.Clientset) error {
|
||||||
// Create the ClusterRoles we need for our RBAC rules
|
if err := CreateRoles(clientset); err != nil {
|
||||||
if err := CreateClusterRoles(clientset); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
if err := CreateRoleBindings(clientset); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Create the CreateClusterRoleBindings we need for our RBAC rules
|
|
||||||
if err := CreateClusterRoleBindings(clientset); err != nil {
|
if err := CreateClusterRoleBindings(clientset); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -84,19 +86,53 @@ func CreateServiceAccounts(clientset *clientset.Clientset) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClusterRoles creates the ClusterRoles that aren't bootstrapped by the apiserver
|
// CreateRoles creates namespaces RBAC Roles
|
||||||
func CreateClusterRoles(clientset *clientset.Clientset) error {
|
func CreateRoles(clientset *clientset.Clientset) error {
|
||||||
// TODO: Remove this ClusterRole when it's automatically bootstrapped in the apiserver
|
roles := []rbac.Role{
|
||||||
clusterRole := rbac.ClusterRole{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: KubeDNSClusterRoleName},
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Rules: []rbac.PolicyRule{
|
Name: BootstrapSignerClusterRoleName,
|
||||||
rbac.NewRule("list", "watch").Groups("").Resources("endpoints", "services").RuleOrDie(),
|
Namespace: metav1.NamespacePublic,
|
||||||
// TODO: remove watch rule when https://github.com/kubernetes/kubernetes/pull/38816 gets merged
|
},
|
||||||
rbac.NewRule("get", "list", "watch").Groups("").Resources("configmaps").RuleOrDie(),
|
Rules: []rbac.PolicyRule{
|
||||||
|
rbac.NewRule("get").Groups("").Resources("configmaps").RuleOrDie(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if _, err := clientset.Rbac().ClusterRoles().Create(&clusterRole); err != nil {
|
for _, role := range roles {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@ -116,24 +152,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
|
|||||||
Subjects: []rbac.Subject{
|
Subjects: []rbac.Subject{
|
||||||
{
|
{
|
||||||
Kind: "Group",
|
Kind: "Group",
|
||||||
Name: kubeadmconstants.CSVTokenBootstrapGroup,
|
Name: bootstrapapi.BootstrapGroup,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -157,7 +176,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, clusterRoleBinding := range clusterRoleBindings {
|
for _, clusterRoleBinding := range clusterRoleBindings {
|
||||||
if _, err := clientset.Rbac().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
|
if _, err := clientset.RbacV1beta1().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,7 @@ load(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = ["bootstrap_test.go"],
|
||||||
"bootstrap_test.go",
|
|
||||||
"csv_test.go",
|
|
||||||
],
|
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||||
@ -21,22 +18,18 @@ go_test(
|
|||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = ["bootstrap.go"],
|
||||||
"bootstrap.go",
|
|
||||||
"csv.go",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
|
||||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||||
"//pkg/bootstrap/api: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/api/errors",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//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/kubernetes",
|
||||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||||
|
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||||
|
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,7 +24,9 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"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"
|
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
)
|
)
|
||||||
@ -32,26 +34,26 @@ import (
|
|||||||
const tokenCreateRetries = 5
|
const tokenCreateRetries = 5
|
||||||
|
|
||||||
// CreateNewToken tries to create a token and fails if one with the same ID already exists
|
// 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 {
|
func CreateNewToken(client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error {
|
||||||
return UpdateOrCreateToken(client, d, true, tokenDuration, usages, description)
|
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.
|
// 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 {
|
func UpdateOrCreateToken(client *clientset.Clientset, token string, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error {
|
||||||
// Let's make sure the token is valid
|
tokenID, tokenSecret, err := tokenutil.ParseToken(token)
|
||||||
if valid, err := tokenutil.ValidateToken(d); !valid {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, d.ID)
|
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID)
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for i := 0; i < tokenCreateRetries; i++ {
|
for i := 0; i < tokenCreateRetries; i++ {
|
||||||
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if failIfExists {
|
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 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 {
|
if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
@ -67,7 +69,7 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove
|
|||||||
Name: secretName,
|
Name: secretName,
|
||||||
},
|
},
|
||||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
|
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 {
|
if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
|
||||||
return 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
|
// 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{
|
data := map[string][]byte{
|
||||||
bootstrapapi.BootstrapTokenIDKey: []byte(d.ID),
|
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
|
||||||
bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret),
|
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
|
||||||
}
|
}
|
||||||
|
|
||||||
if duration > 0 {
|
if duration > 0 {
|
||||||
|
@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) {
|
|||||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
|
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
|
||||||
}
|
}
|
||||||
for _, rt := range tests {
|
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)) {
|
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
@ -99,3 +99,15 @@ func WriteToDisk(filename string, kubeconfig *clientcmdapi.Config) error {
|
|||||||
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filename)
|
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filename)
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
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
|
// GenerateToken generates a new token with a token ID that is valid as a
|
||||||
// Kubernetes DNS label.
|
// Kubernetes DNS label.
|
||||||
// For more info, see kubernetes/pkg/util/validation/validation.go.
|
// For more info, see kubernetes/pkg/util/validation/validation.go.
|
||||||
func GenerateToken(d *kubeadmapi.TokenDiscovery) error {
|
func GenerateToken() (string, error) {
|
||||||
tokenID, err := randBytes(TokenIDBytes)
|
tokenID, err := randBytes(TokenIDBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := randBytes(TokenSecretBytes)
|
tokenSecret, err := randBytes(TokenSecretBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.ID = strings.ToLower(tokenID)
|
return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil
|
||||||
d.Secret = strings.ToLower(token)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTokenID tries and parse a valid token ID from a string.
|
// ParseTokenID tries and parse a valid token ID from a string.
|
||||||
|
@ -117,15 +117,19 @@ func TestValidateToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateToken(t *testing.T) {
|
func TestGenerateToken(t *testing.T) {
|
||||||
td := &kubeadmapi.TokenDiscovery{}
|
token, err := GenerateToken()
|
||||||
if err := GenerateToken(td); err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
|
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
|
||||||
}
|
}
|
||||||
if len(td.ID) != 6 {
|
tokenID, tokenSecret, err := ParseToken(token)
|
||||||
t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(td.ID))
|
if err != nil {
|
||||||
|
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
|
||||||
}
|
}
|
||||||
if len(td.Secret) != 16 {
|
if len(tokenID) != 6 {
|
||||||
t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(td.Secret))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ cmd/kube-discovery
|
|||||||
cmd/kube-proxy
|
cmd/kube-proxy
|
||||||
cmd/kubeadm
|
cmd/kubeadm
|
||||||
cmd/kubeadm/app/apis/kubeadm/install
|
cmd/kubeadm/app/apis/kubeadm/install
|
||||||
|
cmd/kubeadm/app/discovery/https
|
||||||
cmd/kubeadm/app/phases/apiconfig
|
cmd/kubeadm/app/phases/apiconfig
|
||||||
cmd/kubeadm/app/phases/certs
|
cmd/kubeadm/app/phases/certs
|
||||||
cmd/kubeadm/app/phases/kubeconfig
|
cmd/kubeadm/app/phases/kubeconfig
|
||||||
|
@ -64,6 +64,7 @@ bench-pods
|
|||||||
bench-quiet
|
bench-quiet
|
||||||
bench-tasks
|
bench-tasks
|
||||||
bench-workers
|
bench-workers
|
||||||
|
bind-addrsse
|
||||||
bind-address
|
bind-address
|
||||||
bind-pods-burst
|
bind-pods-burst
|
||||||
bind-pods-qps
|
bind-pods-qps
|
||||||
@ -663,6 +664,7 @@ tls-cert-file
|
|||||||
tls-private-key-file
|
tls-private-key-file
|
||||||
tls-sni-cert-key
|
tls-sni-cert-key
|
||||||
token-auth-file
|
token-auth-file
|
||||||
|
token-ttl
|
||||||
to-version
|
to-version
|
||||||
to-version
|
to-version
|
||||||
ttl-keys-prefix
|
ttl-keys-prefix
|
||||||
|
Loading…
Reference in New Issue
Block a user