RKE2 Image security Admission Controller V2

Page content

In a previous post we talked about anchore-image-validator made by Banzaicloud. In this post I will show you how I updated that scenario for a real word solution.

Parts of the K8S Security Lab series

Container Runetime Security
Advanced Kernel Security
Network Security
Secure Kubernetes Install
User Security
Image Security
  • Part1: Image security Admission Controller
  • Part2: Image security Admission Controller V2
  • Part3: Image security Admission Controller V3
  • Part4: Continuous Image security
  • Part5: trivy-operator 1.0
  • Part6: trivy-operator 2.1: Trivy-operator is now an Admisssion controller too!!!
  • Part7: trivy-operator 2.2: Patch release for Admisssion controller
  • Part8: trivy-operator 2.3: Patch release for Admisssion controller
  • Part8: trivy-operator 2.4: Patch release for Admisssion controller
  • Part8: trivy-operator 2.5: Patch release for Admisssion controller
  • Part9_ Image Signature Verification with Connaisseur
  • Part10: Image Signature Verification with Connaisseur 2.0
  • Part11: Image Signature Verification with Kyverno
  • Part12: How to use imagePullSecrets cluster-wide??
  • Part13: Automatically change registry in pod definition
  • Part14: ArgoCD auto image updater
    Pod Security
    Secret Security
    Monitoring and Observability
    Backup

    I found multiple solution for Anchore Engine so the first step is to deploy with its helm chart. In RKE2 I will use Rancher’s Helm controller what is preinstalled.

    apiVersion: v1
    kind: Namespace
    metadata:
      name: securty-system
    
    ---
    apiVersion: helm.cattle.io/v1
    kind: HelmChart
    metadata:
      name: anchore-enginn
      namespace: kube-system
    spec:
      repo: "https://charts.anchore.io"
      chart: anchore-engine
      targetNamespace: securty-system
      valuesContent: |-
        postgresql:
          image: centos/postgresql-96-centos7
          extraEnv:
          - name: POSTGRESQL_USER
            value: anchoreengine
          - name: POSTGRESQL_PASSWORD
            value: Password1
          - name: POSTGRESQL_DATABASE
            value: anchore
          - name: PGUSER
            value: postgres
          postgresPassword: Password1
          persistence:
            size: 10Gi
        anchoreGlobal:
          defaultAdminPassword: Password1
          defaultAdminEmail: devopstales@mydomain.intra    
    

    Then we can Deploy an Admission Controller to us this tool to automaticle scann any image deploy in the cluster and reject if is vulnerable. As I sad before there is multiple solution for this. In the previous pos I used Banzaicloud’s anchore-image-validator but it turned out Anchore’s own Admission Controller is more controllable. It allows to use different policies based on tag or annotations.

    Create a secret for the anchore credentials that the controller will use to make api calls to Anchore.

    nano credentials.json
    {
      "users": [
        { "username": "admin", "password": "Password1"}
      ]
    }
    
    kubectl create secret generic anchore-credentials --from-file=credentials.json
    

    Create a job that automaticle upload policies to anchore engin:

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: anchore-policys
    data:
      production_bundle.json: |-
        {
            "blacklisted_images": [], 
            "comment": "Production bundle", 
            "id": "production_bundle", 
            "mappings": [
                {
                    "id": "c4f9bf74-dc38-4ddf-b5cf-00e9c0074611", 
                    "image": {
                        "type": "tag", 
                        "value": "*"
                    }, 
                    "name": "default", 
                    "policy_id": "48e6f7d6-1765-11e8-b5f9-8b6f228548b6", 
                    "registry": "*", 
                    "repository": "*", 
                    "whitelist_ids": [
                        "37fd763e-1765-11e8-add4-3b16c029ac5c"
                    ]
                }
            ], 
            "name": "production bundle", 
            "policies": [
                {
                    "comment": "System default policy", 
                    "id": "48e6f7d6-1765-11e8-b5f9-8b6f228548b6", 
                    "name": "DefaultPolicy", 
                    "rules": [
                        {
                            "action": "STOP", 
                            "gate": "dockerfile", 
                            "id": "312d9e41-1c05-4e2f-ad89-b7d34b0855bb", 
                            "params": [
                                {
                                    "name": "instruction", 
                                    "value": "HEALTHCHECK"
                                }, 
                                {
                                    "name": "check", 
                                    "value": "not_exists"
                                }
                            ], 
                            "trigger": "instruction"
                        }, 
                        {
                            "action": "STOP", 
                            "gate": "vulnerabilities", 
                            "id": "b30e8abc-444f-45b1-8a37-55be1b8c8bb5", 
                            "params": [
                                {
                                    "name": "package_type", 
                                    "value": "all"
                                }, 
                                {
                                    "name": "severity_comparison", 
                                    "value": ">="
                                }, 
                                {
                                    "name": "severity", 
                                    "value": "high"
                                }
                            ], 
                            "trigger": "package"
                        }
                    ], 
                    "version": "1_0"
                }
            ], 
            "version": "1_0", 
            "whitelisted_images": [], 
            "whitelists": [
                {
                    "comment": "Default global whitelist", 
                    "id": "37fd763e-1765-11e8-add4-3b16c029ac5c", 
                    "items": [], 
                    "name": "Global Whitelist", 
                    "version": "1_0"
                }
            ]
        }    
      testing_bundle.json: |-
        {
            "blacklisted_images": [], 
            "comment": "testing bundle", 
            "id": "testing_bundle", 
            "mappings": [
                {
                    "id": "c4f9bf74-dc38-4ddf-b5cf-00e9c0074611", 
                    "image": {
                        "type": "tag", 
                        "value": "*"
                    }, 
                    "name": "default", 
                    "policy_id": "48e6f7d6-1765-11e8-b5f9-8b6f228548b6", 
                    "registry": "*", 
                    "repository": "*", 
                    "whitelist_ids": [
                        "37fd763e-1765-11e8-add4-3b16c029ac5c"
                    ]
                }
            ], 
            "name": "Testing bundle", 
            "policies": [
                {
                    "comment": "System default policy", 
                    "id": "48e6f7d6-1765-11e8-b5f9-8b6f228548b6", 
                    "name": "DefaultPolicy", 
                    "rules": [
                        {
                            "action": "WARN", 
                            "gate": "dockerfile", 
                            "id": "312d9e41-1c05-4e2f-ad89-b7d34b0855bb", 
                            "params": [
                                {
                                    "name": "instruction", 
                                    "value": "HEALTHCHECK"
                                }, 
                                {
                                    "name": "check", 
                                    "value": "not_exists"
                                }
                            ], 
                            "trigger": "instruction"
                        }, 
                        {
                            "action": "STOP", 
                            "gate": "vulnerabilities", 
                            "id": "b30e8abc-444f-45b1-8a37-55be1b8c8bb5", 
                            "params": [
                                {
                                    "name": "package_type", 
                                    "value": "all"
                                }, 
                                {
                                    "name": "severity_comparison", 
                                    "value": ">"
                                }, 
                                {
                                    "name": "severity", 
                                    "value": "high"
                                }
                            ], 
                            "trigger": "package"
                        }
                    ], 
                    "version": "1_0"
                }
            ], 
            "version": "1_0", 
            "whitelisted_images": [], 
            "whitelists": [
                {
                    "comment": "Default global whitelist", 
                    "id": "37fd763e-1765-11e8-add4-3b16c029ac5c", 
                    "items": [], 
                    "name": "Global Whitelist", 
                    "version": "1_0"
                }
            ]
        }    
      allow-all.json: |-
        {
          "blacklisted_images": [],
          "comment": "Allow all images and warn if vulnerabilities are found",
          "id": "allow_all_and_warn",
          "mappings": [
              {
                  "id": "5fec9738-59e3-4c4c-9e74-281cbbe0337e",
                  "image": {
                      "type": "tag",
                      "value": "*"
                  },
                  "name": "allow_all",
                  "policy_id": "6472311c-e343-4d7f-9949-c258e3a5191e",
                  "registry": "*",
                  "repository": "*",
                  "whitelist_ids": []
              }
          ],
          "name": "Allow all and warn bundle",
          "policies": [
              {
                  "comment": "Allow all policy",
                  "id": "6472311c-e343-4d7f-9949-c258e3a5191e",
                  "name": "AllowAll",
                  "rules": [
                      {
                          "action": "WARN",
                          "gate": "dockerfile",
                          "id": "bf8922ba-1f4e-4c4b-9057-165aa5f84b31",
                          "params": [
                              {
                                  "name": "ports",
                                  "value": "22"
                              },
                              {
                                  "name": "type",
                                  "value": "blacklist"
                              }
                          ],
                          "trigger": "exposed_ports"
                      },
                      {
                          "action": "WARN",
                          "gate": "dockerfile",
                          "id": "c44c6e6d-6d3f-4f20-971f-f5283b840e8f",
                          "params": [
                              {
                                  "name": "instruction",
                                  "value": "HEALTHCHECK"
                              },
                              {
                                  "name": "check",
                                  "value": "not_exists"
                              }
                          ],
                          "trigger": "instruction"
                      },
                      {
                          "action": "WARN",
                          "gate": "vulnerabilities",
                          "id": "6e04f5d8-27f7-47b9-b30a-de98fdf83d85",
                          "params": [
                              {
                                  "name": "max_days_since_sync",
                                  "value": "2"
                              }
                          ],
                          "trigger": "stale_feed_data"
                      },
                      {
                          "action": "WARN",
                          "gate": "vulnerabilities",
                          "id": "8494170c-5c3e-4a59-830b-367f2a8e1633",
                          "params": [],
                          "trigger": "vulnerability_data_unavailable"
                      },
                      {
                          "action": "WARN",
                          "gate": "vulnerabilities",
                          "id": "f3a89c1c-2363-4b6f-a05d-e784496ddb6f",
                          "params": [
                              {
                                  "name": "package_type",
                                  "value": "all"
                              },
                              {
                                  "name": "severity_comparison",
                                  "value": ">"
                              },
                              {
                                  "name": "severity",
                                  "value": "medium"
                              }
                          ],
                          "trigger": "package"
                      }
                  ],
                  "version": "1_0"
              }
          ],
          "version": "1_0",
          "whitelisted_images": [],
          "whitelists": []
        }    
    ---
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: anchore-policy-uplodaer
    spec:
      template:
        metadata:
          name: anchore-policy-uplodaer
        spec:
          volumes:
          - name: anchore-policys
            configMap:
              name: anchore-policys
          containers:
          - name: anchore-policys
            image: "anchore/engine-cli"
            volumeMounts:
            - name: anchore-policys
              mountPath: /policy
            env:
            - name: ANCHORE_CLI_USER
              value: admin
            - name: ANCHORE_CLI_PASS
              value: Password1
            - name: ANCHORE_CLI_URL
              value: http://anchore-enginn-anchore-engine-api:8228
            securityContext:
              runAsUser: 2
            command:
            - "sh"
            - "-c"
            - |
              set -ex
              anchore-cli policy add /policy/production_bundle.json
              anchore-cli policy add /policy/testing_bundle.json
              anchore-cli policy add /policy/allow-all.json          
          restartPolicy: OnFailure
    

    Sadly anchore-image-validator run as root so we need to use my predifinde PSP to allow this.

    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: psp-rolebinding-securty-system
      namespace: securty-system
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: system-unrestricted-psp-role
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: Group
      name: system:serviceaccounts
    
    ---
    apiVersion: helm.cattle.io/v1
    kind: HelmChart
    metadata:
      name: anchore-policy-validator
      namespace: kube-system
    spec:
      repo: "https://charts.anchore.io/stable"
      chart: anchore-admission-controller
      targetNamespace: securty-system
      valuesContent: |-
        existingCredentialsSecret: anchore-credentials   
        anchoreEndpoint: "http://anchore-enginn-anchore-engine-api:8228"
        policySelectors:
        - Selector:
            ResourceType: "pod"
            SelectorKeyRegex: "^breakglass$"
            SelectorValueRegex: "^true$"
          PolicyReference:
            Username: "admin"
            PolicyBundleId: "testing_bundle"
          Mode: breakglass
        - Selector:
            ResourceType: "namespace"
            SelectorKeyRegex: "name"
            SelectorValueRegex: "^testing$"
          PolicyReference:
            Username: "admin"
            PolicyBundleId: "testing_bundle"
          Mode: policy
        - Selector:
            ResourceType: "namespace"
            SelectorKeyRegex: "name"
            SelectorValueRegex: "^production$"
          PolicyReference:
            Username: "admin"
            PolicyBundleId: "production_bundle"
          Mode: policy
        - Selector:
            ResourceType: "image"
            SelectorKeyRegex: ".*"
            SelectorValueRegex: ".*"
          PolicyReference:
            Username: "admin"
            PolicyBundleId: "allow-all"
          Mode: breakglass    
    

    Check the config of anchore server

    kubectl run -i -t anchorecli --image anchore/engine-cli --restart=Always \
    --env ANCHORE_CLI_URL=http://anchore-enginn-anchore-engine-api:8228 \
    --env ANCHORE_CLI_USER=admin \
    --env ANCHORE_CLI_PASS=Password1
    
    # check policys
    anchore-cli policy list
    
    anchore-cli image add nginx
    anchore-cli image list
    
    anchore-cli evaluate check alpine --policy testing_bundle
    anchore-cli evaluate check alpine --policy production_bundle
    

    Test the Admission Controller

    kubectl create ns testing
    kubectl create ns production
    kubectl create ns www
    
    kubectl -n testing run -it alpine --restart=Never --image alpine /bin/sh                                                                               
    If you don't see a command prompt, try pressing enter.
    / #
    
    
    kubectl -n production run -it alpine --restart=Never --image alpine /bin/sh
    Error from server: admission webhook "anchore-admission-controller-admission.anchore.io" denied the request: Image alpine with digest sha256:e103c1b4bf019dc290bcc7aca538dc2bf7a9d0fc836e186f5fa34945c5168310 failed policy checks for policy bundle production_bundle
    
    kubectl -n production run -it alpine --labels="breakglass=true" --restart=Never --image alpine /bin/sh
    If you don't see a command prompt, try pressing enter.
    / #
    

    As you can see the alpine image failed in the policy checks in bruducrion namespace but if you add the “breakglass=true” label, it will be allowed:

    kubectl -n production run -it alpine --restart=Never --labels="breakglass=true" --image alpine /bin/sh
    If you don't see a command prompt, try pressing enter.
    / # exit