mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +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"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -35,7 +34,6 @@ func SetEnvParams() *EnvParams {
|
||||
"host_etcd_path": "/var/lib/etcd",
|
||||
"hyperkube_image": "",
|
||||
"repo_prefix": "gcr.io/google_containers",
|
||||
"discovery_image": fmt.Sprintf("gcr.io/google_containers/kube-discovery-%s:%s", runtime.GOARCH, "1.0"),
|
||||
"etcd_image": "",
|
||||
}
|
||||
|
||||
@ -50,7 +48,6 @@ func SetEnvParams() *EnvParams {
|
||||
HostEtcdPath: path.Clean(envParams["host_etcd_path"]),
|
||||
HyperkubeImage: envParams["hyperkube_image"],
|
||||
RepositoryPrefix: envParams["repo_prefix"],
|
||||
DiscoveryImage: envParams["discovery_image"],
|
||||
EtcdImage: envParams["etcd_image"],
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} {
|
||||
obj.Networking.ServiceSubnet = "foo"
|
||||
obj.Networking.DNSDomain = "foo"
|
||||
obj.AuthorizationMode = "foo"
|
||||
obj.Discovery.Token = &kubeadm.TokenDiscovery{}
|
||||
obj.CertificatesDir = "foo"
|
||||
obj.APIServerCertSANs = []string{}
|
||||
obj.Token = "foo"
|
||||
},
|
||||
func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(obj)
|
||||
|
@ -46,7 +46,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&MasterConfiguration{},
|
||||
&NodeConfiguration{},
|
||||
&ClusterInfo{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@ -25,7 +27,6 @@ type EnvParams struct {
|
||||
HostEtcdPath string
|
||||
HyperkubeImage string
|
||||
RepositoryPrefix string
|
||||
DiscoveryImage string
|
||||
EtcdImage string
|
||||
}
|
||||
|
||||
@ -33,13 +34,15 @@ type MasterConfiguration struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
API API
|
||||
Discovery Discovery
|
||||
Etcd Etcd
|
||||
Networking Networking
|
||||
KubernetesVersion string
|
||||
CloudProvider string
|
||||
AuthorizationMode string
|
||||
|
||||
Token string
|
||||
TokenTTL time.Duration
|
||||
|
||||
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
|
||||
// controller manager are managed by Kubernetes itself. This option is likely to
|
||||
// become the default in the future.
|
||||
@ -62,20 +65,6 @@ type API struct {
|
||||
BindPort int32
|
||||
}
|
||||
|
||||
type Discovery struct {
|
||||
HTTPS *HTTPSDiscovery
|
||||
File *FileDiscovery
|
||||
Token *TokenDiscovery
|
||||
}
|
||||
|
||||
type HTTPSDiscovery struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
type FileDiscovery struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
type TokenDiscovery struct {
|
||||
ID string
|
||||
Secret string
|
||||
@ -106,11 +95,3 @@ type NodeConfiguration struct {
|
||||
TLSBootstrapToken string
|
||||
Token string
|
||||
}
|
||||
|
||||
// ClusterInfo TODO add description
|
||||
type ClusterInfo struct {
|
||||
metav1.TypeMeta
|
||||
// TODO(phase1+) this may become simply `api.Config`
|
||||
CertificateAuthorities []string
|
||||
Endpoints []string
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ go_library(
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
|
@ -20,14 +20,15 @@ import (
|
||||
"net/url"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceDNSDomain = "cluster.local"
|
||||
DefaultServicesSubnet = "10.96.0.0/12"
|
||||
DefaultKubernetesVersion = "latest"
|
||||
DefaultKubernetesVersion = "latest-1.6"
|
||||
// This is only for clusters without internet, were the latest stable version can't be determined
|
||||
DefaultKubernetesFallbackVersion = "v1.6.0-alpha.1"
|
||||
DefaultKubernetesFallbackVersion = "v1.6.0-beta.1"
|
||||
DefaultAPIBindPort = 6443
|
||||
DefaultDiscoveryBindPort = 9898
|
||||
DefaultAuthorizationMode = "RBAC"
|
||||
@ -60,10 +61,6 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
|
||||
obj.Networking.DNSDomain = DefaultServiceDNSDomain
|
||||
}
|
||||
|
||||
if obj.Discovery.Token == nil && obj.Discovery.File == nil && obj.Discovery.HTTPS == nil {
|
||||
obj.Discovery.Token = &TokenDiscovery{}
|
||||
}
|
||||
|
||||
if obj.AuthorizationMode == "" {
|
||||
obj.AuthorizationMode = DefaultAuthorizationMode
|
||||
}
|
||||
@ -71,6 +68,10 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
|
||||
if obj.CertificatesDir == "" {
|
||||
obj.CertificatesDir = DefaultCertificatesDir
|
||||
}
|
||||
|
||||
if obj.TokenTTL == 0 {
|
||||
obj.TokenTTL = constants.DefaultTokenDuration
|
||||
}
|
||||
}
|
||||
|
||||
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
|
||||
|
@ -47,7 +47,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&MasterConfiguration{},
|
||||
&NodeConfiguration{},
|
||||
&ClusterInfo{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@ -24,13 +26,15 @@ type MasterConfiguration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
API API `json:"api"`
|
||||
Discovery Discovery `json:"discovery"`
|
||||
Etcd Etcd `json:"etcd"`
|
||||
Networking Networking `json:"networking"`
|
||||
KubernetesVersion string `json:"kubernetesVersion"`
|
||||
CloudProvider string `json:"cloudProvider"`
|
||||
AuthorizationMode string `json:"authorizationMode"`
|
||||
|
||||
Token string `json:"token"`
|
||||
TokenTTL time.Duration `json:"tokenTTL"`
|
||||
|
||||
// SelfHosted enables an alpha deployment type where the apiserver, scheduler, and
|
||||
// controller manager are managed by Kubernetes itself. This option is likely to
|
||||
// become the default in the future.
|
||||
@ -53,20 +57,6 @@ type API struct {
|
||||
BindPort int32 `json:"bindPort"`
|
||||
}
|
||||
|
||||
type Discovery struct {
|
||||
HTTPS *HTTPSDiscovery `json:"https"`
|
||||
File *FileDiscovery `json:"file"`
|
||||
Token *TokenDiscovery `json:"token"`
|
||||
}
|
||||
|
||||
type HTTPSDiscovery struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type FileDiscovery struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type TokenDiscovery struct {
|
||||
ID string `json:"id"`
|
||||
Secret string `json:"secret"`
|
||||
@ -96,11 +86,3 @@ type NodeConfiguration struct {
|
||||
TLSBootstrapToken string `json:"tlsBootstrapToken"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ClusterInfo TODO add description
|
||||
type ClusterInfo struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// TODO(phase1+) this may become simply `api.Config`
|
||||
CertificateAuthorities []string `json:"certificateAuthorities"`
|
||||
Endpoints []string `json:"endpoints"`
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList
|
||||
allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...)
|
||||
allErrs = append(allErrs, ValidateAPIServerCertSANs(c.APIServerCertSANs, field.NewPath("cert-altnames"))...)
|
||||
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
|
||||
allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@ -87,6 +88,14 @@ func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.
|
||||
allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...)
|
||||
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...)
|
||||
allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, fldPath)...)
|
||||
|
||||
if len(c.DiscoveryToken) != 0 {
|
||||
allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, fldPath)...)
|
||||
}
|
||||
if len(c.DiscoveryFile) != 0 {
|
||||
allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, fldPath)...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@ -172,41 +172,21 @@ func TestValidateMasterConfiguration(t *testing.T) {
|
||||
}{
|
||||
{&kubeadm.MasterConfiguration{}, false},
|
||||
{&kubeadm.MasterConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
|
||||
},
|
||||
AuthorizationMode: "RBAC",
|
||||
Networking: kubeadm.Networking{
|
||||
ServiceSubnet: "10.96.0.1/12",
|
||||
DNSDomain: "cluster.local",
|
||||
},
|
||||
CertificatesDir: "/some/cert/dir",
|
||||
}, true},
|
||||
}, false},
|
||||
{&kubeadm.MasterConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
File: &kubeadm.FileDiscovery{Path: "foo"},
|
||||
},
|
||||
AuthorizationMode: "RBAC",
|
||||
Networking: kubeadm.Networking{
|
||||
ServiceSubnet: "10.96.0.1/12",
|
||||
DNSDomain: "cluster.local",
|
||||
},
|
||||
CertificatesDir: "/some/other/cert/dir",
|
||||
}, true},
|
||||
{&kubeadm.MasterConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
Token: &kubeadm.TokenDiscovery{
|
||||
ID: "abcdef",
|
||||
Secret: "1234567890123456",
|
||||
Addresses: []string{"foobar"},
|
||||
},
|
||||
},
|
||||
AuthorizationMode: "RBAC",
|
||||
Networking: kubeadm.Networking{
|
||||
ServiceSubnet: "10.96.0.1/12",
|
||||
DNSDomain: "cluster.local",
|
||||
},
|
||||
CertificatesDir: "/yet/another/cert/dir",
|
||||
Token: "abcdef.0123456789abcdef",
|
||||
}, true},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
|
@ -30,9 +30,7 @@ import (
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
var (
|
||||
minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion)
|
||||
)
|
||||
var minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion)
|
||||
|
||||
func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
@ -73,23 +71,11 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)")
|
||||
}
|
||||
|
||||
// Validate token if any, otherwise generate
|
||||
if cfg.Discovery.Token != nil {
|
||||
if cfg.Discovery.Token.ID != "" && cfg.Discovery.Token.Secret != "" {
|
||||
fmt.Printf("[init] A token has been provided, validating [%s]\n", tokenutil.BearerToken(cfg.Discovery.Token))
|
||||
if valid, err := tokenutil.ValidateToken(cfg.Discovery.Token); valid == false {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[init] A token has not been provided, generating one")
|
||||
if err := tokenutil.GenerateToken(cfg.Discovery.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If there aren't any addresses specified, default to the first advertised address which can be user-provided or the default network interface's IP address
|
||||
if len(cfg.Discovery.Token.Addresses) == 0 {
|
||||
cfg.Discovery.Token.Addresses = []string{fmt.Sprintf("%s:%d", cfg.API.AdvertiseAddress, kubeadmapiext.DefaultDiscoveryBindPort)}
|
||||
if cfg.Token == "" {
|
||||
var err error
|
||||
cfg.Token, err = tokenutil.GenerateToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't generate random token: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,6 @@ import (
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
|
||||
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
|
||||
addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons"
|
||||
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
|
||||
@ -40,7 +39,6 @@ import (
|
||||
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -56,16 +54,14 @@ var (
|
||||
|
||||
You can now join any number of machines by running the following on each node:
|
||||
|
||||
kubeadm join --discovery %s
|
||||
kubeadm join --token %s %s:%d
|
||||
`)
|
||||
)
|
||||
|
||||
// NewCmdInit returns "kubeadm init" command.
|
||||
func NewCmdInit(out io.Writer) *cobra.Command {
|
||||
versioned := &kubeadmapiext.MasterConfiguration{}
|
||||
api.Scheme.Default(versioned)
|
||||
cfg := kubeadmapi.MasterConfiguration{}
|
||||
api.Scheme.Convert(versioned, &cfg, nil)
|
||||
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||
api.Scheme.Default(cfg)
|
||||
|
||||
var cfgPath string
|
||||
var skipPreFlight bool
|
||||
@ -73,7 +69,11 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
||||
Use: "init",
|
||||
Short: "Run this in order to set up the Kubernetes master",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
i, err := NewInit(cfgPath, &cfg, skipPreFlight)
|
||||
api.Scheme.Default(cfg)
|
||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||
api.Scheme.Convert(cfg, internalcfg, nil)
|
||||
|
||||
i, err := NewInit(cfgPath, internalcfg, skipPreFlight)
|
||||
kubeadmutil.CheckErr(err)
|
||||
kubeadmutil.CheckErr(i.Validate())
|
||||
kubeadmutil.CheckErr(i.Run(out))
|
||||
@ -113,17 +113,20 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
||||
`Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.`,
|
||||
)
|
||||
|
||||
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file")
|
||||
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
|
||||
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&skipPreFlight, "skip-preflight-checks", skipPreFlight,
|
||||
"Skip preflight checks normally run before modifying the system",
|
||||
)
|
||||
|
||||
cmd.PersistentFlags().Var(
|
||||
discovery.NewDiscoveryValue(&cfg.Discovery), "discovery",
|
||||
"The discovery method kubeadm will use for connecting nodes to the master",
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&cfg.Token, "token", cfg.Token,
|
||||
"The token to use for establishing bidirectional trust between nodes and masters.")
|
||||
|
||||
cmd.PersistentFlags().DurationVar(
|
||||
&cfg.TokenTTL, "token-ttl", cfg.TokenTTL,
|
||||
"The duration before the bootstrap token is automatically deleted. 0 means 'never expires'.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -199,20 +202,13 @@ func (i *Init) Run(out io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: It's not great to have an exception for token here, but necessary because the apiserver doesn't handle this properly in the API yet
|
||||
// but relies on files on disk for now, which is daunting.
|
||||
if i.cfg.Discovery.Token != nil {
|
||||
if err := tokenphase.CreateTokenAuthFile(i.cfg.CertificatesDir, tokenutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 3: Bootstrap the control plane
|
||||
if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := kubemaster.CreateClientAndWaitForAPI(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName))
|
||||
adminKubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)
|
||||
client, err := kubemaster.CreateClientAndWaitForAPI(adminKubeConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -225,22 +221,22 @@ func (i *Init) Run(out io.Writer) error {
|
||||
if i.cfg.SelfHosted {
|
||||
// Temporary control plane is up, now we create our self hosted control
|
||||
// plane components and remove the static manifests:
|
||||
fmt.Println("[init] Creating self-hosted control plane...")
|
||||
fmt.Println("[self-hosted] Creating self-hosted control plane...")
|
||||
if err := kubemaster.CreateSelfHostedControlPlane(i.cfg, client); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 4: Set up the bootstrap tokens
|
||||
if i.cfg.Discovery.Token != nil {
|
||||
fmt.Printf("[token-discovery] Using token: %s\n", tokenutil.BearerToken(i.cfg.Discovery.Token))
|
||||
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil {
|
||||
return err
|
||||
}
|
||||
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
|
||||
if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Discovery.Token, false, kubeadmconstants.DefaultTokenDuration, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[token] Using token: %s\n", i.cfg.Token)
|
||||
|
||||
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
|
||||
if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tokenphase.CreateBootstrapConfigMap(adminKubeConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// PHASE 5: Install and deploy all addons, and configure things as necessary
|
||||
@ -260,11 +256,6 @@ func (i *Init) Run(out io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, initDoneMsgf, path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), generateJoinArgs(i.cfg))
|
||||
fmt.Fprintf(out, initDoneMsgf, path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), i.cfg.Token, i.cfg.API.AdvertiseAddress, i.cfg.API.BindPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateJoinArgs generates kubeadm join arguments
|
||||
func generateJoinArgs(cfg *kubeadmapi.MasterConfiguration) string {
|
||||
return discovery.NewDiscoveryValue(&cfg.Discovery).String()
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
@ -90,9 +91,11 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
|
||||
`),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cfg.DiscoveryTokenAPIServers = args
|
||||
|
||||
api.Scheme.Default(cfg)
|
||||
internalcfg := &kubeadmapi.NodeConfiguration{}
|
||||
api.Scheme.Convert(cfg, internalcfg, nil)
|
||||
|
||||
j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight)
|
||||
kubeadmutil.CheckErr(err)
|
||||
kubeadmutil.CheckErr(j.Validate())
|
||||
@ -174,7 +177,19 @@ func (j *Join) Run(out io.Writer) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kubenode.PerformTLSBootstrap(cfg); err != nil {
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := kubeconfigutil.KubeConfigToClientSet(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kubenode.ValidateAPIServer(client); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kubenode.PerformTLSBootstrap(cfg, hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -189,36 +189,37 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command {
|
||||
// RunCreateToken generates a new bootstrap token and stores it as a secret on the server.
|
||||
func RunCreateToken(out io.Writer, client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error {
|
||||
|
||||
td := &kubeadmapi.TokenDiscovery{}
|
||||
var err error
|
||||
if len(token) == 0 {
|
||||
err = tokenutil.GenerateToken(td)
|
||||
var err error
|
||||
token, err = tokenutil.GenerateToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
td.ID, td.Secret, err = tokenutil.ParseToken(token)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
_, _, err := tokenutil.ParseToken(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Validate usages here so we don't allow something unsupported
|
||||
err = tokenphase.CreateNewToken(client, td, tokenDuration, usages, description)
|
||||
err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, tokenutil.BearerToken(td))
|
||||
fmt.Fprintln(out, token)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunGenerateToken just generates a random token for the user
|
||||
func RunGenerateToken(out io.Writer) error {
|
||||
td := &kubeadmapi.TokenDiscovery{}
|
||||
err := tokenutil.GenerateToken(td)
|
||||
token, err := tokenutil.GenerateToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, tokenutil.BearerToken(td))
|
||||
fmt.Fprintln(out, token)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ const (
|
||||
SchedulerKubeConfigFileName = "scheduler.conf"
|
||||
|
||||
// Important: a "v"-prefix shouldn't exist here; semver doesn't allow that
|
||||
MinimumControlPlaneVersion = "1.6.0-alpha.2"
|
||||
MinimumControlPlaneVersion = "1.6.0-beta.1"
|
||||
|
||||
// Some well-known users and groups in the core Kubernetes authorization system
|
||||
|
||||
@ -72,6 +72,8 @@ const (
|
||||
|
||||
// APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation
|
||||
APICallRetryInterval = 500 * time.Millisecond
|
||||
// DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery
|
||||
DiscoveryRetryInterval = 5 * time.Second
|
||||
|
||||
// Minimum amount of nodes the Service subnet should allow.
|
||||
// We need at least ten, because the DNS service is always at the tenth cluster clusterIP
|
||||
@ -85,15 +87,6 @@ const (
|
||||
// It's copied over to kubeadm until it's merged in core: https://github.com/kubernetes/kubernetes/pull/39112
|
||||
LabelNodeRoleMaster = "node-role.kubernetes.io/master"
|
||||
|
||||
// CSVTokenBootstrapUser is currently the user the bootstrap token in the .csv file
|
||||
// TODO: This should change to something more official and supported
|
||||
// TODO: Prefix with kubeadm prefix
|
||||
CSVTokenBootstrapUser = "kubeadm-node-csr"
|
||||
// CSVTokenBootstrapGroup specifies the group the tokens in the .csv file will belong to
|
||||
CSVTokenBootstrapGroup = "kubeadm:kubelet-bootstrap"
|
||||
// The file name of the tokens file that can be used for bootstrapping
|
||||
CSVTokenFileName = "tokens.csv"
|
||||
|
||||
// MinExternalEtcdVersion indicates minimum external etcd version which kubeadm supports
|
||||
MinExternalEtcdVersion = "3.0.14"
|
||||
|
||||
|
@ -10,36 +10,24 @@ load(
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"discovery.go",
|
||||
"flags.go",
|
||||
],
|
||||
srcs = ["discovery.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/discovery/file:go_default_library",
|
||||
"//cmd/kubeadm/app/discovery/https:go_default_library",
|
||||
"//cmd/kubeadm/app/discovery/token:go_default_library",
|
||||
"//cmd/kubeadm/app/node:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//vendor:github.com/spf13/pflag",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"discovery_test.go",
|
||||
"flags_test.go",
|
||||
],
|
||||
srcs = ["discovery_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//vendor:github.com/davecgh/go-spew/spew",
|
||||
],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
|
@ -18,27 +18,47 @@ package discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/https"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/token"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
)
|
||||
|
||||
// For identifies and executes the desired discovery mechanism.
|
||||
func For(d *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
|
||||
const TokenUser = "tls-bootstrap-token-user"
|
||||
|
||||
// For returns a KubeConfig object that can be used for doing the TLS Bootstrap with the right credentials
|
||||
// Also, before returning anything, it makes sure it can trust the API Server
|
||||
func For(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
|
||||
// TODO: Print summary info about the CA certificate, along with the the checksum signature
|
||||
// we also need an ability for the user to configure the client to validate received CA cert against a checksum
|
||||
clusterinfo, err := GetValidatedClusterInfoObject(cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't validate the identity of the API Server: %v", err)
|
||||
}
|
||||
|
||||
return kubeconfigutil.CreateWithToken(
|
||||
clusterinfo.Server,
|
||||
"kubernetes",
|
||||
TokenUser,
|
||||
clusterinfo.CertificateAuthorityData,
|
||||
cfg.TLSBootstrapToken,
|
||||
), nil
|
||||
}
|
||||
|
||||
// GetValidatedClusterInfoObject returns a validated Cluster object that specifies where the cluster is and the CA cert to trust
|
||||
func GetValidatedClusterInfoObject(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) {
|
||||
switch {
|
||||
case len(d.DiscoveryFile) != 0:
|
||||
if isHTTPSURL(d.DiscoveryFile) {
|
||||
return runHTTPSDiscovery(d.DiscoveryFile)
|
||||
case len(cfg.DiscoveryFile) != 0:
|
||||
if isHTTPSURL(cfg.DiscoveryFile) {
|
||||
return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
|
||||
}
|
||||
return runFileDiscovery(d.DiscoveryFile)
|
||||
case len(d.DiscoveryToken) != 0:
|
||||
return runTokenDiscovery(d.DiscoveryToken, d.DiscoveryTokenAPIServers)
|
||||
return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
|
||||
case len(cfg.DiscoveryToken) != 0:
|
||||
return token.RetrieveValidatedClusterInfo(cfg.DiscoveryToken, cfg.DiscoveryTokenAPIServers)
|
||||
default:
|
||||
return nil, fmt.Errorf("couldn't find a valid discovery configuration.")
|
||||
}
|
||||
@ -49,48 +69,3 @@ func isHTTPSURL(s string) bool {
|
||||
u, err := url.Parse(s)
|
||||
return err == nil && u.Scheme == "https"
|
||||
}
|
||||
|
||||
// runFileDiscovery executes file-based discovery.
|
||||
func runFileDiscovery(fd string) (*clientcmdapi.Config, error) {
|
||||
return clientcmd.LoadFromFile(fd)
|
||||
}
|
||||
|
||||
// runHTTPSDiscovery executes HTTPS-based discovery.
|
||||
func runHTTPSDiscovery(hd string) (*clientcmdapi.Config, error) {
|
||||
response, err := http.Get(hd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
kubeconfig, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clientcmd.Load(kubeconfig)
|
||||
}
|
||||
|
||||
// runTokenDiscovery executes token-based discovery.
|
||||
func runTokenDiscovery(td string, m []string) (*clientcmdapi.Config, error) {
|
||||
id, secret, err := tokenutil.ParseToken(td)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &kubeadmapi.TokenDiscovery{ID: id, Secret: secret, Addresses: m}
|
||||
|
||||
if valid, err := tokenutil.ValidateToken(t); valid == false {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg, err := kubenode.EstablishMasterConnection(t, clusterInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
@ -11,7 +11,17 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["file.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
|
@ -17,13 +17,114 @@ limitations under the License.
|
||||
package file
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
|
||||
func Parse(u *url.URL, c *kubeadm.Discovery) {
|
||||
c.File = &kubeadm.FileDiscovery{
|
||||
Path: u.Path,
|
||||
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
|
||||
// securely to the API Server using the provided CA cert and
|
||||
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
|
||||
func RetrieveValidatedClusterInfo(filepath string) (*clientcmdapi.Cluster, error) {
|
||||
clusterinfo, err := clientcmd.LoadFromFile(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ValidateClusterInfo(clusterinfo)
|
||||
}
|
||||
|
||||
// ValidateClusterInfo connects to the API Server and makes sure it can talk
|
||||
// securely to the API Server using the provided CA cert and
|
||||
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
|
||||
func ValidateClusterInfo(clusterinfo *clientcmdapi.Config) (*clientcmdapi.Cluster, error) {
|
||||
err := validateClusterInfoKubeConfig(clusterinfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is the cluster object we've got from the cluster-info KubeConfig file
|
||||
defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo)
|
||||
|
||||
// Create a new kubeconfig object from the given, just copy over the server and the CA cert
|
||||
// We do this in order to not pick up other possible misconfigurations in the clusterinfo file
|
||||
configFromClusterInfo := kubeconfigutil.CreateBasic(
|
||||
defaultCluster.Server,
|
||||
"kubernetes",
|
||||
"", // no user provided
|
||||
defaultCluster.CertificateAuthorityData,
|
||||
)
|
||||
|
||||
client, err := kubeconfigutil.KubeConfigToClientSet(configFromClusterInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", defaultCluster.Server)
|
||||
|
||||
var clusterinfoCM *v1.ConfigMap
|
||||
wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) {
|
||||
var err error
|
||||
clusterinfoCM, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsForbidden(err) {
|
||||
// If the request is unauthorized, the cluster admin has not granted access to the cluster info configmap for unauthenicated users
|
||||
// In that case, trust the cluster admin and do not refresh the cluster-info credentials
|
||||
fmt.Printf("[discovery] Could not access the %s ConfigMap for refreshing the cluster-info information, but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo)
|
||||
return true, nil
|
||||
} else {
|
||||
fmt.Printf("[discovery] Failed to validate the API Server's identity, will try again: [%v]\n", err)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
// If we couldn't fetch the cluster-info ConfigMap, just return the cluster-info object the user provided
|
||||
if clusterinfoCM == nil {
|
||||
return defaultCluster, nil
|
||||
}
|
||||
|
||||
// We somehow got hold of the ConfigMap, try to read some data from it. If we can't, fallback on the user-provided file
|
||||
refreshedBaseKubeConfig, err := tryParseClusterInfoFromConfigMap(clusterinfoCM)
|
||||
if err != nil {
|
||||
fmt.Printf("[discovery] The %s ConfigMap isn't set up properly (%v), but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo, err)
|
||||
return defaultCluster, nil
|
||||
}
|
||||
|
||||
fmt.Println("[discovery] Synced cluster-info information from the API Server so we have got the latest information")
|
||||
// In an HA world in the future, this will make more sense, because now we've got new information, possibly about new API Servers to talk to
|
||||
return kubeconfigutil.GetClusterFromKubeConfig(refreshedBaseKubeConfig), nil
|
||||
}
|
||||
|
||||
// tryParseClusterInfoFromConfigMap tries to parse a kubeconfig file from a ConfigMap key
|
||||
func tryParseClusterInfoFromConfigMap(cm *v1.ConfigMap) (*clientcmdapi.Config, error) {
|
||||
kubeConfigString, ok := cm.Data[bootstrapapi.KubeConfigKey]
|
||||
if !ok || len(kubeConfigString) == 0 {
|
||||
return nil, fmt.Errorf("no %s key in ConfigMap", bootstrapapi.KubeConfigKey)
|
||||
}
|
||||
parsedKubeConfig, err := clientcmd.Load([]byte(kubeConfigString))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s ConfigMap: %v", bootstrapapi.ConfigMapClusterInfo, err)
|
||||
}
|
||||
return parsedKubeConfig, nil
|
||||
}
|
||||
|
||||
// validateClusterInfoKubeConfig makes sure the user-provided cluster-info KubeConfig file is valid
|
||||
func validateClusterInfoKubeConfig(clusterinfo *clientcmdapi.Config) error {
|
||||
if len(clusterinfo.Clusters) < 1 {
|
||||
return fmt.Errorf("the provided cluster-info KubeConfig file must have at least one Cluster defined")
|
||||
}
|
||||
defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo)
|
||||
if defaultCluster == nil {
|
||||
return fmt.Errorf("the provided cluster-info KubeConfig file must have an unnamed Cluster or a CurrentContext that specifies a non-nil Cluster")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -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",
|
||||
srcs = ["https.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/discovery/file:go_default_library",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
|
@ -17,13 +17,32 @@ limitations under the License.
|
||||
package https
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
|
||||
)
|
||||
|
||||
func Parse(u *url.URL, c *kubeadm.Discovery) {
|
||||
c.HTTPS = &kubeadm.HTTPSDiscovery{
|
||||
URL: u.String(),
|
||||
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
|
||||
// securely to the API Server using the provided CA cert and
|
||||
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
|
||||
func RetrieveValidatedClusterInfo(httpsURL string) (*clientcmdapi.Cluster, error) {
|
||||
response, err := http.Get(httpsURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
kubeconfig, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clusterinfo, err := clientcmd.Load(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file.ValidateClusterInfo(clusterinfo)
|
||||
}
|
||||
|
@ -5,13 +5,25 @@ licenses(["notice"])
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["token.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/controller/bootstrap:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
@ -26,3 +38,14 @@ filegroup(
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["token_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
],
|
||||
)
|
||||
|
@ -17,29 +17,117 @@ limitations under the License.
|
||||
package token
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
"k8s.io/kubernetes/pkg/controller/bootstrap"
|
||||
)
|
||||
|
||||
func Parse(u *url.URL, c *kubeadm.Discovery) {
|
||||
var (
|
||||
hosts []string
|
||||
tokenID, token string
|
||||
)
|
||||
if u.Host != "" {
|
||||
hosts = strings.Split(u.Host, ",")
|
||||
const BootstrapUser = "token-bootstrap-client"
|
||||
|
||||
// RetrieveValidatedClusterInfo connects to the API Server and tries to fetch the cluster-info ConfigMap
|
||||
// It then makes sure it can trust the API Server by looking at the JWS-signed tokens
|
||||
func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers []string) (*clientcmdapi.Cluster, error) {
|
||||
|
||||
tokenId, tokenSecret, err := tokenutil.ParseToken(discoveryToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.User != nil {
|
||||
if p, ok := u.User.Password(); ok {
|
||||
tokenID = u.User.Username()
|
||||
token = p
|
||||
|
||||
// The function below runs for every endpoint, and all endpoints races with each other.
|
||||
// The endpoint that wins the race and completes the task first gets its kubeconfig returned below
|
||||
baseKubeConfig := runForEndpointsAndReturnFirst(tokenAPIServers, func(endpoint string) (*clientcmdapi.Config, error) {
|
||||
|
||||
bootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint)
|
||||
clusterName := bootstrapConfig.Contexts[bootstrapConfig.CurrentContext].Cluster
|
||||
|
||||
client, err := kubeconfigutil.KubeConfigToClientSet(bootstrapConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c.Token = &kubeadm.TokenDiscovery{
|
||||
ID: tokenID,
|
||||
Secret: token,
|
||||
Addresses: hosts,
|
||||
}
|
||||
|
||||
fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", bootstrapConfig.Clusters[clusterName].Server)
|
||||
|
||||
var clusterinfo *v1.ConfigMap
|
||||
wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) {
|
||||
var err error
|
||||
clusterinfo, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("[discovery] Failed to request cluster info, will try again: [%s]\n", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
kubeConfigString, ok := clusterinfo.Data[bootstrapapi.KubeConfigKey]
|
||||
if !ok || len(kubeConfigString) == 0 {
|
||||
return nil, fmt.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect", bootstrapapi.KubeConfigKey, bootstrapapi.ConfigMapClusterInfo)
|
||||
}
|
||||
detachedJWSToken, ok := clusterinfo.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenId]
|
||||
if !ok || len(detachedJWSToken) == 0 {
|
||||
return nil, fmt.Errorf("there is no JWS signed token in the %s ConfigMap. This token id %q is invalid for this cluster, can't connect", bootstrapapi.ConfigMapClusterInfo, tokenId)
|
||||
}
|
||||
if !bootstrap.DetachedTokenIsValid(detachedJWSToken, kubeConfigString, tokenId, tokenSecret) {
|
||||
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server")
|
||||
}
|
||||
|
||||
finalConfig, err := clientcmd.Load([]byte(kubeConfigString))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s configmap: %v", bootstrapapi.ConfigMapClusterInfo, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[discovery] Cluster info signature and contents are valid, will use API Server %q\n", bootstrapConfig.Clusters[clusterName].Server)
|
||||
return finalConfig, nil
|
||||
})
|
||||
|
||||
return kubeconfigutil.GetClusterFromKubeConfig(baseKubeConfig), nil
|
||||
}
|
||||
|
||||
// buildInsecureBootstrapKubeConfig makes a KubeConfig object that connects insecurely to the API Server for bootstrapping purposes
|
||||
func buildInsecureBootstrapKubeConfig(endpoint string) *clientcmdapi.Config {
|
||||
masterEndpoint := fmt.Sprintf("https://%s", endpoint)
|
||||
clusterName := "kubernetes"
|
||||
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clusterName, BootstrapUser, []byte{})
|
||||
bootstrapConfig.Clusters[clusterName].InsecureSkipTLSVerify = true
|
||||
return bootstrapConfig
|
||||
}
|
||||
|
||||
// runForEndpointsAndReturnFirst loops the endpoints slice and let's the endpoints race for connecting to the master
|
||||
func runForEndpointsAndReturnFirst(endpoints []string, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) *clientcmdapi.Config {
|
||||
stopChan := make(chan struct{})
|
||||
var resultingKubeConfig *clientcmdapi.Config
|
||||
var once sync.Once
|
||||
var wg sync.WaitGroup
|
||||
for _, endpoint := range endpoints {
|
||||
wg.Add(1)
|
||||
go func(apiEndpoint string) {
|
||||
defer wg.Done()
|
||||
wait.Until(func() {
|
||||
fmt.Printf("[discovery] Trying to connect to API Server %q\n", apiEndpoint)
|
||||
cfg, err := fetchKubeConfigFunc(apiEndpoint)
|
||||
if err != nil {
|
||||
fmt.Printf("[discovery] Failed to connect to API Server %q: %v\n", apiEndpoint, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("[discovery] Successfully established connection with API Server %q\n", apiEndpoint)
|
||||
|
||||
// connection established, stop all wait threads
|
||||
once.Do(func() {
|
||||
close(stopChan)
|
||||
resultingKubeConfig = cfg
|
||||
})
|
||||
}, constants.DiscoveryRetryInterval, stopChan)
|
||||
}(endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
return resultingKubeConfig
|
||||
}
|
||||
|
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",
|
||||
srcs = [
|
||||
"apiclient.go",
|
||||
"discovery.go",
|
||||
"manifests.go",
|
||||
"selfhosted.go",
|
||||
"templates.go",
|
||||
@ -24,6 +23,7 @@ go_library(
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//vendor:github.com/ghodss/yaml",
|
||||
@ -37,7 +37,6 @@ go_library(
|
||||
"//vendor:k8s.io/client-go/pkg/api",
|
||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||
"//vendor:k8s.io/client-go/pkg/apis/extensions/v1beta1",
|
||||
"//vendor:k8s.io/client-go/util/cert",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
)
|
||||
@ -301,20 +302,20 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
|
||||
}
|
||||
|
||||
defaultArguments := map[string]string{
|
||||
"insecure-port": "0",
|
||||
"admission-control": kubeadmconstants.DefaultAdmissionControl,
|
||||
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
|
||||
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
|
||||
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
|
||||
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
|
||||
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
|
||||
"kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
|
||||
"token-auth-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CSVTokenFileName),
|
||||
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
|
||||
"allow-privileged": "true",
|
||||
"storage-backend": "etcd3",
|
||||
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
|
||||
"insecure-port": "0",
|
||||
"admission-control": kubeadmconstants.DefaultAdmissionControl,
|
||||
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
|
||||
"service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
|
||||
"client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||
"tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
|
||||
"tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
|
||||
"kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
|
||||
"kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
|
||||
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
|
||||
"allow-privileged": "true",
|
||||
"experimental-bootstrap-token-auth": "true",
|
||||
"storage-backend": "etcd3",
|
||||
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
|
||||
// add options to configure the front proxy. Without the generated client cert, this will never be useable
|
||||
// so add it unconditionally with recommended values
|
||||
"requestheader-username-headers": "X-Remote-User",
|
||||
@ -379,8 +380,9 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted
|
||||
"service-account-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
|
||||
"cluster-signing-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||
"cluster-signing-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
|
||||
"insecure-experimental-approve-all-kubelet-csrs-for-group": kubeadmconstants.CSVTokenBootstrapGroup,
|
||||
"insecure-experimental-approve-all-kubelet-csrs-for-group": bootstrapapi.BootstrapGroup,
|
||||
"use-service-account-credentials": "true",
|
||||
"controllers": "*,bootstrapsigner,tokencleaner",
|
||||
}
|
||||
|
||||
command = getComponentBaseCommand(controllerManager)
|
||||
|
@ -397,11 +397,11 @@ func TestGetAPIServerCommand(t *testing.T) {
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--token-auth-file=" + testCertsDir + "/tokens.csv",
|
||||
fmt.Sprintf("--secure-port=%d", 123),
|
||||
"--allow-privileged=true",
|
||||
"--storage-backend=etcd3",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
@ -429,11 +429,11 @@ func TestGetAPIServerCommand(t *testing.T) {
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--token-auth-file=" + testCertsDir + "/tokens.csv",
|
||||
fmt.Sprintf("--secure-port=%d", 123),
|
||||
"--allow-privileged=true",
|
||||
"--storage-backend=etcd3",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
@ -462,11 +462,11 @@ func TestGetAPIServerCommand(t *testing.T) {
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--token-auth-file=" + testCertsDir + "/tokens.csv",
|
||||
fmt.Sprintf("--secure-port=%d", 123),
|
||||
"--allow-privileged=true",
|
||||
"--storage-backend=etcd3",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
@ -509,8 +509,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -527,8 +528,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
"--cloud-provider=foo",
|
||||
},
|
||||
},
|
||||
@ -546,8 +548,9 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
"--allocate-node-cidrs=true",
|
||||
"--cluster-cidr=bar",
|
||||
},
|
||||
|
@ -39,62 +39,5 @@ spec:
|
||||
- image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.0
|
||||
name: dummy
|
||||
hostNetwork: true
|
||||
`
|
||||
KubeDiscoveryDeployment = `
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-discovery
|
||||
name: kube-discovery
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: kube-discovery
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-discovery
|
||||
# TODO: I guess we can remove all these cluster-service labels...
|
||||
kubernetes.io/cluster-service: "true"
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-discovery
|
||||
image: {{ .ImageRepository }}/kube-discovery-{{ .Arch }}:1.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /usr/local/bin/kube-discovery
|
||||
ports:
|
||||
- containerPort: 9898
|
||||
hostPort: 9898
|
||||
name: http
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/secret
|
||||
name: clusterinfo
|
||||
readOnly: true
|
||||
hostNetwork: true
|
||||
# TODO: Why doesn't the Decoder recognize this new field and decode it properly? Right now it's ignored
|
||||
# tolerations:
|
||||
# - key: {{ .MasterTaintKey }}
|
||||
# effect: NoSchedule
|
||||
securityContext:
|
||||
seLinuxOptions:
|
||||
type: spc_t
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: beta.kubernetes.io/arch
|
||||
operator: In
|
||||
values:
|
||||
- {{ .Arch }}
|
||||
volumes:
|
||||
- name: clusterinfo
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: clusterinfo
|
||||
`
|
||||
)
|
||||
|
@ -11,22 +11,16 @@ load(
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bootstrap.go",
|
||||
"csr.go",
|
||||
"discovery.go",
|
||||
"validate.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/kubelet/util/csr:go_default_library",
|
||||
"//vendor:github.com/square/go-jose",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/client-go/kubernetes",
|
||||
"//vendor:k8s.io/client-go/pkg/apis/certificates",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//vendor:k8s.io/client-go/pkg/apis/certificates/v1beta1",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
"//vendor:k8s.io/client-go/util/cert",
|
||||
],
|
||||
@ -34,14 +28,10 @@ go_library(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"bootstrap_test.go",
|
||||
"discovery_test.go",
|
||||
],
|
||||
srcs = ["validate_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/version",
|
||||
"//vendor:k8s.io/client-go/discovery",
|
||||
|
@ -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 (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/csr"
|
||||
)
|
||||
|
||||
// PerformTLSBootstrap executes a node certificate signing request.
|
||||
func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := types.NodeName(hostName)
|
||||
const CSRContextAndUser = "kubelet-csr"
|
||||
|
||||
rc, err := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{}).ClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := clientset.NewForConfig(rc)
|
||||
// PerformTLSBootstrap executes a node certificate signing request.
|
||||
func PerformTLSBootstrap(cfg *clientcmdapi.Config, hostName string) error {
|
||||
client, err := kubeconfigutil.KubeConfigToClientSet(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request")
|
||||
|
||||
key, err := certutil.MakeEllipticPrivateKeyPEM()
|
||||
@ -51,21 +42,20 @@ func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
|
||||
return fmt.Errorf("failed to generate private key [%v]", err)
|
||||
}
|
||||
|
||||
cert, err := csr.RequestNodeCertificate(c.Certificates().CertificateSigningRequests(), key, name)
|
||||
cert, err := csr.RequestNodeCertificate(client.CertificatesV1beta1().CertificateSigningRequests(), key, types.NodeName(hostName))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to request signed certificate from the API server [%v]", err)
|
||||
}
|
||||
fmt.Printf("[csr] Received signed certificate from the API server")
|
||||
fmt.Println("[csr] Generating kubelet configuration")
|
||||
fmt.Println("[csr] Received signed certificate from the API server, generating KubeConfig...")
|
||||
|
||||
cfg.AuthInfos["kubelet"] = &clientcmdapi.AuthInfo{
|
||||
cfg.AuthInfos[CSRContextAndUser] = &clientcmdapi.AuthInfo{
|
||||
ClientKeyData: key,
|
||||
ClientCertificateData: cert,
|
||||
}
|
||||
cfg.Contexts["kubelet"] = &clientcmdapi.Context{
|
||||
AuthInfo: "kubelet",
|
||||
cfg.Contexts[CSRContextAndUser] = &clientcmdapi.Context{
|
||||
AuthInfo: CSRContextAndUser,
|
||||
Cluster: cfg.Contexts[cfg.CurrentContext].Cluster,
|
||||
}
|
||||
cfg.CurrentContext = "kubelet"
|
||||
cfg.CurrentContext = CSRContextAndUser
|
||||
return nil
|
||||
}
|
||||
|
@ -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");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -27,150 +27,9 @@ import (
|
||||
"k8s.io/client-go/discovery"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
)
|
||||
|
||||
func TestEstablishMasterConnection(t *testing.T) {
|
||||
srv := stubServer(t)
|
||||
defer srv.Close()
|
||||
|
||||
tests := []struct {
|
||||
c string
|
||||
e string
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
c: "",
|
||||
e: "",
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
c: "",
|
||||
e: srv.URL,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
c: "foo",
|
||||
e: srv.URL,
|
||||
expect: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
s := &kubeadmapi.TokenDiscovery{}
|
||||
c := &kubeadmapi.ClusterInfo{Endpoints: []string{rt.e}, CertificateAuthorities: []string{rt.c}}
|
||||
_, actual := EstablishMasterConnection(s, c)
|
||||
if (actual == nil) != rt.expect {
|
||||
t.Errorf(
|
||||
"failed EstablishMasterConnection:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expect,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEstablishMasterConnectionWithMultipleEndpoints(t *testing.T) {
|
||||
// ref. https://github.com/kubernetes/kubernetes/issues/36988
|
||||
|
||||
srv := stubServer(t)
|
||||
defer srv.Close()
|
||||
|
||||
s := &kubeadmapi.TokenDiscovery{}
|
||||
c := &kubeadmapi.ClusterInfo{Endpoints: []string{srv.URL, srv.URL}, CertificateAuthorities: []string{"foo"}}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("failed EstablishMasterConnectionWithMultipleEndpoints; got a panic.")
|
||||
}
|
||||
}()
|
||||
|
||||
EstablishMasterConnection(s, c)
|
||||
}
|
||||
|
||||
func stubServer(t *testing.T) *httptest.Server {
|
||||
expect := version.Info{
|
||||
Major: "foo",
|
||||
Minor: "bar",
|
||||
GitCommit: "baz",
|
||||
}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var obj interface{}
|
||||
switch req.URL.Path {
|
||||
case "/api":
|
||||
obj = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1.4",
|
||||
},
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
case "/apis":
|
||||
obj = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "certificates.k8s.io",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "extensions/v1beta1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
default:
|
||||
output, err := json.Marshal(expect)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}
|
||||
}))
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
func TestCreateClients(t *testing.T) {
|
||||
tests := []struct {
|
||||
e string
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
e: "",
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
e: "foo",
|
||||
expect: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
_, actual := createClients(nil, rt.e, "", "")
|
||||
if (actual == nil) != rt.expect {
|
||||
t.Errorf(
|
||||
"failed createClients:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expect,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAPIEndpoint(t *testing.T) {
|
||||
func TestValidateAPIServer(t *testing.T) {
|
||||
expect := version.Info{
|
||||
Major: "foo",
|
||||
Minor: "bar",
|
||||
@ -191,7 +50,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
||||
case "/api":
|
||||
obj = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1.4",
|
||||
"v1.6.0",
|
||||
},
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
@ -222,7 +81,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
||||
case "/api":
|
||||
obj = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1.4",
|
||||
"v1.6.0",
|
||||
},
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
@ -239,7 +98,7 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
||||
{
|
||||
Name: "certificates.k8s.io",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "extensions/v1beta1"},
|
||||
{GroupVersion: "certificates.k8s.io/v1beta1", Version: "v1beta1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -271,13 +130,13 @@ func TestCheckAPIEndpoint(t *testing.T) {
|
||||
rc := &restclient.Config{Host: rt.s.URL}
|
||||
c, err := discovery.NewDiscoveryClientForConfig(rc)
|
||||
if err != nil {
|
||||
t.Fatalf("encountered an error while trying to get New Discovery Client: %v", err)
|
||||
t.Fatalf("encountered an error while trying to get the new discovery client: %v", err)
|
||||
}
|
||||
cs := &clientset.Clientset{DiscoveryClient: c}
|
||||
actual := checkAPIEndpoint(cs, "")
|
||||
actual := ValidateAPIServer(cs)
|
||||
if (actual == nil) != rt.expect {
|
||||
t.Errorf(
|
||||
"failed runChecks:\n\texpected: %t\n\t actual: %t",
|
||||
"failed TestValidateAPIServer:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expect,
|
||||
(actual == nil),
|
||||
)
|
@ -16,6 +16,7 @@ go_library(
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
|
@ -24,33 +24,35 @@ import (
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: This role should eventually be a system:-prefixed, automatically bootstrapped ClusterRole
|
||||
|
||||
// KubeDNSClusterRoleName sets the name for the kube-dns ClusterRole
|
||||
KubeDNSClusterRoleName = "kubeadm:kube-dns"
|
||||
// KubeProxyClusterRoleName sets the name for the kube-proxy ClusterRole
|
||||
KubeProxyClusterRoleName = "system:node-proxier"
|
||||
// NodeBootstrapperClusterRoleName sets the name for the TLS Node Bootstrapper ClusterRole
|
||||
NodeBootstrapperClusterRoleName = "system:node-bootstrapper"
|
||||
// BootstrapSignerClusterRoleName sets the name for the ClusterRole that allows access to ConfigMaps in the kube-public ns
|
||||
BootstrapSignerClusterRoleName = "system:bootstrap-signer-clusterinfo"
|
||||
|
||||
// Constants
|
||||
clusterRoleKind = "ClusterRole"
|
||||
roleKind = "Role"
|
||||
serviceAccountKind = "ServiceAccount"
|
||||
rbacAPIGroup = "rbac.authorization.k8s.io"
|
||||
anonymousUser = "system:anonymous"
|
||||
)
|
||||
|
||||
// TODO: Are there any unit tests that could be made for this file other than duplicating all values and logic in a separate file?
|
||||
|
||||
// CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster
|
||||
func CreateRBACRules(clientset *clientset.Clientset) error {
|
||||
// Create the ClusterRoles we need for our RBAC rules
|
||||
if err := CreateClusterRoles(clientset); err != nil {
|
||||
if err := CreateRoles(clientset); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := CreateRoleBindings(clientset); err != nil {
|
||||
return err
|
||||
}
|
||||
// Create the CreateClusterRoleBindings we need for our RBAC rules
|
||||
if err := CreateClusterRoleBindings(clientset); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -84,19 +86,53 @@ func CreateServiceAccounts(clientset *clientset.Clientset) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateClusterRoles creates the ClusterRoles that aren't bootstrapped by the apiserver
|
||||
func CreateClusterRoles(clientset *clientset.Clientset) error {
|
||||
// TODO: Remove this ClusterRole when it's automatically bootstrapped in the apiserver
|
||||
clusterRole := rbac.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: KubeDNSClusterRoleName},
|
||||
Rules: []rbac.PolicyRule{
|
||||
rbac.NewRule("list", "watch").Groups("").Resources("endpoints", "services").RuleOrDie(),
|
||||
// TODO: remove watch rule when https://github.com/kubernetes/kubernetes/pull/38816 gets merged
|
||||
rbac.NewRule("get", "list", "watch").Groups("").Resources("configmaps").RuleOrDie(),
|
||||
// CreateRoles creates namespaces RBAC Roles
|
||||
func CreateRoles(clientset *clientset.Clientset) error {
|
||||
roles := []rbac.Role{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: BootstrapSignerClusterRoleName,
|
||||
Namespace: metav1.NamespacePublic,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
rbac.NewRule("get").Groups("").Resources("configmaps").RuleOrDie(),
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := clientset.Rbac().ClusterRoles().Create(&clusterRole); err != nil {
|
||||
return err
|
||||
for _, role := range roles {
|
||||
if _, err := clientset.RbacV1beta1().Roles(metav1.NamespacePublic).Create(&role); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRoleBindings creates all namespaced and necessary bindings between bootstrapped & kubeadm-created ClusterRoles and subjects kubeadm is using
|
||||
func CreateRoleBindings(clientset *clientset.Clientset) error {
|
||||
roleBindings := []rbac.RoleBinding{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kubeadm:bootstrap-signer-clusterinfo",
|
||||
Namespace: metav1.NamespacePublic,
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbacAPIGroup,
|
||||
Kind: roleKind,
|
||||
Name: BootstrapSignerClusterRoleName,
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: "User",
|
||||
Name: anonymousUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, roleBinding := range roleBindings {
|
||||
if _, err := clientset.RbacV1beta1().RoleBindings(metav1.NamespacePublic).Create(&roleBinding); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -116,24 +152,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: kubeadmconstants.CSVTokenBootstrapGroup,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kubeadm:kube-dns",
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbacAPIGroup,
|
||||
Kind: clusterRoleKind,
|
||||
Name: KubeDNSClusterRoleName,
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: serviceAccountKind,
|
||||
Name: kubeadmconstants.KubeDNSServiceAccountName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Name: bootstrapapi.BootstrapGroup,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -157,7 +176,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
|
||||
}
|
||||
|
||||
for _, clusterRoleBinding := range clusterRoleBindings {
|
||||
if _, err := clientset.Rbac().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
|
||||
if _, err := clientset.RbacV1beta1().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,7 @@ load(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"bootstrap_test.go",
|
||||
"csv_test.go",
|
||||
],
|
||||
srcs = ["bootstrap_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
@ -21,22 +18,18 @@ go_test(
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bootstrap.go",
|
||||
"csv.go",
|
||||
],
|
||||
srcs = ["bootstrap.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/uuid",
|
||||
"//vendor:k8s.io/client-go/kubernetes",
|
||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -24,7 +24,9 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
@ -32,26 +34,26 @@ import (
|
||||
const tokenCreateRetries = 5
|
||||
|
||||
// CreateNewToken tries to create a token and fails if one with the same ID already exists
|
||||
func CreateNewToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration, usages []string, description string) error {
|
||||
return UpdateOrCreateToken(client, d, true, tokenDuration, usages, description)
|
||||
func CreateNewToken(client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error {
|
||||
return UpdateOrCreateToken(client, token, true, tokenDuration, usages, description)
|
||||
}
|
||||
|
||||
// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist.
|
||||
func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error {
|
||||
// Let's make sure the token is valid
|
||||
if valid, err := tokenutil.ValidateToken(d); !valid {
|
||||
func UpdateOrCreateToken(client *clientset.Clientset, token string, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error {
|
||||
tokenID, tokenSecret, err := tokenutil.ParseToken(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, d.ID)
|
||||
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID)
|
||||
var lastErr error
|
||||
for i := 0; i < tokenCreateRetries; i++ {
|
||||
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
if failIfExists {
|
||||
return fmt.Errorf("a token with id %q already exists", d.ID)
|
||||
return fmt.Errorf("a token with id %q already exists", tokenID)
|
||||
}
|
||||
// Secret with this ID already exists, update it:
|
||||
secret.Data = encodeTokenSecretData(d, tokenDuration, usages, description)
|
||||
secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description)
|
||||
if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
@ -67,7 +69,7 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove
|
||||
Name: secretName,
|
||||
},
|
||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
|
||||
Data: encodeTokenSecretData(d, tokenDuration, usages, description),
|
||||
Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description),
|
||||
}
|
||||
if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
|
||||
return nil
|
||||
@ -85,11 +87,47 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove
|
||||
)
|
||||
}
|
||||
|
||||
// CreateBootstrapConfigMap creates the public cluster-info ConfigMap
|
||||
func CreateBootstrapConfigMap(file string) error {
|
||||
adminConfig, err := clientcmd.LoadFromFile(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load admin kubeconfig [%v]", err)
|
||||
}
|
||||
client, err := kubeconfigutil.KubeConfigToClientSet(adminConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster
|
||||
// Copy the cluster from admin.conf to the bootstrap kubeconfig, contains the CA cert and the server URL
|
||||
bootstrapConfig := &clientcmdapi.Config{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"": adminConfig.Clusters[adminCluster],
|
||||
},
|
||||
}
|
||||
bootstrapBytes, err := clientcmd.Write(*bootstrapConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bootstrapConfigMap := v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: bootstrapapi.ConfigMapClusterInfo},
|
||||
Data: map[string]string{
|
||||
bootstrapapi.KubeConfigKey: string(bootstrapBytes),
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := client.CoreV1().ConfigMaps(metav1.NamespacePublic).Create(&bootstrapConfigMap); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
|
||||
func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration, usages []string, description string) map[string][]byte {
|
||||
func encodeTokenSecretData(tokenId, tokenSecret string, duration time.Duration, usages []string, description string) map[string][]byte {
|
||||
data := map[string][]byte{
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(d.ID),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret),
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
|
||||
}
|
||||
|
||||
if duration > 0 {
|
||||
|
@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) {
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := encodeTokenSecretData(rt.token, rt.t, []string{}, "")
|
||||
actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, "")
|
||||
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
|
@ -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)
|
||||
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"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
)
|
||||
@ -50,20 +49,18 @@ func randBytes(length int) (string, error) {
|
||||
// GenerateToken generates a new token with a token ID that is valid as a
|
||||
// Kubernetes DNS label.
|
||||
// For more info, see kubernetes/pkg/util/validation/validation.go.
|
||||
func GenerateToken(d *kubeadmapi.TokenDiscovery) error {
|
||||
func GenerateToken() (string, error) {
|
||||
tokenID, err := randBytes(TokenIDBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
token, err := randBytes(TokenSecretBytes)
|
||||
tokenSecret, err := randBytes(TokenSecretBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
d.ID = strings.ToLower(tokenID)
|
||||
d.Secret = strings.ToLower(token)
|
||||
return nil
|
||||
return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil
|
||||
}
|
||||
|
||||
// ParseTokenID tries and parse a valid token ID from a string.
|
||||
|
@ -117,15 +117,19 @@ func TestValidateToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateToken(t *testing.T) {
|
||||
td := &kubeadmapi.TokenDiscovery{}
|
||||
if err := GenerateToken(td); err != nil {
|
||||
token, err := GenerateToken()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
|
||||
}
|
||||
if len(td.ID) != 6 {
|
||||
t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(td.ID))
|
||||
tokenID, tokenSecret, err := ParseToken(token)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
|
||||
}
|
||||
if len(td.Secret) != 16 {
|
||||
t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(td.Secret))
|
||||
if len(tokenID) != 6 {
|
||||
t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(tokenID))
|
||||
}
|
||||
if len(tokenSecret) != 16 {
|
||||
t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(tokenSecret))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ cmd/kube-discovery
|
||||
cmd/kube-proxy
|
||||
cmd/kubeadm
|
||||
cmd/kubeadm/app/apis/kubeadm/install
|
||||
cmd/kubeadm/app/discovery/https
|
||||
cmd/kubeadm/app/phases/apiconfig
|
||||
cmd/kubeadm/app/phases/certs
|
||||
cmd/kubeadm/app/phases/kubeconfig
|
||||
|
@ -64,6 +64,7 @@ bench-pods
|
||||
bench-quiet
|
||||
bench-tasks
|
||||
bench-workers
|
||||
bind-addrsse
|
||||
bind-address
|
||||
bind-pods-burst
|
||||
bind-pods-qps
|
||||
@ -663,6 +664,7 @@ tls-cert-file
|
||||
tls-private-key-file
|
||||
tls-sni-cert-key
|
||||
token-auth-file
|
||||
token-ttl
|
||||
to-version
|
||||
to-version
|
||||
ttl-keys-prefix
|
||||
|
Loading…
Reference in New Issue
Block a user