diff --git a/cmd/kubeadm/app/BUILD b/cmd/kubeadm/app/BUILD index 3e721be2fa2..97bf3871fe4 100644 --- a/cmd/kubeadm/app/BUILD +++ b/cmd/kubeadm/app/BUILD @@ -36,6 +36,7 @@ filegroup( "//cmd/kubeadm/app/images:all-srcs", "//cmd/kubeadm/app/master:all-srcs", "//cmd/kubeadm/app/node:all-srcs", + "//cmd/kubeadm/app/phases/apiconfig:all-srcs", "//cmd/kubeadm/app/phases/certs:all-srcs", "//cmd/kubeadm/app/phases/kubeconfig:all-srcs", "//cmd/kubeadm/app/preflight:all-srcs", diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index f922bf0e4ed..aa6f6e004f8 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -39,6 +39,7 @@ type MasterConfiguration struct { Networking Networking KubernetesVersion string CloudProvider string + AuthorizationMode string } type API struct { diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD index 245a85a353c..51baff30032 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD @@ -19,6 +19,7 @@ go_library( tags = ["automanaged"], deps = [ "//pkg/api/v1:go_default_library", + "//pkg/kubeapiserver/authorizer:go_default_library", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime/schema", diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index f676020eff6..8fe01b709d5 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -18,6 +18,7 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/kubeapiserver/authorizer" ) const ( @@ -27,6 +28,7 @@ const ( DefaultKubernetesFallbackVersion = "v1.5.0" DefaultAPIBindPort = 6443 DefaultDiscoveryBindPort = 9898 + DefaultAuthorizationMode = authorizer.ModeRBAC ) func addDefaultingFuncs(scheme *runtime.Scheme) error { @@ -56,4 +58,8 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { if obj.Discovery.Token == nil && obj.Discovery.File == nil && obj.Discovery.HTTPS == nil { obj.Discovery.Token = &TokenDiscovery{} } + + if obj.AuthorizationMode == "" { + obj.AuthorizationMode = DefaultAuthorizationMode + } } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go index cc26c3de9bf..91dca1f9360 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go @@ -29,6 +29,7 @@ type MasterConfiguration struct { Networking Networking `json:"networking"` KubernetesVersion string `json:"kubernetesVersion"` CloudProvider string `json:"cloudProvider"` + AuthorizationMode string `json:"authorizationMode"` } type API struct { diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 84fd2dc7183..afad5b24817 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -27,6 +27,7 @@ go_library( "//cmd/kubeadm/app/discovery:go_default_library", "//cmd/kubeadm/app/master:go_default_library", "//cmd/kubeadm/app/node:go_default_library", + "//cmd/kubeadm/app/phases/apiconfig:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", @@ -34,6 +35,7 @@ go_library( "//pkg/api:go_default_library", "//pkg/api/v1:go_default_library", "//pkg/fields:go_default_library", + "//pkg/kubeapiserver/authorizer:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/util/flag:go_default_library", @@ -54,7 +56,10 @@ go_test( ], library = ":go_default_library", tags = ["automanaged"], - deps = ["//cmd/kubeadm/app/preflight:go_default_library"], + deps = [ + "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", + "//cmd/kubeadm/app/preflight:go_default_library", + ], ) filegroup( diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 19bde91e887..560beed1e01 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/flags" "k8s.io/kubernetes/cmd/kubeadm/app/discovery" kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig" certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" @@ -41,6 +42,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubeapiserver/authorizer" ) var ( @@ -183,6 +185,7 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight } cfg.KubernetesVersion = ver fmt.Println("[init] Using Kubernetes version:", ver) + fmt.Println("[init] Using Authorization mode:", cfg.AuthorizationMode) // Warn about the limitations with the current cloudprovider solution. if cfg.CloudProvider != "" { @@ -255,6 +258,24 @@ func (i *Init) Run(out io.Writer) error { return err } + if i.cfg.AuthorizationMode == authorizer.ModeRBAC { + err = apiconfig.CreateBootstrapRBACClusterRole(client) + if err != nil { + return err + } + + err = apiconfig.CreateKubeDNSRBACClusterRole(client) + if err != nil { + return err + } + + // TODO: remove this when https://github.com/kubernetes/kubeadm/issues/114 is fixed + err = apiconfig.CreateKubeProxyClusterRoleBinding(client) + if err != nil { + return err + } + } + if err := kubemaster.UpdateMasterRoleLabelsAndTaints(client, false); err != nil { return err } diff --git a/cmd/kubeadm/app/cmd/reset.go b/cmd/kubeadm/app/cmd/reset.go index d62222841fe..5093778a5db 100644 --- a/cmd/kubeadm/app/cmd/reset.go +++ b/cmd/kubeadm/app/cmd/reset.go @@ -27,6 +27,7 @@ import ( "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/util/initsystem" @@ -150,7 +151,7 @@ func drainAndRemoveNode(removeNode bool) error { hostname = strings.ToLower(hostname) // TODO: Use the "native" k8s client for this once we're confident the versioned is working - kubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf") + kubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName) getNodesCmd := fmt.Sprintf("kubectl --kubeconfig %s get nodes | grep %s", kubeConfigPath, hostname) output, err := exec.Command("sh", "-c", getNodesCmd).Output() @@ -219,8 +220,8 @@ func resetConfigDir(configPathDir, pkiPathDir string) { } filesToClean := []string{ - path.Join(configPathDir, "admin.conf"), - path.Join(configPathDir, "kubelet.conf"), + path.Join(configPathDir, kubeconfig.AdminKubeConfigFileName), + path.Join(configPathDir, kubeconfig.KubeletKubeConfigFileName), } fmt.Printf("[reset] Deleting files: %v\n", filesToClean) for _, path := range filesToClean { diff --git a/cmd/kubeadm/app/cmd/reset_test.go b/cmd/kubeadm/app/cmd/reset_test.go index 529195e64e6..3558572ddaf 100644 --- a/cmd/kubeadm/app/cmd/reset_test.go +++ b/cmd/kubeadm/app/cmd/reset_test.go @@ -22,6 +22,7 @@ import ( "path/filepath" "testing" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" ) @@ -63,8 +64,8 @@ func TestConfigDirCleaner(t *testing.T) { "manifests/etcd.json", "manifests/kube-apiserver.json", "pki/ca.pem", - "admin.conf", - "kubelet.conf", + kubeconfig.AdminKubeConfigFileName, + kubeconfig.KubeletKubeConfigFileName, }, verifyExists: []string{ "manifests", @@ -77,7 +78,7 @@ func TestConfigDirCleaner(t *testing.T) { }, setupFiles: []string{ "pki/ca.pem", - "kubelet.conf", + kubeconfig.KubeletKubeConfigFileName, }, verifyExists: []string{ "pki", @@ -95,8 +96,8 @@ func TestConfigDirCleaner(t *testing.T) { "manifests/etcd.json", "manifests/kube-apiserver.json", "pki/ca.pem", - "admin.conf", - "kubelet.conf", + kubeconfig.AdminKubeConfigFileName, + kubeconfig.KubeletKubeConfigFileName, "cloud-config", }, verifyExists: []string{ @@ -115,8 +116,8 @@ func TestConfigDirCleaner(t *testing.T) { "manifests/etcd.json", "manifests/kube-apiserver.json", "pki/ca.pem", - "admin.conf", - "kubelet.conf", + kubeconfig.AdminKubeConfigFileName, + kubeconfig.KubeletKubeConfigFileName, ".cloud-config", ".mydir/.myfile", }, @@ -166,8 +167,8 @@ func TestConfigDirCleaner(t *testing.T) { // Verify the files we cleanup implicitly in every test: assertExists(t, tmpDir) - assertNotExists(t, filepath.Join(tmpDir, "admin.conf")) - assertNotExists(t, filepath.Join(tmpDir, "kubelet.conf")) + assertNotExists(t, filepath.Join(tmpDir, kubeconfig.AdminKubeConfigFileName)) + assertNotExists(t, filepath.Join(tmpDir, kubeconfig.KubeletKubeConfigFileName)) assertDirEmpty(t, filepath.Join(tmpDir, "manifests")) assertDirEmpty(t, filepath.Join(tmpDir, "pki")) diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index 8c60e39f902..64f64335a55 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -29,6 +29,7 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/v1" @@ -123,7 +124,7 @@ 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, cmd *cobra.Command, tokenDuration time.Duration, token string) error { - client, err := kubemaster.CreateClientFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "admin.conf")) + client, err := kubemaster.CreateClientFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.AdminKubeConfigFileName)) if err != nil { return err } @@ -156,7 +157,7 @@ func RunGenerateToken(out io.Writer) error { // RunListTokens lists details on all existing bootstrap tokens on the server. func RunListTokens(out io.Writer, errW io.Writer, cmd *cobra.Command) error { - client, err := kubemaster.CreateClientFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "admin.conf")) + client, err := kubemaster.CreateClientFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.AdminKubeConfigFileName)) if err != nil { return err } @@ -215,7 +216,7 @@ func RunDeleteToken(out io.Writer, cmd *cobra.Command, tokenId string) error { return err } - client, err := kubemaster.CreateClientFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "admin.conf")) + client, err := kubemaster.CreateClientFromFile(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.AdminKubeConfigFileName)) if err != nil { return err } diff --git a/cmd/kubeadm/app/master/BUILD b/cmd/kubeadm/app/master/BUILD index 319a756fc52..4a2fd026a47 100644 --- a/cmd/kubeadm/app/master/BUILD +++ b/cmd/kubeadm/app/master/BUILD @@ -22,6 +22,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/images:go_default_library", + "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//pkg/api:go_default_library", "//pkg/api/resource:go_default_library", @@ -30,6 +31,7 @@ go_library( "//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/unversioned/clientcmd:go_default_library", "//pkg/client/unversioned/clientcmd/api:go_default_library", + "//pkg/kubeapiserver/authorizer:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/util/cert:go_default_library", diff --git a/cmd/kubeadm/app/master/addons.go b/cmd/kubeadm/app/master/addons.go index 1121513ab2c..2661bb4c632 100644 --- a/cmd/kubeadm/app/master/addons.go +++ b/cmd/kubeadm/app/master/addons.go @@ -23,6 +23,7 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/images" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/v1" @@ -31,6 +32,8 @@ import ( "k8s.io/kubernetes/pkg/util/intstr" ) +const KubeDNS = "kube-dns" + func createKubeProxyPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.PodSpec { privilegedTrue := true return v1.PodSpec{ @@ -68,7 +71,7 @@ func createKubeProxyPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.PodSpec { { Name: "kubeconfig", VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")}, + HostPath: &v1.HostPathVolumeSource{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName)}, }, }, { @@ -86,6 +89,7 @@ func createKubeDNSPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.PodSpec { dnsmasqPort := int32(53) return v1.PodSpec{ + ServiceAccountName: KubeDNS, Containers: []v1.Container{ // DNS server { @@ -250,7 +254,7 @@ func createKubeDNSServiceSpec(cfg *kubeadmapi.MasterConfiguration) (*v1.ServiceS } return &v1.ServiceSpec{ - Selector: map[string]string{"name": "kube-dns"}, + Selector: map[string]string{"name": KubeDNS}, Ports: []v1.ServicePort{ {Name: "dns", Port: 53, Protocol: v1.ProtocolUDP}, {Name: "dns-tcp", Port: 53, Protocol: v1.ProtocolTCP}, @@ -270,10 +274,14 @@ func CreateEssentialAddons(cfg *kubeadmapi.MasterConfiguration, client *clientse fmt.Println("[addons] Created essential addon: kube-proxy") - kubeDNSDeployment := NewDeployment("kube-dns", 1, createKubeDNSPodSpec(cfg)) + kubeDNSDeployment := NewDeployment(KubeDNS, 1, createKubeDNSPodSpec(cfg)) SetMasterTaintTolerations(&kubeDNSDeployment.Spec.Template.ObjectMeta) SetNodeAffinity(&kubeDNSDeployment.Spec.Template.ObjectMeta, NativeArchitectureNodeAffinity()) - + kubeDNSServiceAccount := &v1.ServiceAccount{} + kubeDNSServiceAccount.ObjectMeta.Name = KubeDNS + if _, err := client.ServiceAccounts(api.NamespaceSystem).Create(kubeDNSServiceAccount); err != nil { + return fmt.Errorf("failed creating kube-dns service account [%v]", err) + } if _, err := client.Extensions().Deployments(api.NamespaceSystem).Create(kubeDNSDeployment); err != nil { return fmt.Errorf("failed creating essential kube-dns addon [%v]", err) } @@ -283,7 +291,7 @@ func CreateEssentialAddons(cfg *kubeadmapi.MasterConfiguration, client *clientse return fmt.Errorf("failed creating essential kube-dns addon [%v]", err) } - kubeDNSService := NewService("kube-dns", *kubeDNSServiceSpec) + kubeDNSService := NewService(KubeDNS, *kubeDNSServiceSpec) kubeDNSService.ObjectMeta.Labels["kubernetes.io/name"] = "KubeDNS" if _, err := client.Services(api.NamespaceSystem).Create(kubeDNSService); err != nil { return fmt.Errorf("failed creating essential kube-dns addon [%v]", err) diff --git a/cmd/kubeadm/app/master/apiclient.go b/cmd/kubeadm/app/master/apiclient.go index a8b54b12c27..4fc9993b92a 100644 --- a/cmd/kubeadm/app/master/apiclient.go +++ b/cmd/kubeadm/app/master/apiclient.go @@ -69,10 +69,15 @@ func CreateClientAndWaitForAPI(file string) (*clientset.Clientset, error) { start := time.Now() wait.PollInfinite(apiCallRetryInterval, func() (bool, error) { + // TODO: use /healthz API instead of this cs, err := client.ComponentStatuses().List(v1.ListOptions{}) if err != nil { + if apierrs.IsForbidden(err) { + fmt.Print("\r[apiclient] Waiting for the API server to create RBAC policies") + } return false, nil } + fmt.Println("\n[apiclient] RBAC policies created") // TODO(phase2) must revisit this when we implement HA if len(cs.Items) < 3 { fmt.Println("[apiclient] Not all control plane components are ready yet") diff --git a/cmd/kubeadm/app/master/manifests.go b/cmd/kubeadm/app/master/manifests.go index 179031a9d18..f966eb3a71a 100644 --- a/cmd/kubeadm/app/master/manifests.go +++ b/cmd/kubeadm/app/master/manifests.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/images" "k8s.io/kubernetes/pkg/api/resource" api "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/kubeapiserver/authorizer" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/intstr" @@ -40,15 +41,17 @@ const ( DefaultClusterName = "kubernetes" DefaultCloudConfigPath = "/etc/kubernetes/cloud-config" - etcd = "etcd" - apiServer = "apiserver" - controllerManager = "controller-manager" - scheduler = "scheduler" - proxy = "proxy" - kubeAPIServer = "kube-apiserver" - kubeControllerManager = "kube-controller-manager" - kubeScheduler = "kube-scheduler" - kubeProxy = "kube-proxy" + etcd = "etcd" + apiServer = "apiserver" + controllerManager = "controller-manager" + scheduler = "scheduler" + proxy = "proxy" + kubeAPIServer = "kube-apiserver" + kubeControllerManager = "kube-controller-manager" + kubeScheduler = "kube-scheduler" + kubeProxy = "kube-proxy" + authorizationPolicyFile = "abac_policy.json" + authorizationWebhookConfigFile = "webhook_authz.conf" ) var ( @@ -296,6 +299,16 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration) []string { "--allow-privileged", ) + if cfg.AuthorizationMode != "" { + command = append(command, "--authorization-mode="+cfg.AuthorizationMode) + switch cfg.AuthorizationMode { + case authorizer.ModeABAC: + command = append(command, "--authorization-policy-file="+path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, authorizationPolicyFile)) + case authorizer.ModeWebhook: + command = append(command, "--authorization-webhook-config-file="+path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, authorizationWebhookConfigFile)) + } + } + // Use first address we are given if len(cfg.API.AdvertiseAddresses) > 0 { command = append(command, fmt.Sprintf("--advertise-address=%s", cfg.API.AdvertiseAddresses[0])) @@ -357,7 +370,7 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration) []string { "--service-account-private-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver-key.pem", "--cluster-signing-cert-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/ca.pem", "--cluster-signing-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/ca-key.pem", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", ) if cfg.CloudProvider != "" { diff --git a/cmd/kubeadm/app/master/manifests_test.go b/cmd/kubeadm/app/master/manifests_test.go index 1e0c228b238..214bf0424c3 100644 --- a/cmd/kubeadm/app/master/manifests_test.go +++ b/cmd/kubeadm/app/master/manifests_test.go @@ -484,7 +484,7 @@ func TestGetControllerManagerCommand(t *testing.T) { "--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem", "--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem", "--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca-key.pem", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", }, }, { @@ -499,7 +499,7 @@ func TestGetControllerManagerCommand(t *testing.T) { "--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem", "--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem", "--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca-key.pem", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", "--cloud-provider=foo", }, }, @@ -515,7 +515,7 @@ func TestGetControllerManagerCommand(t *testing.T) { "--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/apiserver-key.pem", "--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.pem", "--cluster-signing-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca-key.pem", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", "--allocate-node-cidrs=true", "--cluster-cidr=bar", }, diff --git a/cmd/kubeadm/app/master/tokens.go b/cmd/kubeadm/app/master/tokens.go index 3e410ec9da6..3d88e1c7987 100644 --- a/cmd/kubeadm/app/master/tokens.go +++ b/cmd/kubeadm/app/master/tokens.go @@ -32,7 +32,7 @@ func CreateTokenAuthFile(bt string) error { if err := os.MkdirAll(kubeadmapi.GlobalEnvParams.HostPKIPath, 0700); err != nil { return fmt.Errorf("failed to create directory %q [%v]", kubeadmapi.GlobalEnvParams.HostPKIPath, err) } - serialized := []byte(fmt.Sprintf("%s,kubeadm-node-csr,%s,system:kubelet-bootstrap\n", bt, uuid.NewUUID())) + serialized := []byte(fmt.Sprintf("%s,kubeadm-node-csr,%s,kubeadm:kubelet-bootstrap\n", bt, uuid.NewUUID())) // 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) diff --git a/cmd/kubeadm/app/node/BUILD b/cmd/kubeadm/app/node/BUILD index 9b46ebcfc5b..1b9f8324464 100644 --- a/cmd/kubeadm/app/node/BUILD +++ b/cmd/kubeadm/app/node/BUILD @@ -20,7 +20,6 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/util:go_default_library", - "//pkg/api/v1:go_default_library", "//pkg/apis/certificates:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/unversioned/clientcmd:go_default_library", @@ -28,6 +27,8 @@ go_library( "//pkg/kubelet/util/csr:go_default_library", "//pkg/util/cert:go_default_library", "//vendor:github.com/square/go-jose", + "//vendor:k8s.io/apimachinery/pkg/api/errors", + "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/util/wait", ], diff --git a/cmd/kubeadm/app/node/bootstrap.go b/cmd/kubeadm/app/node/bootstrap.go index c40fc16cc47..6c486439942 100644 --- a/cmd/kubeadm/app/node/bootstrap.go +++ b/cmd/kubeadm/app/node/bootstrap.go @@ -22,12 +22,13 @@ import ( "sync" "time" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" @@ -128,14 +129,9 @@ func checkForNodeNameDuplicates(clientSet *clientset.Clientset) error { if err != nil { return fmt.Errorf("Failed to get node hostname [%v]", err) } - nodeList, err := clientSet.Nodes().List(v1.ListOptions{}) - if err != nil { - return fmt.Errorf("Failed to list the nodes in the cluster: [%v]\n", err) - } - for _, node := range nodeList.Items { - if hostName == node.Name { - return fmt.Errorf("Node with name [%q] already exists.", node.Name) - } + _, err = clientSet.Nodes().Get(hostName, metav1.GetOptions{}) + if err != nil && !apierrs.IsNotFound(err) { + return err } return nil } diff --git a/cmd/kubeadm/app/phases/apiconfig/BUILD b/cmd/kubeadm/app/phases/apiconfig/BUILD new file mode 100644 index 00000000000..41f55bf4f68 --- /dev/null +++ b/cmd/kubeadm/app/phases/apiconfig/BUILD @@ -0,0 +1,34 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["clusterroles.go"], + tags = ["automanaged"], + deps = [ + "//cmd/kubeadm/app/master:go_default_library", + "//pkg/api:go_default_library", + "//pkg/apis/rbac/v1beta1:go_default_library", + "//pkg/client/clientset_generated/clientset:go_default_library", + "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/cmd/kubeadm/app/phases/apiconfig/clusterroles.go b/cmd/kubeadm/app/phases/apiconfig/clusterroles.go new file mode 100644 index 00000000000..a22febf857c --- /dev/null +++ b/cmd/kubeadm/app/phases/apiconfig/clusterroles.go @@ -0,0 +1,136 @@ +/* +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 apiconfig + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/cmd/kubeadm/app/master" + "k8s.io/kubernetes/pkg/api" + rbac "k8s.io/kubernetes/pkg/apis/rbac/v1beta1" + "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" +) + +// CreateBootstrapRBACClusterRole creates the necessary ClusterRole for bootstrapping +func CreateBootstrapRBACClusterRole(clientset *clientset.Clientset) error { + clusterRole := rbac.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "kubeadm:kubelet-bootstrap"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get").Groups("").Resources("nodes").RuleOrDie(), + rbac.NewRule("create", "watch").Groups("certificates.k8s.io").Resources("certificatesigningrequests").RuleOrDie(), + }, + } + if _, err := clientset.Rbac().ClusterRoles().Create(&clusterRole); err != nil { + return err + } + + subject := rbac.Subject{ + Kind: "Group", + Name: "kubeadm:kubelet-bootstrap", + } + + clusterRoleBinding := rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeadm:kubelet-bootstrap", + }, + RoleRef: rbac.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "kubeadm:kubelet-bootstrap", + }, + Subjects: []rbac.Subject{subject}, + } + if _, err := clientset.Rbac().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil { + return err + } + fmt.Println("[apiconfig] Created kubelet-bootstrap RBAC rules") + + return nil +} + +// CreateKubeDNSRBACClusterRole creates the necessary ClusterRole for kube-dns +func CreateKubeDNSRBACClusterRole(clientset *clientset.Clientset) error { + clusterRole := rbac.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "kubeadm:" + master.KubeDNS}, + 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(), + }, + } + if _, err := clientset.Rbac().ClusterRoles().Create(&clusterRole); err != nil { + return err + } + + subject := rbac.Subject{ + Kind: "ServiceAccount", + Name: master.KubeDNS, + Namespace: api.NamespaceSystem, + } + + clusterRoleBinding := rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeadm:" + master.KubeDNS, + }, + RoleRef: rbac.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "kubeadm:" + master.KubeDNS, + }, + Subjects: []rbac.Subject{subject}, + } + if _, err := clientset.Rbac().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil { + return err + } + fmt.Println("[apiconfig] Created kube-dns RBAC rules") + + return nil +} + +// CreateKubeProxyClusterRoleBinding creates the necessary ClusterRole for kube-dns +func CreateKubeProxyClusterRoleBinding(clientset *clientset.Clientset) error { + systemKubeProxySubject := rbac.Subject{ + Kind: "User", + Name: "system:kube-proxy", + Namespace: api.NamespaceSystem, + } + + systemNodesSubject := rbac.Subject{ + Kind: "Group", + Name: "system:nodes", + Namespace: api.NamespaceSystem, + } + + clusterRoleBinding := rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system:node-proxier", + }, + RoleRef: rbac.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "system:node-proxier", + }, + Subjects: []rbac.Subject{systemKubeProxySubject, systemNodesSubject}, + } + if _, err := clientset.Rbac().ClusterRoleBindings().Update(&clusterRoleBinding); err != nil { + return err + } + fmt.Println("[apiconfig] Created kube-proxy RBAC rules") + + return nil +} diff --git a/cmd/kubeadm/app/phases/certs/pki_helpers.go b/cmd/kubeadm/app/phases/certs/pki_helpers.go index fab73001945..0d33d7d6cc0 100644 --- a/cmd/kubeadm/app/phases/certs/pki_helpers.go +++ b/cmd/kubeadm/app/phases/certs/pki_helpers.go @@ -34,7 +34,6 @@ func newCertificateAuthority() (*rsa.PrivateKey, *x509.Certificate, error) { config := certutil.Config{ CommonName: "kubernetes", } - cert, err := certutil.NewSelfSignedCACert(config, key) if err != nil { return nil, nil, fmt.Errorf("unable to create self-signed certificate [%v]", err) @@ -61,16 +60,13 @@ func newServerKeyAndCert(caCert *x509.Certificate, caKey *rsa.PrivateKey, altNam return key, cert, nil } -func NewClientKeyAndCert(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*rsa.PrivateKey, *x509.Certificate, error) { +func NewClientKeyAndCert(config *certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*rsa.PrivateKey, *x509.Certificate, error) { key, err := certutil.NewPrivateKey() if err != nil { return nil, nil, fmt.Errorf("unable to create private key [%v]", err) } - config := certutil.Config{ - CommonName: "kubernetes-client", - } - cert, err := certutil.NewSignedCert(config, key, caCert, caKey) + cert, err := certutil.NewSignedCert(*config, key, caCert, caKey) if err != nil { return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err) } diff --git a/cmd/kubeadm/app/phases/certs/pki_helpers_test.go b/cmd/kubeadm/app/phases/certs/pki_helpers_test.go index a0e72f4a3ad..3dd81ba27fa 100644 --- a/cmd/kubeadm/app/phases/certs/pki_helpers_test.go +++ b/cmd/kubeadm/app/phases/certs/pki_helpers_test.go @@ -105,7 +105,11 @@ func TestNewClientKeyAndCert(t *testing.T) { t.Fatalf("Couldn't create rsa Private Key") } caCert := &x509.Certificate{} - _, _, actual := NewClientKeyAndCert(caCert, caKey) + config := &certutil.Config{ + CommonName: "test", + Organization: []string{"test"}, + } + _, _, actual := NewClientKeyAndCert(config, caCert, caKey) if (actual == nil) != rt.expected { t.Errorf( "failed NewClientKeyAndCert:\n\texpected: %t\n\t actual: %t", diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index afb1739fdd2..1a10ea1b400 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -31,9 +31,11 @@ import ( ) const ( - KubernetesDirPermissions = 0700 - AdminKubeConfigFileName = "admin.conf" - KubeletKubeConfigFileName = "kubelet.conf" + KubernetesDirPermissions = 0700 + AdminKubeConfigFileName = "admin.conf" + AdminKubeConfigClientName = "kubernetes-admin" + KubeletKubeConfigFileName = "kubelet.conf" + KubeletKubeConfigClientName = "kubelet" ) // This function is called from the main init and does the work for the default phase behaviour @@ -68,36 +70,47 @@ func CreateAdminAndKubeletKubeConfig(masterEndpoint, pkiDir, outDir string) erro } // User admin should have full access to the cluster - if err := createKubeConfigFileForClient(masterEndpoint, "admin", outDir, caCert, caKey); err != nil { - return fmt.Errorf("couldn't create a kubeconfig file for admin: %v", err) + adminCertConfig := &certutil.Config{ + CommonName: AdminKubeConfigClientName, + Organization: []string{"system:masters"}, + } + adminKubeConfigFilePath := path.Join(outDir, AdminKubeConfigFileName) + if err := createKubeConfigFileForClient(masterEndpoint, adminKubeConfigFilePath, adminCertConfig, caCert, caKey); err != nil { + return fmt.Errorf("couldn't create config for %s: %v", AdminKubeConfigClientName, err) } - // TODO: The kubelet should have limited access to the cluster - if err := createKubeConfigFileForClient(masterEndpoint, "kubelet", outDir, caCert, caKey); err != nil { - return fmt.Errorf("couldn't create a kubeconfig file for kubelet: %v", err) + // The kubelet should have limited access to the cluster + kubeletCertConfig := &certutil.Config{ + CommonName: KubeletKubeConfigClientName, + Organization: []string{"system:nodes"}, } + kubeletKubeConfigFilePath := path.Join(outDir, KubeletKubeConfigFileName) + if err := createKubeConfigFileForClient(masterEndpoint, kubeletKubeConfigFilePath, kubeletCertConfig, caCert, caKey); err != nil { + return fmt.Errorf("couldn't create config for %s: %v", KubeletKubeConfigClientName, err) + } + + // TODO make credentials for the controller manager and kube proxy return nil } -func createKubeConfigFileForClient(masterEndpoint, client, outDir string, caCert *x509.Certificate, caKey *rsa.PrivateKey) error { - key, cert, err := certphase.NewClientKeyAndCert(caCert, caKey) +func createKubeConfigFileForClient(masterEndpoint, kubeConfigFilePath string, config *certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) error { + key, cert, err := certphase.NewClientKeyAndCert(config, caCert, caKey) if err != nil { - return fmt.Errorf("failure while creating %s client certificate [%v]", client, err) + return fmt.Errorf("failure while creating %s client certificate [%v]", config.CommonName, err) } - config := MakeClientConfigWithCerts( + kubeConfig := MakeClientConfigWithCerts( masterEndpoint, "kubernetes", - client, + config.CommonName, certutil.EncodeCertPEM(caCert), certutil.EncodePrivateKeyPEM(key), certutil.EncodeCertPEM(cert), ) // Write it now to a file - filepath := path.Join(outDir, fmt.Sprintf("%s.conf", client)) - return WriteKubeconfigToDisk(filepath, config) + return WriteKubeconfigToDisk(kubeConfigFilePath, kubeConfig) } func WriteKubeconfigToDisk(filepath string, kubeconfig *clientcmdapi.Config) error { diff --git a/cmd/kubeadm/app/preflight/BUILD b/cmd/kubeadm/app/preflight/BUILD index d714bc5de99..48300a682d2 100644 --- a/cmd/kubeadm/app/preflight/BUILD +++ b/cmd/kubeadm/app/preflight/BUILD @@ -14,6 +14,7 @@ go_library( tags = ["automanaged"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//pkg/api/validation:go_default_library", "//pkg/util/initsystem:go_default_library", "//pkg/util/node:go_default_library", diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index ba6f484adb2..5c17ab7db14 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -29,6 +29,7 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/util/initsystem" "k8s.io/kubernetes/pkg/util/node" @@ -320,8 +321,8 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error { DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")}, DirAvailableCheck{Path: kubeadmapi.GlobalEnvParams.HostPKIPath}, DirAvailableCheck{Path: "/var/lib/kubelet"}, - FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "admin.conf")}, - FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")}, + FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.AdminKubeConfigFileName)}, + FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName)}, FileContentCheck{Path: bridgenf, Content: []byte{'1'}}, InPathCheck{executable: "ip", mandatory: true}, InPathCheck{executable: "iptables", mandatory: true}, @@ -355,7 +356,7 @@ func RunJoinNodeChecks(cfg *kubeadmapi.NodeConfiguration) error { PortOpenCheck{port: 10250}, DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")}, DirAvailableCheck{Path: "/var/lib/kubelet"}, - FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")}, + FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName)}, FileContentCheck{Path: bridgenf, Content: []byte{'1'}}, InPathCheck{executable: "ip", mandatory: true}, InPathCheck{executable: "iptables", mandatory: true}, diff --git a/hack/.linted_packages b/hack/.linted_packages index 60b3c906478..d53d9615dc9 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -27,6 +27,7 @@ cmd/kube-proxy cmd/kubeadm cmd/kubeadm cmd/kubeadm/app/apis/kubeadm/install +cmd/kubeadm/app/phases/apiconfig cmd/kubectl cmd/kubelet cmd/libs/go2idl/client-gen diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index d32473b07ed..69182cf012f 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -562,6 +562,7 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz obj.API.Port = 20 obj.Networking.ServiceSubnet = "foo" obj.Networking.DNSDomain = "foo" + obj.AuthorizationMode = "foo" obj.Discovery.Token = &kubeadm.TokenDiscovery{} }, func(s *policy.PodDisruptionBudgetStatus, c fuzz.Continue) { diff --git a/pkg/util/cert/cert.go b/pkg/util/cert/cert.go index fff5b38d634..05664c927be 100644 --- a/pkg/util/cert/cert.go +++ b/pkg/util/cert/cert.go @@ -90,7 +90,7 @@ func NewSignedCert(cfg Config, key *rsa.PrivateKey, caCert *x509.Certificate, ca certTmpl := x509.Certificate{ Subject: pkix.Name{ CommonName: cfg.CommonName, - Organization: caCert.Subject.Organization, + Organization: cfg.Organization, }, DNSNames: cfg.AltNames.DNSNames, IPAddresses: cfg.AltNames.IPs,