如何使用 Traefik 启用 PSP

2019 年 1 月 24 日 | Panagiotis Georgiadis | 无许可

如果您正在阅读本文,很可能是因为您已经了解 Traefik 是什么,并且希望在不以 root 用户身份(主进程 pid 1)或任何其他特权用户身份运行它时使用它。如果安全性是您非常关心的问题,启用 Kubernetes PodSecurityPolicy - PSP 被认为是安全机制的最佳实践之一。

为此,我们一直在尝试构建一个使用 libcap-progsauthbind 配置的 Traefik 容器镜像。如果您足够好奇,可以阅读我们的 源代码。最后但并非最不重要的一点,此镜像目前不是 openSUSE 的官方组成部分(尚未),但在进行了一些测试(是的,我们测试我们的容器镜像)之后,我们希望将其加入其中。

如何使用它

首先,您需要一个可用的 Kubernetes 集群。如果您想跟随本指南,您应该自己设置一个集群。这意味着使用 openSUSE Kubic 配合 kubeadm 或遵循上游 Traefik 指令。您也可以在您的机器上使用 minikube,因为它是获取本地 Kubernetes 集群进行实验和开发的最快方法。最终,我们假设您的系统中已经安装了 kubectl 二进制文件。

$ sudo zypper in minikube kubernetes-client
$ minikube start --vm-driver=kvm2

Starting local Kubernetes v1.13.2 cluster...
Starting VM...
Downloading Minikube ISO
 181.48 MB / 181.48 MB [============================================] 100.00% 0s
Getting VM IP address...
Moving files into cluster...
Downloading kubeadm v1.13.2
Downloading kubelet v1.13.2
Finished Downloading kubeadm v1.13.2
Finished Downloading kubelet v1.13.2
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Stopping extra container runtimes...
Starting cluster components...
Verifying kubelet health ...
Verifying apiserver health ...
Kubectl is now configured to use the cluster.
Loading cached images from config file.

Everything looks great. Please enjoy minikube!

现在,您的客户端和集群应该已经配置好了

$ kubectl get nodes

NAME       STATUS   ROLES    AGE    VERSION
minikube   Ready    master   103s   v1.13.2

授权 Traefik 使用您的 Kubernetes 集群

新的 kubernetes 版本正在使用 RBAC(基于角色的访问控制),它允许 Kubernetes 资源以受控方式与其 API 通信。有两种方法可以设置权限,以允许 Traefik 资源与 k8s API 通信

  • 通过 RoleBinding(命名空间特定)
  • 通过 ClusterRoleBinding(全局,适用于所有命名空间)

为了简单起见,我们将使用 ClusterRoleBinding 以在集群级别和所有命名空间中授予权限。以下 ClusterRoleBinding 允许属于 ServiceAccount 的任何用户或资源使用 traefik ingress controller。

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups:     ['policy']
    resources:     ['podsecuritypolicies']
    verbs:         ['use']
    resourceNames: ['traefik-ingress-controller']
  - apiGroups:
      - ""
    resources:
      - pods
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
  name: traefik-ingress-controller
  namespace: kube-system
$ kubectl apply -f rbac.yaml

serviceaccount/traefik-ingress-controller created
clusterrole.rbac.authorization.k8s.io/traefik-ingress-controller created
clusterrolebinding.rbac.authorization.k8s.io/traefik-ingress-controller created

请注意,ClusterRole 定义的一部分是强制其 use(动词)podsecuritypolicy

启用 PSP

我们将启用一个 PodSecurityPolicy,禁止 root 用户运行我们的 Traefik 容器

---
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: traefik-ingress-controller
spec:
  allowedCapabilities:
    - NET_BIND_SERVICE
  privileged: false
  allowPrivilegeEscalation: true
  # Allow core volume types.
  volumes:
    - 'configMap'
    - 'secret'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    # Require the container to run without root privileges.
    rule: 'MustRunAsNonRoot'
  supplementalGroups:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  fsGroup:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  readOnlyRootFilesystem: false
  seLinux:
    rule: 'RunAsAny'
  hostPorts:
    - max: 65535
      min: 1
$ kubectl apply -f podsecuritypolicy.yaml

podsecuritypolicy.extensions/traefik-ingress-controller created

您可以通过输入以下命令来验证它是否已加载

$ kubectl get psp
NAME                         PRIV    CAPS               SELINUX    RUNASUSER          FSGROUP     SUPGROUP    READONLYROOTFS   VOLUMES
traefik-ingress-controller   false   NET_BIND_SERVICE   RunAsAny   MustRunAsNonRoot   MustRunAs   MustRunAs   false            configMap,secret

部署我们的实验性 Traefik 镜像

我将使用 NodePort 作为 deployment 类型 部署 Traefik。

kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: traefik-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
      - image: registry.opensuse.org/devel/kubic/containers/container/kubic/traefik:1.7
        name: traefik-ingress-lb
        ports:
        - name: http
          containerPort: 80
        - name: admin
          containerPort: 8080
        args:
        - --api
        - --kubernetes
        - --logLevel=INFO
        securityContext:
          capabilities:
              drop:
              - ALL
              add:
              - NET_BIND_SERVICE
          runAsUser: 38

正如您所见,我们的 Traefik 镜像正在使用 authbindsetcap 来启用一个普通用户(具有 38 uid)来打开低于 1024 的端口。

$ kubectl apply -f deployment.yaml

deployment.extensions/traefik-ingress-controller created

要验证它是否正在运行,请列出 kube-system 命名空间中的 Pod

kubectl -n kube-system get pods | grep traefik

traefik-ingress-controller-87cbbbfb7-stlzm   1/1     Running   0          41s

此外,您还可以查询部署

$ kubectl -n kube-system get deployments

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
coredns                      2/2     2            2           106m
traefik-ingress-controller   1/1     1            1           27m

要验证 Traefik 是否以普通用户(名称应为 traefik,UID 为 38)运行

traefikpod=$(kubectl -n kube-system get pods | grep traefik | awk '{ print $1 }')
kubectl -n kube-system exec -it $traefikpod -- whoami && id

traefik
uid=1000(tux) gid=100(users) groups=100(users),469(docker),472(libvirt),474(qemu),475(kvm),1003(osc)

到目前为止,我们还没有任何服务来访问它。它只是 Pod 的一部分,该 Pod 是部署的一部分。

$ kubectl -n kube-system expose deployment traefik-ingress-controller --target-port=80 --type=NodePort

service/traefik-ingress-controller exposed

您可以通过查询 kube-system 命名空间下的服务来验证这一点

$ kubectl get svc -n kube-system

NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                       AGE
kube-dns                     ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP                 107m
traefik-ingress-controller   NodePort    10.105.27.208   <none>        80:31308/TCP,8080:30815/TCP   2s

我们看到 traefik-ingress-controller 服务正在每个节点的端口 31308 上可用 – 端口号在您的集群中可能不同。因此,外部 IP 是我们集群中任何节点的 IP。您现在应该能够通过请求端口 31308 在 Minikube 集群的端口 80 上访问 Traefik

$ curl $(minikube ip):31308

404 page not found

注意我们预计在这里会看到 404 响应,因为我们尚未为 Traefik 提供任何配置。

最后一步是创建一个 Service 和一个 Ingress,以暴露 Traefik Web UI。从现在开始,您可以实际使用官方 Traefik 文档

kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml

现在让我们在 /etc/hosts 文件中设置一个条目,将 traefik-ui.minikube 路由到我们的集群。

在生产环境中,您需要设置真实的 DNS 条目。您可以通过运行 minikube ip 来获取 minikube 实例的 IP 地址

echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts

我们现在应该能够在浏览器中访问 traefik-ui.minikube:<NODEPORT> 并查看 Traefik web UI。

traefik_webui

现在,您应该能够继续阅读 官方 traefik 文档 并执行所有很酷的事情,但具有更好的安全性。

更有趣吗?

如果您正在使用使用 Kubic 的完整 Kubernetes 集群(意味着:您可以使用多个节点),请随时在您的超visor 上设置一个 LoadBalancer,该超visor 托管您的 Kubic 虚拟机

sudo zypper in nginx

cat /etc/nginx/nginx.conf

load_module '/usr/lib64/nginx/modules/ngx_stream_module.so';
events {
     worker_connections  1024;
}

stream {
     upstream stream_backend {
         # server <IP_ADDRESS_OF_KUBIC_NODE>:<TRAEFIK_NODEPORT>;
         server worker-0.kubic-init.suse.net:31380;
         server worker-1.kubic-init.suse.net:31380;
         server worker-2.kubic-init.suse.net:31380;
     }

   server {
     listen ultron.suse.de:80;
     proxy_pass stream_backend;
   }
}

然后启动负载均衡器:sudo systemctl start nginx

这意味着访问我的机器的任何人(在本例中为 ultron.suse.de)将被重定向到我的 Kubernetes 节点之一,端口为 nodeport (31380)。

玩得开心

类别: 博客

标签

分享这篇文章