亚马逊AWS官方博客

在 Amazon EKS 上使用 Open Policy Agent

OPA 与 Amazon EKS

Open Policy Agent (OPA)云原生计算基金会 (CNCF) 的一个沙盒项目,可帮助您针对几乎所有内容实施自动化策略,其工作原理与 AWS Identity and Access Management (IAM) 类似。借助 OPA,您可以使用一种基于 datalog 的名为 rego 的语言编写非常精简的策略。然后,您可以将这些策略部署到 rego 文件中的 Open Policy Agent Admission Controller,并让其验证或修改向 Kubernetes API 服务器发送的请求。这就是说,您可以编写一个有两行代码的策略,并让此策略对请求中的属性实施验证,例如可以检查 imagePullPolicy 是否设置为 Always,或者 Deployment 是否总是拥有至少 2 个 replica,或者映像是否来源于白名单存储库中的注册表。下文将介绍如何将 OPA 部署到 Amazon Elastic Container Service for Kubernetes (EKS) 集群中并实施检查,仅允许存在来自您自己的 Amazon Elastic Container Registry (ECR) 或 EKS ECR 存储库的映像。

开始使用

在开始之前,我们需要设置一个 Amazon EKS 集群。我们将在集群配置文件机制中使用 eksctl。首先,必须下载以下工具:

在安装所有必要工具后,我们便可开始启动 EKS 集群。在本例中,我们会将集群部署在 us-east-2(即我们的俄亥俄区域)中,您可将 AWS_REGION 替换为支持 Amazon EKS 的任何区域。

部署集群

export AWS_REGION=us-east-2
export ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

在导出区域后,我们可以按如下所示创建 ClusterConfig

cat >cluster.yaml <<EOF
apiVersion: eksctl.io/v1alpha4
kind: ClusterConfig
metadata:
  name: opa
  region: ${AWS_REGION}

nodeGroups:
  - name: ng-1
    desiredCapacity: 2
    allowSSH: true
EOF

在创建该文件后,我们将使用 eksctl create cluster 命令创建集群:

eksctl create cluster -f cluster.yaml

完成此操作大约需要 15 分钟时间,之后,我们便可获得可供使用的 Amazon EKS 集群。与此同时,我们可以开始为满足 Open Policy Agent 需求做准备。首先,我们将为 Admission Controller 生成自签名证书颁发机构,以便所有通信均可通过 TLS 完成。

创建资源

openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj "/CN=admission_ca"

创建 CA 后,我们需要为 OPA 创建 TLS 密钥和证书:

cat >server.conf <<EOF
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
EOF

创建 server.conf 后,我们可以再次使用 openssl 生成密钥和证书:

openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=opa.opa.svc" -config server.conf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf

接下来,我们将创建 Open Policy Agent Admission Controller 清单:

cat >admission-controller.yaml <<EOF
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: opa-viewer
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
  name: system:serviceaccounts:opa
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: opa
  name: configmap-modifier
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: opa
  name: opa-configmap-modifier
roleRef:
  kind: Role
  name: configmap-modifier
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
  name: system:serviceaccounts:opa
  apiGroup: rbac.authorization.k8s.io
---
kind: Service
apiVersion: v1
metadata:
  name: opa
  namespace: opa
spec:
  selector:
    app: opa
  ports:
  - name: https
    protocol: TCP
    port: 443
    targetPort: 443
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: opa
  namespace: opa
  name: opa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: opa
  template:
    metadata:
      labels:
        app: opa
      name: opa
    spec:
      containers:
        - name: opa
          image: openpolicyagent/opa:0.10.5
          args:
            - "run"
            - "--server"
            - "--tls-cert-file=/certs/tls.crt"
            - "--tls-private-key-file=/certs/tls.key"
            - "--addr=0.0.0.0:443"
            - "--addr=http://127.0.0.1:8181"
          volumeMounts:
            - readOnly: true
              mountPath: /certs
              name: opa-server
        - name: kube-mgmt
          image: openpolicyagent/kube-mgmt:0.6
          args:
            - "--replicate-cluster=v1/namespaces"
            - "--replicate=extensions/v1beta1/ingresses"
      volumes:
        - name: opa-server
          secret:
            secretName: opa-server
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: opa-default-system-main
  namespace: opa
data:
  main: |
    package system

    import data.kubernetes.admission

    main = {
      "apiVersion": "admission.k8s.io/v1beta1",
      "kind": "AdmissionReview",
      "response": response,
    }

    default response = {"allowed": true}

    response = {
        "allowed": false,
        "status": {
            "reason": reason,
        },
    } {
        reason = concat(", ", admission.deny)
        reason != ""
    }
EOF

然后,我们将创建 ValidatingWebhookConfiguration,它将指示 Kubernetes 发送 CREATEUPDATE Pod 事件,以让我们的策略对这些事件进行验证:

cat > webhook-configuration.yaml <<EOF
kind: ValidatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1beta1
metadata:
  name: opa-validating-webhook
webhooks:
  - name: validating-webhook.openpolicyagent.org
    rules:
      - operations: ["CREATE", "UPDATE"]
        apiGroups: ["*"]
        apiVersions: ["v1"]
        resources: ["Pod"]
    clientConfig:
      caBundle: $(cat ca.crt | base64 | tr -d '\n')
      service:
        namespace: opa
        name: opa
EOF

然后,我们将创建第一个策略。我们要创建的策略将验证每个 Pod 的映像是否来自白名单中的注册表。我们策略中的前两个条目分别为我们自己账户的 Amazon ECR 和 Amazon EKS 特定的 ECR。

cat > image_source.rego <<EOF
package kubernetes.admission

deny[msg] {
    input.request.kind.kind = "Pod"
    input.request.operation = "CREATE"
    image = input.request.object.spec.containers[_].image
    name = input.request.object.metadata.name
    not registry_whitelisted(image,whitelisted_registries)
    msg = sprintf("pod %q has invalid registry %q", [name, image])
}

whitelisted_registries = {registry |
    registries = [
        "602401143452.dkr.ecr.${AWS_REGION}.amazonaws.com",
        "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
    ]
    registry = registries[_]
}

registry_whitelisted(str, patterns) {
    registry_matches(str, patterns[_])
}

registry_matches(str, pattern) {
    contains(str, pattern)
}
EOF

最后,我们将创建用来测试策略的 nginx test Pod 清单:

cat > nginx.yaml <<EOF
kind: Pod
apiVersion: v1
metadata:
  name: nginx
  labels:
    app: nginx
  namespace: default
spec:
  containers:
  - image: nginx
    name: nginx
EOF

在创建集群之后,我们便可部署所有资源并对其进行测试。

部署资源

首先,我们将创建 opa 命名空间,并在当前环境中对其进行设置。这样,可以更轻松地跟踪我们正在执行的所有操作:

kubectl create namespace opa
kubectl config set-context --namespace opa --current

接下来,我们将部署之前创建的所有清单,先从使用我们的服务器密钥和证书的 TLS 密钥开始:

kubectl create secret tls opa-server --cert=server.crt --key=server.key

然后,我们将部署 Admission Controller:

kubectl apply -f admission-controller.yaml

如果您要在多租户或不受信任的环境中运行集群,建议您通读此处的 TLS 和身份验证方案。

在部署 AdmissionController 之后,我们可以部署已创建的 ValidatingWebhookConfiguration。这会指示 Kubernetes API 服务器将所有 Pod CREATEUPDATE 事件发送至 OPA 服务进行验证。

kubectl apply -f webhook-configuration.yaml

最后需要进行的配置是部署我们的自定义策略,限制 Pod 通过不受信任的注册表进行部署:

kubectl create configmap image-source --from-file=image_source.rego

注意:与我们正在使用的其他 kubectl 命令不同,此命令属于命令性配置。这有时会使您的 ConfigMap 很难保持同步,尤其是在使用 Gitops 之类的部署策略时,难度更大。如果您想使用 apply,则需创建 ConfigMap 清单,并将您的策略复制到 data 属性中。

在创建策略之后,我们便可进行测试,看看它是否已正确编译,以及 Open Policy Agent 是否已成功对 rego 脚本进行了初始化。要执行此操作,请使用 kubectl get configmap image-source -o yaml

$ kubectl get configmap image-source -o jsonpath="{.metadata.annotations}"
map[openpolicyagent.org/policy-status (http://openpolicyagent.org/policy-status):{"status":"ok"}]

在资源部署完毕并已在 Open Policy Agent 中进行初始化之后,我们便可运行 nginx Pod 测试。

测试策略

$ kubectl apply -f nginx.yaml
Error from server (pod "nginx" has invalid registry "nginx"): error when creating "nginx.yaml": admission webhook "validating-webhook.openpolicyagent.org" denied the request: pod "nginx" has invalid registry "nginx"

现在,让我们使用自己的注册表标记 nginx 映像,推送该映像,并尝试通过我们自己的注册表进行部署。首先,我们需要创建一个存储库:

aws ecr create-repository —repository-name nginx

然后,我们将从 API 中获得存储库名称,以便标记我们的 nginx 实例:

export REPOSITORY=$(aws ecr describe-repositories --repository-name nginx --query "repositories[0].repositoryUri" --output text)

现在,我们会将 Docker Hub 公有 nginx 提取到本地,以便重新对其进行标记:

docker pull nginx

然后,我们可以使用存储库名称重新标记 latest,并将映像推送至您自己的 Amazon ECR。首先,我们登录 Amazon ECR:

aws ecr get-login —no-include-email | bash -

然后,将 latest 标签推送至 Amazon ECR:

docker push ${REPOSITORY}:latest

接下来,我们可以重新创建 nginx 清单文件,并将其推送至 Amazon EKS 集群:

cat > private-nginx.yaml <<EOF
kind: Pod
apiVersion: v1
metadata:
  name: nginx
  labels:
    app: nginx
  namespace: default
spec:
  containers:
  - image: ${REPOSITORY}:latest
    name: nginx
EOF

最后,我们将 nginx Pod 部署到集群:

$ kubectl apply -f private-nginx.yaml
pod/nginx created

清理

如果您想删除已创建的集群,可以先删除集群中创建的所有资源,然后使用 eksctl 再次删除 Amazon EKS 集群:

kubectl delete -f private-nginx.yaml
kubectl delete namespace opa
eksctl delete cluster -f cluster.yaml

更多示例

以下是一些很好的示例,其中展示了人们目前是如何将 Open Policy Agent 与 Kubernetes 集群结合使用,来帮助管理其自定义策略的:

小结

借助 Open Policy Agent,您不必再通过编写自定义代码来处理组织或团队的自定义策略。现在,您可以像我们在本文中所做的那样部署自定义 ValidatingAdmissionControllersMutatingAdmissionControllers(为您的 Kubernetes 资源提供正常的默认值),或设置适当的标签。您会拥有几乎无穷无尽的控件。要了解有关 Open Policy Agent 的更多信息,请查看 Open Policy Agent 文档,并加入 Open Policy Agent 社区

阅读 Chris 撰写的更多博文

本篇作者

Chris Hein

Chris Hein

Chris Hein 是一位资深的开发者,倡导 Amazon Web Services 的 Kubernetes/EKS。加入 Amazon 之前,Chris 曾就职于众多规模不等的公司,如 GoPro、Sproutling 和 Mattel。有关 Chris 的更多信息,请访问 https://aws.amazon.com/blogs/opensource/author/heichris/,并通过 @christopherhein 关注他