diff --git a/authz/manifests.go b/authz/manifests.go index 2e595e51..b107fdb4 100644 --- a/authz/manifests.go +++ b/authz/manifests.go @@ -39,4 +39,69 @@ subjects: - kind: ServiceAccount namespace: kube-system name: rke-job-deployer` + + DefaultPodSecurityPolicy = ` +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: default-psp + namespace: kube-system + annotations: + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' +spec: + privileged: true + allowPrivilegeEscalation: true + allowedCapabilities: + - '*' + volumes: + - '*' + hostNetwork: true + hostPorts: + - min: 0 + max: 65535 + hostIPC: true + hostPID: true + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny'` + + DefaultPodSecurityRole = ` +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: default-psp-role + namespace: kube-system +rules: +- apiGroups: ['extensions'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - default-psp` + + DefaultPodSecurityRoleBinding = ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: default-psp-rolebinding + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: default-psp-role +subjects: +# Authorize all service accounts in a namespace: +- kind: Group + apiGroup: rbac.authorization.k8s.io + name: system:serviceaccounts +# Or equivalently, all authenticated users in a namespace: +- kind: Group + apiGroup: rbac.authorization.k8s.io + name: system:authenticated + +` ) diff --git a/authz/psp.go b/authz/psp.go new file mode 100644 index 00000000..67efa174 --- /dev/null +++ b/authz/psp.go @@ -0,0 +1,35 @@ +package authz + +import ( + "github.com/rancher/rke/k8s" + "github.com/sirupsen/logrus" +) + +func ApplyDefaultPodSecurityPolicy(kubeConfigPath string) error { + logrus.Infof("[authz] Applying default PodSecurityPolicy") + k8sClient, err := k8s.NewClient(kubeConfigPath) + if err != nil { + return err + } + if err := k8s.UpdatePodSecurityPolicyFromYaml(k8sClient, DefaultPodSecurityPolicy); err != nil { + return err + } + logrus.Infof("[authz] Default PodSecurityPolicy applied successfully") + return nil +} + +func ApplyDefaultPodSecurityPolicyRole(kubeConfigPath string) error { + logrus.Infof("[authz] Applying default PodSecurityPolicy Role and RoleBinding") + k8sClient, err := k8s.NewClient(kubeConfigPath) + if err != nil { + return err + } + if err := k8s.UpdateRoleFromYaml(k8sClient, DefaultPodSecurityRole); err != nil { + return err + } + if err := k8s.UpdateRoleBindingFromYaml(k8sClient, DefaultPodSecurityRoleBinding); err != nil { + return err + } + logrus.Infof("[authz] Default PodSecurityPolicy Role and RoleBinding applied successfully") + return nil +} diff --git a/cluster.yml b/cluster.yml index 32619f96..0ad76b27 100644 --- a/cluster.yml +++ b/cluster.yml @@ -1,5 +1,4 @@ --- - auth: strategy: x509 options: @@ -21,10 +20,8 @@ network: ssh_key_path: ~/.ssh/test ignore_docker_version: false - # Kubernetes authorization mode; currently only `rbac` is supported and enabled by default. # Use `mode: none` to disable authorization - authorization: mode: rbac options: @@ -53,6 +50,7 @@ services: kube-api: image: rancher/k8s:v1.8.3-rancher2 service_cluster_ip_range: 10.233.0.0/18 + pod_security_policy: false extra_args: v: 4 kube-controller: diff --git a/cluster/cluster.go b/cluster/cluster.go index 2a7382b2..aed93358 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -140,6 +140,10 @@ func (c *Cluster) setClusterDefaults() { if len(c.Authorization.Mode) == 0 { c.Authorization.Mode = DefaultAuthorizationMode } + if c.Services.KubeAPI.PodSecurityPolicy && c.Authorization.Mode != services.RBACAuthorizationMode { + logrus.Warnf("PodSecurityPolicy can't be enabled with RBAC support disabled") + c.Services.KubeAPI.PodSecurityPolicy = false + } c.setClusterServicesDefaults() c.setClusterNetworkDefaults() c.setClusterImageDefaults() @@ -258,5 +262,13 @@ func (c *Cluster) ApplyAuthzResources() error { return fmt.Errorf("Failed to apply the ClusterRoleBinding needed for node authorization: %v", err) } } + if c.Authorization.Mode == services.RBACAuthorizationMode && c.Services.KubeAPI.PodSecurityPolicy { + if err := authz.ApplyDefaultPodSecurityPolicy(c.LocalKubeConfigPath); err != nil { + return fmt.Errorf("Failed to apply default PodSecurityPolicy: %v", err) + } + if err := authz.ApplyDefaultPodSecurityPolicyRole(c.LocalKubeConfigPath); err != nil { + return fmt.Errorf("Failed to apply default PodSecurityPolicy ClusterRole and ClusterRoleBinding: %v", err) + } + } return nil } diff --git a/cmd/config.go b/cmd/config.go index 40fb3505..37f14717 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -260,6 +260,16 @@ func getServiceConfig(reader *bufio.Reader) (*v3.RKEConfigServices, error) { servicesConfig.KubeAPI.ServiceClusterIPRange = serviceClusterIPRange servicesConfig.KubeController.ServiceClusterIPRange = serviceClusterIPRange + podSecurityPolicy, err := getConfig(reader, "Enable PodSecurityPolicy", "n") + if err != nil { + return nil, err + } + if podSecurityPolicy == "y" || podSecurityPolicy == "Y" { + servicesConfig.KubeAPI.PodSecurityPolicy = true + } else { + servicesConfig.KubeAPI.PodSecurityPolicy = false + } + clusterNetworkCidr, err := getConfig(reader, "Cluster Network CIDR", cluster.DefaultClusterCIDR) if err != nil { return nil, err diff --git a/k8s/clusterrole.go b/k8s/clusterrole.go index 6939a674..cc6e03a9 100644 --- a/k8s/clusterrole.go +++ b/k8s/clusterrole.go @@ -32,3 +32,32 @@ func UpdateClusterRoleBindingFromYaml(k8sClient *kubernetes.Clientset, clusterRo } return err } + +func UpdateClusterRoleFromYaml(k8sClient *kubernetes.Clientset, clusterRoleYaml string) error { + clusterRole := rbacv1.ClusterRole{} + err := decodeYamlResource(&clusterRole, clusterRoleYaml) + if err != nil { + return err + } + + for retries := 0; retries <= 5; retries++ { + if err = updateClusterRole(k8sClient, clusterRole); err != nil { + time.Sleep(time.Second * 5) + continue + } + return nil + } + return err +} + +func updateClusterRole(k8sClient *kubernetes.Clientset, cr rbacv1.ClusterRole) error { + if _, err := k8sClient.RbacV1().ClusterRoles().Create(&cr); err != nil { + if !apierrors.IsAlreadyExists(err) { + return err + } + if _, err := k8sClient.RbacV1().ClusterRoles().Update(&cr); err != nil { + return err + } + } + return nil +} diff --git a/k8s/k8s.go b/k8s/k8s.go index e2210185..7831cd1a 100644 --- a/k8s/k8s.go +++ b/k8s/k8s.go @@ -1,6 +1,9 @@ package k8s import ( + "bytes" + + yamlutil "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) @@ -17,3 +20,8 @@ func NewClient(kubeConfigPath string) (*kubernetes.Clientset, error) { } return K8sClientSet, nil } + +func decodeYamlResource(resource interface{}, yamlManifest string) error { + decoder := yamlutil.NewYAMLToJSONDecoder(bytes.NewReader([]byte(yamlManifest))) + return decoder.Decode(&resource) +} diff --git a/k8s/psp.go b/k8s/psp.go new file mode 100644 index 00000000..0f368189 --- /dev/null +++ b/k8s/psp.go @@ -0,0 +1,38 @@ +package k8s + +import ( + "time" + + "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/kubernetes" +) + +func UpdatePodSecurityPolicyFromYaml(k8sClient *kubernetes.Clientset, pspYaml string) error { + psp := v1beta1.PodSecurityPolicy{} + err := decodeYamlResource(&psp, pspYaml) + if err != nil { + return err + } + for retries := 0; retries <= 5; retries++ { + if err = updatePodSecurityPolicy(k8sClient, psp); err != nil { + time.Sleep(time.Second * 5) + continue + } + return nil + } + return err +} + +func updatePodSecurityPolicy(k8sClient *kubernetes.Clientset, psp v1beta1.PodSecurityPolicy) error { + if _, err := k8sClient.ExtensionsV1beta1().PodSecurityPolicies().Create(&psp); err != nil { + if !apierrors.IsAlreadyExists(err) { + return err + } + if _, err := k8sClient.ExtensionsV1beta1().PodSecurityPolicies().Update(&psp); err != nil { + return err + } + } + return nil + +} diff --git a/k8s/role.go b/k8s/role.go new file mode 100644 index 00000000..5da857c7 --- /dev/null +++ b/k8s/role.go @@ -0,0 +1,67 @@ +package k8s + +import ( + "time" + + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/kubernetes" +) + +func UpdateRoleBindingFromYaml(k8sClient *kubernetes.Clientset, roleBindingYaml string) error { + roleBinding := rbacv1.RoleBinding{} + err := decodeYamlResource(&roleBinding, roleBindingYaml) + if err != nil { + return err + } + + for retries := 0; retries <= 5; retries++ { + if err = updateRoleBinding(k8sClient, roleBinding); err != nil { + time.Sleep(time.Second * 5) + continue + } + return nil + } + return err +} + +func updateRoleBinding(k8sClient *kubernetes.Clientset, roleBinding rbacv1.RoleBinding) error { + if _, err := k8sClient.RbacV1().RoleBindings(roleBinding.Namespace).Create(&roleBinding); err != nil { + if !apierrors.IsAlreadyExists(err) { + return err + } + if _, err := k8sClient.RbacV1().RoleBindings(roleBinding.Namespace).Update(&roleBinding); err != nil { + return err + } + } + return nil +} + +func UpdateRoleFromYaml(k8sClient *kubernetes.Clientset, roleYaml string) error { + role := rbacv1.Role{} + err := decodeYamlResource(&role, roleYaml) + if err != nil { + return err + } + + for retries := 0; retries <= 5; retries++ { + if err = updateRole(k8sClient, role); err != nil { + time.Sleep(time.Second * 5) + continue + } + return nil + } + return err +} + +func updateRole(k8sClient *kubernetes.Clientset, role rbacv1.Role) error { + if _, err := k8sClient.RbacV1().Roles(role.Namespace).Create(&role); err != nil { + if !apierrors.IsAlreadyExists(err) { + return err + } + if _, err := k8sClient.RbacV1().Roles(role.Namespace).Update(&role); err != nil { + return err + } + } + return nil +} diff --git a/services/kubeapi.go b/services/kubeapi.go index 5a99949d..01382ae9 100644 --- a/services/kubeapi.go +++ b/services/kubeapi.go @@ -47,6 +47,9 @@ func buildKubeAPIConfig(host *hosts.Host, kubeAPIService v3.KubeAPIService, etcd if authorizationMode == RBACAuthorizationMode { imageCfg.Cmd = append(imageCfg.Cmd, "--authorization-mode=RBAC") } + if kubeAPIService.PodSecurityPolicy { + imageCfg.Cmd = append(imageCfg.Cmd, "--runtime-config=extensions/v1beta1/podsecuritypolicy=true", "--admission-control=PodSecurityPolicy") + } hostCfg := &container.HostConfig{ VolumesFrom: []string{ SidekickContainerName,