EKSでFalcoを試す

以下のAWSブログにしたがってEKSでFalcoを試したメモ。

もう一つFalcoに関するAWSブログがあるが、内容がちょっと古い。

コンポーネント バージョン
EKS 1.18
プラットフォームバージョン eks.3
Falco 0.27.0
Falcoチャート 1.7.7

クラスターの準備

はじめは1.19で試したがFalcoのPodがドライバーのインストールに失敗し、CrashLoopBackOffとなり起動しなかった。

$ k logs -f falco-czwwm
* Setting up /usr/src links from host
* Running falco-driver-loader with: driver=module, compile=yes, download=yes
* Unloading falco module, if present
* Trying to dkms install falco module with GCC /usr/bin/gcc
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc)
* Trying to dkms install falco module with GCC /usr/bin/gcc-8
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc-8)
* Trying to dkms install falco module with GCC /usr/bin/gcc-6
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc-6)
* Trying to dkms install falco module with GCC /usr/bin/gcc-5
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc-5)
* Trying to load a system falco driver, if present
* Trying to find locally a prebuilt falco module for kernel 5.4.95-42.163.amzn2.x86_64, if present
* Trying to download prebuilt module from https://download.falco.org/driver/5c0b863ddade7a45568c0ac97d037422c9efb750/falco_amazonlinux2_5.4.95-42.163.amzn2.x86_64_1.ko
curl: (22) The requested URL returned error: 404 
Download failed, consider compiling your own falco module and loading it or getting in touch with the Falco community
Tue Mar  9 02:23:42 2021: Falco version 0.27.0 (driver version 5c0b863ddade7a45568c0ac97d037422c9efb750)
Tue Mar  9 02:23:42 2021: Falco initialized with configuration file /etc/falco/falco.yaml
Tue Mar  9 02:23:42 2021: Loading rules from file /etc/falco/falco_rules.yaml:
Tue Mar  9 02:23:43 2021: Loading rules from file /etc/falco/falco_rules.local.yaml:
Tue Mar  9 02:23:44 2021: Unable to load the driver.
Tue Mar  9 02:23:44 2021: Runtime error: error opening device /host/dev/falco0. Make sure you have root credentials and that the falco module is loaded.. Exiting.

1.18でクラスターを作成する。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: falco
  region: ap-northeast-1
  version: "1.18"
vpc:
  cidr: "10.1.0.0/16"

availabilityZones:
  - ap-northeast-1a
  - ap-northeast-1c

managedNodeGroups:
  - name: managed-ng-1
    minSize: 2
    maxSize: 2
    desiredCapacity: 2
    ssh:
      allow: true
      publicKeyName: default

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
eksctl create cluster -f cluster.yaml

サンプルアプリのデプロイ

サンプルのNginx Deploymentを作成する。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx2
  labels:
    app: nginx2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx2
  template:
    metadata:
      labels:
        app: nginx2
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: beta.kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64
      containers:
      - name: nginx
        image: nginx:1.19.2
        ports:
        - containerPort: 80
$ kubectl apply -f deployment.yaml
deployment.apps/nginx2 created

Deploymentを確認する。

$ kubectl get deployments --all-namespaces
NAMESPACE     NAME      READY   UP-TO-DATE   AVAILABLE   AGE
default       nginx2    3/3     3            3           10s
kube-system   coredns   2/2     2            2           26m

Fluent Bitのデプロイ

FalcoとFirelensとのインテグレーションのリポジトリをクローンする。

git clone http://github.com/sysdiglabs/falco-aws-firelens-integration

Fluent Bit用のIAMポリシーを作成する。

$ cd falco-aws-firelens-integration/eks/
$ aws iam create-policy --policy-name EKS-CloudWatchLogs --policy-document file://./fluent-bit/aws/iam_role_policy.json
{
    "Policy": {
        "PolicyName": "EKS-CloudWatchLogs",
        "PolicyId": "ANPASYSBLVT2LCKTUML73",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:policy/EKS-CloudWatchLogs",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2021-03-09T01:50:04+00:00",
        "UpdateDate": "2021-03-09T01:50:04+00:00"
    }
}

EKSのノードにこのポリシーをアタッチする。今ならIRSAを使うところだがこのままにする。

POLICY_ARN=$(aws iam list-policies | jq -r '.[][] | select(.PolicyName == "EKS-CloudWatchLogs") | .Arn')
ROLE_NAME=$(aws iam list-roles | jq -r '.[][] | select( .RoleName | contains("falco") and contains("NodeInstanceRole") ) | .RoleName')
aws iam attach-role-policy --role-name ${ROLE_NAME} --policy-arn ${POLICY_ARN}

Fluent Bitの設定ファイルは以下のようになっているので、リージョンだけap-northeast-1に直しておく。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  labels:
    app.kubernetes.io/name: fluentbit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parsers.conf
    [INPUT]
        Name              tail
        Tag               falco.*
        Path              /var/log/containers/falco*.log
        Parser            falco
        DB                /var/log/flb_falco.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10
    [OUTPUT]
        Name cloudwatch
        Match falco.**
        region ap-northeast-1
        log_group_name falco
        log_stream_name alerts
        auto_create_group true
  parsers.conf: |
    [PARSER]
        Name        falco
        Format      json
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L
        Time_Keep   Off
        # Command      |  Decoder | Field | Optional Action
        # =============|==================|=================
        Decode_Field_As   json    log

Fluent Bitをデプロイする。

kubectl apply -f fluent-bit/kubernetes/

Podを確認する。

$ k get pod
NAME                      READY   STATUS    RESTARTS   AGE
fluentbit-8s5tc           1/1     Running   0          37s
fluentbit-mmqh9           1/1     Running   0          37s
nginx2-5d48dd4879-k8rzr   1/1     Running   0          2m34s
nginx2-5d48dd4879-klpx8   1/1     Running   0          2m34s
nginx2-5d48dd4879-lc49k   1/1     Running   0          2m34s

Falcoのデプロイ

FalcoのHelmチャートリポジトリを追加する。

helm repo add falcosecurity https://falcosecurity.github.io/charts

リポジトリを確認する。

$ helm repo list
NAME                    URL                                                            
(省略)
falcosecurity           https://falcosecurity.github.io/charts   

リポジトリをクローンする。

git clone https://github.com/falcosecurity/charts

falcoチャートのディレクトリのrulesフォルダにルールファイルが格納されている。

デフォルト設定でFalcoをインストールする。

$ helm install falco falcosecurity/falco
NAME: falco
LAST DEPLOYED: Tue Mar  9 13:09:41 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Falco agents are spinning up on each node in your cluster. After a few
seconds, they are going to start monitoring your containers looking for
security issues.


No further action should be required.


Tip: 
You can easily forward Falco events to Slack, Kafka, AWS Lambda and more with falcosidekick. 
Full list of outputs: https://github.com/falcosecurity/charts/falcosidekick.
You can enable its deployment with `--set falcosidekick.enabled=true` or in your values.yaml. 
See: https://github.com/falcosecurity/charts/blob/master/falcosidekick/values.yaml for configuration values.

Tipに表示されているように以下を使うとイベントの連携が簡単にできそう。

Podを確認する。

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
default       falco-2h7sd                1/1     Running   0          32s
default       falco-pjlsw                1/1     Running   0          32s
default       fluentbit-8s5tc            1/1     Running   0          94s
default       fluentbit-mmqh9            1/1     Running   0          94s
default       nginx2-5d48dd4879-k8rzr    1/1     Running   0          3m31s
default       nginx2-5d48dd4879-klpx8    1/1     Running   0          3m31s
default       nginx2-5d48dd4879-lc49k    1/1     Running   0          3m31s
kube-system   aws-node-md79b             1/1     Running   0          7m41s
kube-system   aws-node-rtl2g             1/1     Running   0          7m44s
kube-system   coredns-86f7d88d77-7kggj   1/1     Running   0          30m
kube-system   coredns-86f7d88d77-mk88s   1/1     Running   0          30m
kube-system   kube-proxy-2rtww           1/1     Running   0          7m44s
kube-system   kube-proxy-wvfss           1/1     Running   0          7m41s

ログを確認する。ドライバーのダウンロードに成功している。

$ k logs falco-2h7sd
* Setting up /usr/src links from host
* Running falco-driver-loader with: driver=module, compile=yes, download=yes
* Unloading falco module, if present
* Trying to dkms install falco module with GCC /usr/bin/gcc
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc)
* Trying to dkms install falco module with GCC /usr/bin/gcc-8
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc-8)
* Trying to dkms install falco module with GCC /usr/bin/gcc-6
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc-6)
* Trying to dkms install falco module with GCC /usr/bin/gcc-5
DIRECTIVE: MAKE="'/tmp/falco-dkms-make'"
* Running dkms build failed, couldn't find /var/lib/dkms/falco/5c0b863ddade7a45568c0ac97d037422c9efb750/build/make.log (with GCC /usr/bin/gcc-5)
* Trying to load a system falco driver, if present
* Trying to find locally a prebuilt falco module for kernel 4.14.219-161.340.amzn2.x86_64, if present
* Trying to download prebuilt module from https://download.falco.org/driver/5c0b863ddade7a45568c0ac97d037422c9efb750/falco_amazonlinux2_4.14.219-161.340.amzn2.x86_64_1.ko
* Download succeeded
* Success: falco module loaded
Tue Mar  9 04:10:06 2021: Falco version 0.27.0 (driver version 5c0b863ddade7a45568c0ac97d037422c9efb750)
Tue Mar  9 04:10:06 2021: Falco initialized with configuration file /etc/falco/falco.yaml
Tue Mar  9 04:10:06 2021: Loading rules from file /etc/falco/falco_rules.yaml:
Tue Mar  9 04:10:07 2021: Loading rules from file /etc/falco/falco_rules.local.yaml:
Tue Mar  9 04:10:08 2021: Starting internal webserver, listening on port 8765
04:10:08.109765000: Notice Privileged container started (user=<NA> user_loginuid=0 command=container:e1ad82b7bf81 k8s.ns=kube-system k8s.pod=kube-proxy-wvfss container=e1ad82b7bf81 image=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/kube-proxy:v1.18.8-eksbuild.1) k8s.ns=kube-system k8s.pod=kube-proxy-wvfss container=e1ad82b7bf81

CloudWatch Logsを確認すると、例えば上記の最後の1行が以下のように出力されているのが確認できる。Kubernetesのパーサーを通していないので、Namespaceとかのメタ情報は付与されていない。

{
    "log": "04:10:08.109765000: Notice Privileged container started (user=<NA> user_loginuid=0 command=container:e1ad82b7bf81 k8s.ns=kube-system k8s.pod=kube-proxy-wvfss container=e1ad82b7bf81 image=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/kube-proxy:v1.18.8-eksbuild.1) k8s.ns=kube-system k8s.pod=kube-proxy-wvfss container=e1ad82b7bf81\n",
    "stream": "stdout"
}

テスト

例1

Nginxのコンテナに入り、/etc以下に対して操作を行う。

fluentbit-mmqh9           1/1     Running   0          11m
nginx2-5d48dd4879-k8rzr   1/1     Running   0          13m
nginx2-5d48dd4879-klpx8   1/1     Running   0          13m
nginx2-5d48dd4879-lc49k   1/1     Running   0          13m
$ k exec -it nginx2-5d48dd4879-k8rzr -- bash
root@nginx2-5d48dd4879-k8rzr:/# touch /etc/2
root@nginx2-5d48dd4879-k8rzr:/# cat /etc/shadow > /dev/null 2>&1
root@nginx2-5d48dd4879-k8rzr:/# 

このPodと同じノードのFalcoのPodのログに出力される。

04:20:57.661292144: Error File below /etc opened for writing (user=root user_loginuid=-1 command=touch /etc/2 parent=bash pcmdline=bash file=/etc/2 program=touch gparent=<NA> ggparent=<NA> gggparent=<NA> container_id=0dfdabe83a1e image=nginx) k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e
04:21:26.844237768: Warning Sensitive file opened for reading by non-trusted program (user=root user_loginuid=-1 program=cat command=cat /etc/shadow file=/etc/shadow parent=bash gparent=<NA> ggparent=<NA> gggparent=<NA> container_id=0dfdabe83a1e image=nginx) k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e

例2

Nginxのコンテナに入り、/binディレクトリを作成する。

root@nginx2-5d48dd4879-k8rzr:/# cd /bin
root@nginx2-5d48dd4879-k8rzr:/bin# mkdir hello

ログにアラートが出力される。

04:25:43.204565892: Error Directory below known binary directory created (user=root user_loginuid=-1 command=mkdir hello directory=/bin/hello container_id=0dfdabe83a1e image=nginx) k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e

カスタムルールの作成

Helmチャートのデフォルト値を確認する。

$ helm inspect values falcosecurity/falco

custom_alerts.yamlを作成しカスタムルールを定義する。

customRules:
  rules-nginx.yaml: |
    - macro: nginx_consider_syscalls
      condition: (evt.num < 0)

    - macro: app_nginx
      condition: container and container.image contains "nginx"

    # Any outbound traffic raises a WARNING

    - rule: The program "whoami" is run in a container
      desc: An event will trigger every time you run "whoami" in a container
      condition: evt.type = execve and evt.dir=< and container.id != host and proc.name = whoami
      output: "whoami command run in container (user=%user.name %container.info parent=%proc.pname cmdline=%proc.cmdline)"
      priority: NOTICE
      warn_evttypes: False

    - rule: The program "locate" is run in a container
      desc: An event will trigger every time you run "locate" in a container
      condition: evt.type = execve and evt.dir=< and container.id != host and proc.name = locate
      output: "locate command run in container (user=%user.name %container.info parent=%proc.pname cmdline=%proc.cmdline)"
      priority: NOTICE
      warn_evttypes: False

リリースを更新する。

helm upgrade -i falco -f custom_alerts.yaml falcosecurity/falco

追加のルールは別のConfigMapになっている。

$ k get cm
NAME                DATA   AGE
falco               5      28m
falco-rules         1      10s
fluent-bit-config   2      29m
$ k get cm falco-rules -o yaml | k neat
apiVersion: v1
data:
  rules-nginx.yaml: |-
    - macro: nginx_consider_syscalls
      condition: (evt.num < 0)

    - macro: app_nginx
      condition: container and container.image contains "nginx"

    # Any outbound traffic raises a WARNING

    - rule: The program "whoami" is run in a container
      desc: An event will trigger every time you run "whoami" in a container
      condition: evt.type = execve and evt.dir=< and container.id != host and proc.name = whoami
      output: "whoami command run in container (user=%user.name %container.info parent=%proc.pname cmdline=%proc.cmdline)"
      priority: NOTICE
      warn_evttypes: False

    - rule: The program "locate" is run in a container
      desc: An event will trigger every time you run "locate" in a container
      condition: evt.type = execve and evt.dir=< and container.id != host and proc.name = locate
      output: "locate command run in container (user=%user.name %container.info parent=%proc.pname cmdline=%proc.cmdline)"
      priority: NOTICE
      warn_evttypes: False
kind: ConfigMap
metadata:
  annotations:
    meta.helm.sh/release-name: falco
    meta.helm.sh/release-namespace: default
  labels:
    app: falco
    app.kubernetes.io/managed-by: Helm
    chart: falco-1.7.7
    heritage: Helm
    release: falco
  name: falco-rules
  namespace: default

Nginxのコンテナに入り、whoamifindコマンドを実行する。

Falcoのログを確認する。whoamiのログが確認できる。findは確認できない。そのようなルールは定義していないので、ブログの記載ミスと思われる。

04:41:08.686680090: Notice whoami command run in container (user=root k8s.ns=default k8s.pod=nginx2-5d48dd4879-k8rzr container=0dfdabe83a1e parent=bash cmdline=whoami)

CloudWatch Insights

CloudWatchで新しいダッシュボードを作成する。

f:id:sotoiwa:20210309140855p:plain

ログテーブルのウィジットを2つ作成する。

f:id:sotoiwa:20210309140909p:plain

fields @timestamp, @message
| filter @message like 'Mkdir binary dirs'
| sort @timestamp desc
fields @timestamp, @message
| filter @message like 'Read sensitive file untrusted'
| sort @timestamp desc

f:id:sotoiwa:20210309140927p:plain

CloudWatch アラーム

falcoのロググループのIncommingLogEventsメトリクスに対してアラームを作成する。

f:id:sotoiwa:20210309140942p:plain

f:id:sotoiwa:20210309140956p:plain

これによってFalcoにログが追記されるとアラームが発報する。

f:id:sotoiwa:20210309141009p:plain

なお、これだけだと起動時のログでも通知されるし、通知の中にログの中に内容が入っていないので、あまり実用的ではない。

Slack連携

Slack等への連携は、falcosecurity/falcosidekickが利用可能だが、Falco本体の機能でもOutputとしてプログラム実行ができ、SlackのIncoming Webhookを使った連携が可能。

チャートのvaluesに以下を追加する。

falco:
  jsonOutput: true
  programOutput:
    enabled: true
    keepAlive: false
    program: "jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/XXXX"

リリースを更新する。

helm upgrade -i falco -f custom_alerts.yaml falcosecurity/falco

これでSlackに通知が飛ぶようになる。

f:id:sotoiwa:20210309162108p:plain

ECS タスク起動ハマりポイント

よく ECS タスクが上手く起動しなくてハマるので、ハマったポイントをメモしておく。

  • コンテナのポートを間違えていてヘルスチェックに失敗している
  • ALB からのヘルスチェックがセキュリティグループにブロックされている
  • AmazonECSTaskExecutionRolePolicy にはロググループを作る権限がないので、ロググループを自分で作るか、権限を追加してタスク定義のログドライバー設定で awslogs-create-grouptrue に設定する必要がある
  • タスクを(間違えて)Public サブネットで起動しようとしているが、サブネットの設定で Public IP を自動的にアサインするようになっていない
  • アプリの起動が遅いので、ECS サービス設定でヘルスチェックの猶予期間を設定するか、ALB のヘルスチェックにゆとりを持たせる必要がある
  • Spring Boot アプリへのリソース割り当てが不足

参考リンク

CDKの導入

ひさしぶりにCDKを使おうとして環境の更新方法を忘れてたのでメモ。

Node.js

Node.jsはnodenvを使って導入している。

CDKの要件はNode.js 10.3.0以上で最新のLTSバージョンが推奨。

LTSリリースは以下で確認。

Node.jsの利用可能なバージョンを確認する。

nodenv install --list

14系の最新を入れる。

$ nodenv install 14.15.5
Downloading node-v14.15.5-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v14.15.5/node-v14.15.5-darwin-x64.tar.gz
Installing node-v14.15.5-darwin-x64...
Installed node-v14.15.5-darwin-x64 to /Users/sotosugi/.nodenv/versions/14.15.5

Global設定をそちらに向ける。

nodenv global 14.15.5

入っているバージョンと設定を確認。

$ nodenv versions
  12.13.0
  12.20.2
* 14.15.5 (set by /Users/sotosugi/.nodenv/version)

npm

npmを更新する。

$ npm --version
6.14.11
$ npm install -g npm
/Users/sotosugi/.nodenv/versions/14.15.5/bin/npm -> /Users/sotosugi/.nodenv/versions/14.15.5/lib/node_modules/npm/bin/npm-cli.js
/Users/sotosugi/.nodenv/versions/14.15.5/bin/npx -> /Users/sotosugi/.nodenv/versions/14.15.5/lib/node_modules/npm/bin/npx-cli.js
+ npm@7.5.6
added 59 packages from 24 contributors, removed 241 packages and updated 194 packages in 6.259s
$ npm --version
7.5.6

CDK

CDKを導入する。

$ npm install -g aws-cdk

added 193 packages, and audited 194 packages in 4s

found 0 vulnerabilities
$ cdk --version
1.91.0 (build 0f728ce)

Kyvernoを試す

Kyvernoを試してみたメモ。AWSブログにしたがって試す。

参考リンク

Kyvernoのインストール

クラスターを作成する。

eksctl create cluster -f cluster.yaml

マニフェストまたはHelmでインストールが可能。

$ helm search repo kyverno
NAME            CHART VERSION   APP VERSION     DESCRIPTION                        
kyverno/kyverno v1.3.3          v1.3.3          Kubernetes Native Policy Management

今回はマニフェストで導入する。

$ kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/master/definitions/release/install.yaml
namespace/kyverno created
customresourcedefinition.apiextensions.k8s.io/clusterpolicies.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/clusterpolicyreports.wgpolicyk8s.io created
customresourcedefinition.apiextensions.k8s.io/clusterreportchangerequests.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/generaterequests.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/policies.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/policyreports.wgpolicyk8s.io created
customresourcedefinition.apiextensions.k8s.io/reportchangerequests.kyverno.io created
serviceaccount/kyverno-service-account created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-policies created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-policyreport created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-reportchangerequest created
clusterrole.rbac.authorization.k8s.io/kyverno:customresources created
clusterrole.rbac.authorization.k8s.io/kyverno:generatecontroller created
clusterrole.rbac.authorization.k8s.io/kyverno:policycontroller created
clusterrole.rbac.authorization.k8s.io/kyverno:userinfo created
clusterrole.rbac.authorization.k8s.io/kyverno:webhook created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:customresources created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:generatecontroller created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:policycontroller created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:userinfo created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:webhook created
configmap/init-config created
service/kyverno-svc created
deployment.apps/kyverno created

特権コンテナの禁止

以下のようなポリシーを作成する。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
  annotations:
    policies.kyverno.io/category: Security
    policies.kyverno.io/description: Privileged containers are defined as any
      container where the container uid 0 is mapped to the host’s uid 0.
      A process within a privileged container can get unrestricted host access.
      With `securityContext.allowPrivilegeEscalation` enabled, a process can
      gain privileges from its parent.
spec:
  validationFailureAction: enforce
  rules:
  - name: validate-privileged
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Privileged mode is not allowed. Set privileged to false"
      pattern:
        spec:
          containers:
          - =(securityContext):
              =(privileged): false
  - name: validate-allowPrivilegeEscalation
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Privileged mode is not allowed. Set allowPrivilegeEscalation to false"
      pattern:
        spec:
          containers:
          - securityContext:
              allowPrivilegeEscalation: false
$ kubectl create -f disallow_privileged.yaml
clusterpolicy.kyverno.io/disallow-privileged created

作成したポリシーを確認する。

$ kubectl get cpol
NAME                  BACKGROUND   ACTION
disallow-privileged   true         enforce

自動生成の機能でアノテーションが追加されているし、Pod以外の定義も追加されている。

$ k get cpol disallow-privileged -o yaml | k neat
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  annotations:
    pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,Job,StatefulSet,CronJob
    policies.kyverno.io/category: Security
    policies.kyverno.io/description: Privileged containers are defined as any container
      where the container uid 0 is mapped to the host’s uid 0. A process within a
      privileged container can get unrestricted host access. With `securityContext.allowPrivilegeEscalation`
      enabled, a process can gain privileges from its parent.
  name: disallow-privileged
spec:
  background: true
  rules:
  - match:
      resources:
        kinds:
        - Pod
    name: validate-privileged
    validate:
      message: Privileged mode is not allowed. Set privileged to false
      pattern:
        spec:
          containers:
          - =(securityContext):
              =(privileged): false
  - match:
      resources:
        kinds:
        - Pod
    name: validate-allowPrivilegeEscalation
    validate:
      message: Privileged mode is not allowed. Set allowPrivilegeEscalation to false
      pattern:
        spec:
          containers:
          - securityContext:
              allowPrivilegeEscalation: false
  - match:
      resources:
        kinds:
        - DaemonSet
        - Deployment
        - Job
        - StatefulSet
    name: autogen-validate-privileged
    validate:
      message: Privileged mode is not allowed. Set privileged to false
      pattern:
        spec:
          template:
            spec:
              containers:
              - =(securityContext):
                  =(privileged): false
  - match:
      resources:
        kinds:
        - CronJob
    name: autogen-cronjob-validate-privileged
    validate:
      message: Privileged mode is not allowed. Set privileged to false
      pattern:
        spec:
          jobTemplate:
            spec:
              template:
                spec:
                  containers:
                  - =(securityContext):
                      =(privileged): false
  - match:
      resources:
        kinds:
        - DaemonSet
        - Deployment
        - Job
        - StatefulSet
    name: autogen-validate-allowPrivilegeEscalation
    validate:
      message: Privileged mode is not allowed. Set allowPrivilegeEscalation to false
      pattern:
        spec:
          template:
            spec:
              containers:
              - securityContext:
                  allowPrivilegeEscalation: false
  - match:
      resources:
        kinds:
        - CronJob
    name: autogen-cronjob-validate-allowPrivilegeEscalation
    validate:
      message: Privileged mode is not allowed. Set allowPrivilegeEscalation to false
      pattern:
        spec:
          jobTemplate:
            spec:
              template:
                spec:
                  containers:
                  - securityContext:
                      allowPrivilegeEscalation: false
  validationFailureAction: enforce

特権昇格が可能なNginxのPodを作成してみる。

apiVersion: v1
kind: Pod
metadata:
  name: nginx-privileged
  labels:
    app: nginx-privileged
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      allowPrivilegeEscalation: true

拒否される。

$ kubectl create -f nginx-privileged.yaml
Error from server: error when creating "nginx-privileged.yaml": admission webhook "validate.kyverno.svc" denied the request: 

resource Pod/default/nginx-privileged was blocked due to the following policies

disallow-privileged:
  validate-allowPrivilegeEscalation: 'validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule validate-allowPrivilegeEscalation failed at path /spec/containers/0/securityContext/allowPrivilegeEscalation/'

同じPodSpecのDeployment定義で試す。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-privileged
  name: nginx-privileged
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-privileged
  template:
    metadata:
      labels:
        app: nginx-privileged
    spec:
      containers:
      - image: nginx
        name: nginx
        securityContext:
          allowPrivilegeEscalation: true

ちゃんと拒否される。

$ k create -f nginx-privileged-deploy.yaml
Error from server: error when creating "nginx-privileged-deploy.yaml": admission webhook "validate.kyverno.svc" denied the request: 

resource Deployment/default/nginx-privileged was blocked due to the following policies

disallow-privileged:
  autogen-validate-allowPrivilegeEscalation: 'validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule autogen-validate-allowPrivilegeEscalation failed at path /spec/template/spec/containers/0/securityContext/allowPrivilegeEscalation/'

不明なイメージレジストリの禁止

不明なイメージレジストリを禁止する以下のポリシーを作成する。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deployment-pod-valid-registry
  labels:
    app: kyverno
  annotations:
    policies.kyverno.io/category: Compliance
    policies.kyverno.io/description: Rules to enforce correct image source registry
spec:
  validationFailureAction: enforce
  rules:
  - name: validate-registries
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Unknown image registry"
      pattern:
        spec:
          containers:
          - image: "k8s.gcr.io/* | gallery.ecr.aws/*"
$ k create -f unknown-image-registry.yaml
clusterpolicy.kyverno.io/deployment-pod-valid-registry created

Docker HubからイメージをPullするPod定義で試す。

apiVersion: v1
kind: Pod
metadata:
  name: mongo
  labels:
    app: mongo
spec:
  containers:
  - name: mongo
    image: mongo:latest

拒否される。

$ k create -f mongo.yaml
Error from server: error when creating "mongo.yaml": admission webhook "validate.kyverno.svc" denied the request: 

resource Pod/default/mongo was blocked due to the following policies

deployment-pod-valid-registry:
  validate-registries: 'validation error: Unknown image registry. Rule validate-registries failed at path /spec/containers/0/image/'
disallow-privileged:
  validate-allowPrivilegeEscalation: 'validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule validate-allowPrivilegeEscalation failed at path /spec/containers/0/securityContext/'

ArgoCDを使用した自動化

ArgoCDのインストールがこけるので、ここまでに作成したポリシーを削除する。

$ k delete cpol --all
clusterpolicy.kyverno.io "deployment-pod-valid-registry" deleted
clusterpolicy.kyverno.io "disallow-privileged" deleted

ArgoCDをインストールする。

$ kubectl create namespace argocd
namespace/argocd created
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
serviceaccount/argocd-application-controller created
serviceaccount/argocd-dex-server created
serviceaccount/argocd-redis created
serviceaccount/argocd-server created
role.rbac.authorization.k8s.io/argocd-application-controller created
role.rbac.authorization.k8s.io/argocd-dex-server created
role.rbac.authorization.k8s.io/argocd-redis created
role.rbac.authorization.k8s.io/argocd-server created
clusterrole.rbac.authorization.k8s.io/argocd-application-controller created
clusterrole.rbac.authorization.k8s.io/argocd-server created
rolebinding.rbac.authorization.k8s.io/argocd-application-controller created
rolebinding.rbac.authorization.k8s.io/argocd-dex-server created
rolebinding.rbac.authorization.k8s.io/argocd-redis created
rolebinding.rbac.authorization.k8s.io/argocd-server created
clusterrolebinding.rbac.authorization.k8s.io/argocd-application-controller created
clusterrolebinding.rbac.authorization.k8s.io/argocd-server created
configmap/argocd-cm created
configmap/argocd-gpg-keys-cm created
configmap/argocd-rbac-cm created
configmap/argocd-ssh-known-hosts-cm created
configmap/argocd-tls-certs-cm created
secret/argocd-secret created
service/argocd-dex-server created
service/argocd-metrics created
service/argocd-redis created
service/argocd-repo-server created
service/argocd-server created
service/argocd-server-metrics created
deployment.apps/argocd-dex-server created
deployment.apps/argocd-redis created
deployment.apps/argocd-repo-server created
deployment.apps/argocd-server created
statefulset.apps/argocd-application-controller created

ArgoCDをType: LoadBalancerで外部公開する。

kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
export ARGOCD_SERVER=`kubectl get svc argocd-server -n argocd -o json | jq --raw-output .status.loadBalancer.ingress[0].hostname`

パスワードを取得する。

ARGO_PWD=`kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2`

UIにログインしてNEW APPを作成する。

f:id:sotoiwa:20210219023808p:plain

ソースにサンプルリポジトリhttps://github.com/texanraj/kyverno/tree/master/samples/core_best_practicesを入力する。その他以下のように設定してCREATEする。

f:id:sotoiwa:20210219023827p:plain

f:id:sotoiwa:20210219023848p:plain

f:id:sotoiwa:20210219023902p:plain

SYNCとSYNCHRONIZEをクリックして同期する。

f:id:sotoiwa:20210219023918p:plain

なお、自動生成部分があるため、ちゃんと同期しない。

f:id:sotoiwa:20210219023937p:plain

ポリシーを確認する。

$ kubectl get cpol
NAME                         BACKGROUND   ACTION
disallow-bind-mounts         true         audit
disallow-docker-sock-mount   true         audit
disallow-helm-tiller         true         audit
disallow-host-network-port   true         audit
disallow-new-capabilities    true         audit
disallow-privileged          true         enforce
disallow-root-user           true         enforce
disallow-sysctls             true         enforce
require-ro-rootfs            true         audit

ポリシー違反のレポートはPolicyReport/ClusterPolicyReportを確認する。

$ kubectl get cpolr
NAME                  PASS   FAIL   WARN   ERROR   SKIP   AGE
clusterpolicyreport   0      0      0      0       0      40m
$ kubectl get polr -A
NAMESPACE   NAME             PASS   FAIL   WARN   ERROR   SKIP   AGE
argocd      polr-ns-argocd   36     14     0      0       0      40m
$ kubectl describe polr -A | grep -i "status: \+fail" -B10
  Message:        validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule autogen-validate-allowPrivilegeEscalation failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         disallow-privileged
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-dex-server
    Namespace:    argocd
    UID:          b246153b-2852-46e6-bd7d-6b076bbc5734
  Rule:           autogen-validate-allowPrivilegeEscalation
  Scored:         true
  Status:         fail
--
  Message:        validation error: Root filesystem must be read-only. Rule autogen-validate-readOnlyRootFilesystem failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         require-ro-rootfs
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-repo-server
    Namespace:    argocd
    UID:          6d4581bb-3571-40f3-a5a8-83d0d4292e9b
  Rule:           autogen-validate-readOnlyRootFilesystem
  Scored:         true
  Status:         fail
--
  Message:        validation error: Root filesystem must be read-only. Rule autogen-validate-readOnlyRootFilesystem failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         require-ro-rootfs
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-dex-server
    Namespace:    argocd
    UID:          b246153b-2852-46e6-bd7d-6b076bbc5734
  Rule:           autogen-validate-readOnlyRootFilesystem
  Scored:         true
  Status:         fail
--
  Message:        validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule autogen-validate-allowPrivilegeEscalation failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         disallow-privileged
  Resources:
    API Version:  apps/v1
    Kind:         StatefulSet
    Name:         argocd-application-controller
    Namespace:    argocd
    UID:          883b6d14-ea19-4f31-b0b9-c89efed157e6
  Rule:           autogen-validate-allowPrivilegeEscalation
  Scored:         true
  Status:         fail
--
  Message:        validation error: Root filesystem must be read-only. Rule autogen-validate-readOnlyRootFilesystem failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         require-ro-rootfs
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-server
    Namespace:    argocd
    UID:          085a25b7-a84c-42bf-9098-0e27acb38486
  Rule:           autogen-validate-readOnlyRootFilesystem
  Scored:         true
  Status:         fail
--
  Message:        validation error: Root filesystem must be read-only. Rule autogen-validate-readOnlyRootFilesystem failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         require-ro-rootfs
  Resources:
    API Version:  apps/v1
    Kind:         StatefulSet
    Name:         argocd-application-controller
    Namespace:    argocd
    UID:          883b6d14-ea19-4f31-b0b9-c89efed157e6
  Rule:           autogen-validate-readOnlyRootFilesystem
  Scored:         true
  Status:         fail
--
  Message:        validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule autogen-validate-allowPrivilegeEscalation failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         disallow-privileged
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-server
    Namespace:    argocd
    UID:          085a25b7-a84c-42bf-9098-0e27acb38486
  Rule:           autogen-validate-allowPrivilegeEscalation
  Scored:         true
  Status:         fail
--
  Message:        validation error: Running as root is not allowed. Set runAsNonRoot to true, or use runAsUser. Rule autogen-validate-runAsNonRoot[0] failed at path /spec/template/spec/securityContext/runAsNonRoot/. Rule autogen-validate-runAsNonRoot[1] failed at path /spec/template/spec/securityContext/runAsUser/. Rule autogen-validate-runAsNonRoot[2] failed at path /spec/template/spec/containers/0/securityContext/. Rule autogen-validate-runAsNonRoot[3] failed at path /spec/template/spec/containers/0/securityContext/.
  Policy:         disallow-root-user
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-repo-server
    Namespace:    argocd
    UID:          6d4581bb-3571-40f3-a5a8-83d0d4292e9b
  Rule:           autogen-validate-runAsNonRoot
  Scored:         true
  Status:         fail
--
  Message:        validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule autogen-validate-allowPrivilegeEscalation failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         disallow-privileged
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-repo-server
    Namespace:    argocd
    UID:          6d4581bb-3571-40f3-a5a8-83d0d4292e9b
  Rule:           autogen-validate-allowPrivilegeEscalation
  Scored:         true
  Status:         fail
--
  Message:        validation error: Running as root is not allowed. Set runAsNonRoot to true, or use runAsUser. Rule autogen-validate-runAsNonRoot[0] failed at path /spec/template/spec/securityContext/runAsNonRoot/. Rule autogen-validate-runAsNonRoot[1] failed at path /spec/template/spec/securityContext/runAsUser/. Rule autogen-validate-runAsNonRoot[2] failed at path /spec/template/spec/containers/0/securityContext/. Rule autogen-validate-runAsNonRoot[3] failed at path /spec/template/spec/containers/0/securityContext/.
  Policy:         disallow-root-user
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-server
    Namespace:    argocd
    UID:          085a25b7-a84c-42bf-9098-0e27acb38486
  Rule:           autogen-validate-runAsNonRoot
  Scored:         true
  Status:         fail
--
  Message:        validation error: Privileged mode is not allowed. Set allowPrivilegeEscalation to false. Rule autogen-validate-allowPrivilegeEscalation failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         disallow-privileged
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-redis
    Namespace:    argocd
    UID:          3920ea11-3a4f-4e8f-9272-fc0165fc894e
  Rule:           autogen-validate-allowPrivilegeEscalation
  Scored:         true
  Status:         fail
--
  Message:        validation error: Running as root is not allowed. Set runAsNonRoot to true, or use runAsUser. Rule autogen-validate-runAsNonRoot[0] failed at path /spec/template/spec/securityContext/runAsNonRoot/. Rule autogen-validate-runAsNonRoot[1] failed at path /spec/template/spec/securityContext/runAsUser/. Rule autogen-validate-runAsNonRoot[2] failed at path /spec/template/spec/containers/0/securityContext/. Rule autogen-validate-runAsNonRoot[3] failed at path /spec/template/spec/containers/0/securityContext/.
  Policy:         disallow-root-user
  Resources:
    API Version:  apps/v1
    Kind:         StatefulSet
    Name:         argocd-application-controller
    Namespace:    argocd
    UID:          883b6d14-ea19-4f31-b0b9-c89efed157e6
  Rule:           autogen-validate-runAsNonRoot
  Scored:         true
  Status:         fail
--
  Message:        validation error: Root filesystem must be read-only. Rule autogen-validate-readOnlyRootFilesystem failed at path /spec/template/spec/containers/0/securityContext/
  Policy:         require-ro-rootfs
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-redis
    Namespace:    argocd
    UID:          3920ea11-3a4f-4e8f-9272-fc0165fc894e
  Rule:           autogen-validate-readOnlyRootFilesystem
  Scored:         true
  Status:         fail
--
  Message:        validation error: Running as root is not allowed. Set runAsNonRoot to true, or use runAsUser. Rule autogen-validate-runAsNonRoot[0] failed at path /spec/template/spec/securityContext/runAsNonRoot/. Rule autogen-validate-runAsNonRoot[1] failed at path /spec/template/spec/securityContext/runAsUser/. Rule autogen-validate-runAsNonRoot[2] failed at path /spec/template/spec/containers/0/securityContext/. Rule autogen-validate-runAsNonRoot[3] failed at path /spec/template/spec/containers/0/securityContext/.
  Policy:         disallow-root-user
  Resources:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         argocd-dex-server
    Namespace:    argocd
    UID:          b246153b-2852-46e6-bd7d-6b076bbc5734
  Rule:           autogen-validate-runAsNonRoot
  Scored:         true
  Status:         fail

QoS ClassとPriorityClass

QoS ClassとPriorityClassの違いがわからなくなったので、調べたメモ。

QoS ClassとPriorityClassは2つの独立した無関係の機能。

QoS Classはノードのリソースが不足した際にEviction ManagerがPodをEvictionする際に使われる。PriorityClassはSchedulerがスケジューリングのために既存のPodを削除(Preemption)する際に使われる。

参考リンク

EKSのManagedNodeGroupでSSMエージェントをインストールする

ワーカーノードのロールにAmazonSSMManagedInstanceCoreを付与する。

マネージド型ノードグループの起動テンプレートでユーザーデータに以下を書く。

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="

--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"

#!/bin/bash

yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent

--==MYBOUNDARY==--

eksctl

eksctlでクラスターの作成時にSSMエージェントを入れるには以下のように書けばよい。

managedNodeGroups:
  - name: managed-ng-1
    minSize: 2
    maxSize: 2
    desiredCapacity: 2
    ssh:
      allow: true
      publicKeyName: default
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
    preBootstrapCommands:
      - yum install -y amazon-ssm-agent
      - systemctl enable amazon-ssm-agent
      - systemctl start amazon-ssm-agent

と思ったが、以下のオプションがあったので、これだけでよさそう。非マネージドでも同様。

managedNodeGroups:
  - name: managed-ng-1
    minSize: 2
    maxSize: 2
    desiredCapacity: 2
    ssh:
      allow: true
      publicKeyName: default
      enableSsm: true

CiliumのGetting StartedをKindでやってみた

Ciliumの理解が進まないので、Getting StartedをKindでやってみたメモ。

クラスターの作成

kind-config.yamlを作成する。

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
networking:
  disableDefaultCNI: true

クラスターを作成する。

$ kind create cluster --config=kind-config.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.20.2) 🖼
 ✓ Preparing nodes 📦 📦 📦 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing StorageClass 💾
 ✓ Joining worker nodes 🚜
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a nice day! 👋

ノードを確認する。

$ k get node
NAME                 STATUS     ROLES                  AGE     VERSION
kind-control-plane   NotReady   control-plane,master   5m4s    v1.20.2
kind-worker          NotReady   <none>                 4m24s   v1.20.2
kind-worker2         NotReady   <none>                 4m25s   v1.20.2
kind-worker3         NotReady   <none>                 4m24s   v1.20.2

Ciliumのインストール

Ciliumのtarをダウンロードして展開する。

curl -LO https://github.com/cilium/cilium/archive/master.tar.gz
tar xzf master.tar.gz
cd cilium-master/install/kubernetes

CiliumのイメージをpullしてKindクラスターにロードする。

docker pull cilium/cilium:latest
kind load docker-image cilium/cilium:latest

ローカルディレクトリからHelmでインストールする。

helm install cilium ./cilium \
   --namespace kube-system \
   --set nodeinit.enabled=true \
   --set kubeProxyReplacement=partial \
   --set hostServices.enabled=false \
   --set externalIPs.enabled=true \
   --set nodePort.enabled=true \
   --set hostPort.enabled=true \
   --set bpf.masquerade=false \
   --set image.pullPolicy=IfNotPresent \
   --set ipam.mode=kubernetes

Podが全て起動したことを確認する。

$ kubectl -n kube-system get pod
NAME                                         READY   STATUS    RESTARTS   AGE
cilium-25tq6                                 1/1     Running   0          3m12s
cilium-4csrs                                 1/1     Running   0          3m13s
cilium-8lsmq                                 1/1     Running   0          3m12s
cilium-node-init-7p5b7                       1/1     Running   1          3m13s
cilium-node-init-hzwf7                       1/1     Running   1          3m12s
cilium-node-init-rqtct                       1/1     Running   1          3m13s
cilium-node-init-zkx9b                       1/1     Running   1          3m13s
cilium-operator-748d8bf4f7-7gtpb             1/1     Running   0          3m12s
cilium-operator-748d8bf4f7-b6csm             1/1     Running   0          3m12s
cilium-xkqdz                                 1/1     Running   0          3m12s
coredns-74ff55c5b-7prvm                      1/1     Running   0          10m
coredns-74ff55c5b-grsh7                      1/1     Running   0          10m
etcd-kind-control-plane                      1/1     Running   0          10m
kube-apiserver-kind-control-plane            1/1     Running   0          10m
kube-controller-manager-kind-control-plane   1/1     Running   0          10m
kube-proxy-5t94q                             1/1     Running   0          9m54s
kube-proxy-mf2sm                             1/1     Running   0          10m
kube-proxy-t6vct                             1/1     Running   0          9m55s
kube-proxy-zttsg                             1/1     Running   0          9m54s
kube-scheduler-kind-control-plane            1/1     Running   0          10m

Identity-Aware and HTTP-Aware Policy Enforcement

デモアプリのインストール

デモアプリをインストールする。

$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/http-sw-app.yaml
service/deathstar created
deployment.apps/deathstar created
pod/tiefighter created
pod/xwing created

PodとServiceを確認する。

$ k get pod,svc -o wide --show-labels
NAME                            READY   STATUS    RESTARTS   AGE     IP             NODE           NOMINATED NODE   READINESS GATES   LABELS
pod/deathstar-c74d84667-cpd99   1/1     Running   0          7m41s   10.244.3.208   kind-worker    <none>           <none>            class=deathstar,org=empire,pod-template-hash=c74d84667
pod/deathstar-c74d84667-tfsn2   1/1     Running   0          7m41s   10.244.3.31    kind-worker    <none>           <none>            class=deathstar,org=empire,pod-template-hash=c74d84667
pod/tiefighter                  1/1     Running   0          7m41s   10.244.1.246   kind-worker2   <none>           <none>            class=tiefighter,org=empire
pod/xwing                       1/1     Running   0          7m41s   10.244.2.124   kind-worker3   <none>           <none>            class=xwing,org=alliance

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR                     LABELS
service/deathstar    ClusterIP   10.96.51.254   <none>        80/TCP    7m43s   class=deathstar,org=empire   <none>
service/kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   21m     <none>                       component=apiserver,provider=kubernetes

CiliumのPodを確認する。

$ kubectl -n kube-system get pod -l k8s-app=cilium -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP           NODE                 NOMINATED NODE   READINESS GATES
cilium-25tq6   1/1     Running   0          12m   172.18.0.4   kind-worker2         <none>           <none>
cilium-4csrs   1/1     Running   0          12m   172.18.0.2   kind-control-plane   <none>           <none>
cilium-8lsmq   1/1     Running   0          12m   172.18.0.5   kind-worker3         <none>           <none>
cilium-xkqdz   1/1     Running   0          12m   172.18.0.3   kind-worker          <none>           <none>

各PodはCiliumのエンドポイントとなる。cilium endpoint listコマンドでエンドポイントの状態が確認できる。

$ kubectl -n kube-system exec cilium-25tq6 -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                       IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
402        Disabled           Disabled          4          reserved:health                                          10.244.1.160   ready
609        Disabled           Disabled          16260      k8s:class=tiefighter                                     10.244.1.246   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
1776       Disabled           Disabled          1          reserved:host                                                           ready
$ kubectl -n kube-system exec cilium-4csrs -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                 IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
2089       Disabled           Disabled          1          k8s:node-role.kubernetes.io/control-plane                         ready
                                                           k8s:node-role.kubernetes.io/master
                                                           reserved:host
3500       Disabled           Disabled          4          reserved:health                                    10.244.0.151   ready
$ kubectl -n kube-system exec cilium-8lsmq -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                                                      IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
94         Disabled           Disabled          6547       k8s:class=xwing                                                                         10.244.2.124   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=alliance
291        Disabled           Disabled          31776      k8s:io.cilium.k8s.policy.cluster=default                                                10.244.2.193   ready
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
1533       Disabled           Disabled          8324       k8s:app=local-path-provisioner                                                          10.244.2.176   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=local-path-provisioner-service-account
                                                           k8s:io.kubernetes.pod.namespace=local-path-storage
1639       Disabled           Disabled          31776      k8s:io.cilium.k8s.policy.cluster=default                                                10.244.2.70    ready
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
1825       Disabled           Disabled          4          reserved:health                                                                         10.244.2.177   ready
3168       Disabled           Disabled          1          reserved:host                                                                                          ready
$ kubectl -n kube-system exec cilium-xkqdz -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                       IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
302        Disabled           Disabled          48677      k8s:class=deathstar                                      10.244.3.31    ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
587        Disabled           Disabled          1          reserved:host                                                           ready
1699       Disabled           Disabled          4          reserved:health                                          10.244.3.1     ready
2742       Disabled           Disabled          48677      k8s:class=deathstar                                      10.244.3.208   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire

まだNetwork Policyが定義されていないので、ENFORCEMENTはDisabledとなっている。

現在の状態を確認する。xwingtiefighterdeathstar Service(のPod)にアクセス可能になっている。

$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

L3/L4ポリシーの適用

org=empireのラベルを持つPodからのみdeathstar Service(のPod)にアクセスできるようにする。

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L3-L4 policy to restrict deathstar access to empire ships only"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
$ k apply -f rule1-l4.yaml
ciliumnetworkpolicy.cilium.io/rule1 created

確認する。

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
^C

deathstarのPodについて、IngressのENFORCEMENTがEnabledになっていることを確認する。

$ kubectl -n kube-system exec cilium-25tq6 -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                       IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
402        Disabled           Disabled          4          reserved:health                                          10.244.1.160   ready
609        Disabled           Disabled          16260      k8s:class=tiefighter                                     10.244.1.246   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
1776       Disabled           Disabled          1          reserved:host                                                           ready
$ kubectl -n kube-system exec cilium-4csrs -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                 IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
2089       Disabled           Disabled          1          k8s:node-role.kubernetes.io/control-plane                         ready
                                                           k8s:node-role.kubernetes.io/master
                                                           reserved:host
3500       Disabled           Disabled          4          reserved:health                                    10.244.0.151   ready
$ kubectl -n kube-system exec cilium-8lsmq -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                                                      IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
94         Disabled           Disabled          6547       k8s:class=xwing                                                                         10.244.2.124   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=alliance
291        Disabled           Disabled          31776      k8s:io.cilium.k8s.policy.cluster=default                                                10.244.2.193   ready
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
1533       Disabled           Disabled          8324       k8s:app=local-path-provisioner                                                          10.244.2.176   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=local-path-provisioner-service-account
                                                           k8s:io.kubernetes.pod.namespace=local-path-storage
1639       Disabled           Disabled          31776      k8s:io.cilium.k8s.policy.cluster=default                                                10.244.2.70    ready
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
1825       Disabled           Disabled          4          reserved:health                                                                         10.244.2.177   ready
3168       Disabled           Disabled          1          reserved:host                                                                                          ready
$ kubectl -n kube-system exec cilium-xkqdz -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                       IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
302        Enabled            Disabled          48677      k8s:class=deathstar                                      10.244.3.31    ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
587        Disabled           Disabled          1          reserved:host                                                           ready
1699       Disabled           Disabled          4          reserved:health                                          10.244.3.1     ready
2742       Enabled            Disabled          48677      k8s:class=deathstar                                      10.244.3.208   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire

L7 HTTPポリシーの適用

deathstar Service(のPod)が危険なAPIを持っているとする。アクセスして確認する。

$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Panic: deathstar exploded

goroutine 1 [running]:
main.HandleGarbage(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
        /code/src/github.com/empire/deathstar/
        temp/main.go:9 +0x64
main.main()
        /code/src/github.com/empire/deathstar/
        temp/main.go:5 +0x85

/v1/request-landingへのPOSTは許可するが、/v1/exhaust-portへのPUTは禁止する。

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "POST"
          path: "/v1/request-landing"
$ k apply -f rule1-l7.yaml
ciliumnetworkpolicy.cilium.io/rule1 configured

確認する。

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Access denied

cilium policy getでポリシーを確認できる。

$ kubectl -n kube-system exec cilium-xkqdz -- cilium policy get
[
  {
    "endpointSelector": {
      "matchLabels": {
        "any:class": "deathstar",
        "any:org": "empire",
        "k8s:io.kubernetes.pod.namespace": "default"
      }
    },
    "ingress": [
      {
        "fromEndpoints": [
          {
            "matchLabels": {
              "any:org": "empire",
              "k8s:io.kubernetes.pod.namespace": "default"
            }
          }
        ],
        "toPorts": [
          {
            "ports": [
              {
                "port": "80",
                "protocol": "TCP"
              }
            ],
            "rules": {
              "http": [
                {
                  "path": "/v1/request-landing",
                  "method": "POST"
                }
              ]
            }
          }
        ]
      }
    ],
    "labels": [
      {
        "key": "io.cilium.k8s.policy.derived-from",
        "value": "CiliumNetworkPolicy",
        "source": "k8s"
      },
      {
        "key": "io.cilium.k8s.policy.name",
        "value": "rule1",
        "source": "k8s"
      },
      {
        "key": "io.cilium.k8s.policy.namespace",
        "value": "default",
        "source": "k8s"
      },
      {
        "key": "io.cilium.k8s.policy.uid",
        "value": "ba8b809f-fc18-4296-8e1d-596b38921b04",
        "source": "k8s"
      }
    ],
    "description": "L7 policy to restrict access to specific HTTP call"
  }
]
Revision: 4

``でトラフィックをモニターできる。ただし、このノードが対象。

$ kubectl -n kube-system exec -it cilium-xkqdz -- cilium monitor -v --type l7
Listening for events on 4 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
<- Request http from 0 ([k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire]) to 302 ([k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.cilium.k8s.policy.cluster=default]), identity 16260->48677, verdict Denied PUT http://deathstar.default.svc.cluster.local/v1/exhaust-port => 403
<- Request http from 0 ([k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire]) to 302 ([k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.cilium.k8s.policy.cluster=default k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default]), identity 16260->48677, verdict Denied PUT http://deathstar.default.svc.cluster.local/v1/exhaust-port => 403
<- Request http from 0 ([k8s:org=empire k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default]) to 302 ([k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.cilium.k8s.policy.cluster=default k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default]), identity 16260->48677, verdict Denied PUT http://deathstar.default.svc.cluster.local/v1/exhaust-port => 403
^C
Received an interrupt, disconnecting from monitor...

クリーンアップ

$ kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/http-sw-app.yaml
service "deathstar" deleted
deployment.apps "deathstar" deleted
pod "tiefighter" deleted
pod "xwing" deleted
$ kubectl delete cnp rule1
ciliumnetworkpolicy.cilium.io "rule1" deleted

Locking down external access with DNS-based policies

デモアプリのインストール

デモアプリをインストールする。先ほどとは異なるアプリ。

$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.9/examples/kubernetes-dns/dns-sw-app.yaml
pod/mediabot created

Podを確認する。

$ k get pod -o wide --show-labels
NAME       READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES   LABELS
mediabot   1/1     Running   0          58s   10.244.1.211   kind-worker2   <none>           <none>            class=mediabot,org=empire

DNSポリシーの適用

api.twitter.comへのEgressを許可するポリシーを作成する。

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "api.twitter.com"
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"
$ k apply -f fqdn1.yaml
ciliumnetworkpolicy.cilium.io/fqdn created

確認する。

$ kubectl exec -it mediabot -- curl -sL https://api.twitter.com
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
...
</html>
$ kubectl exec -it mediabot -- curl -sL https://help.twitter.com
^Ccommand terminated with exit code 130

パターンを使ったDNSポリシー

matchNameは完全一致なので、代わりにmatchPatternを使ってみる。

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "api.twitter.com"
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"
$ k apply -f fqdn2.yaml
ciliumnetworkpolicy.cilium.io/fqdn configured

確認する。twitter.comは許可されないことに注意。

$ kubectl exec -it mediabot -- curl -sL https://help.twitter.com
<!doctype html>
...
$ kubectl exec -it mediabot -- curl -sL https://about.twitter.com
<!DOCTYPE html>
...
$ kubectl exec -it mediabot -- curl -sL https://twitter.com
^Ccommand terminated with exit code 130

DNS, Port, L7ルールの組み合わせ

アクセスできるポートを443に限定する。

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchPattern: "*.twitter.com"
    toPorts:
    - ports:
      - port: "443"
        protocol: TCP
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"
$ k apply -f fqdn3.yaml
ciliumnetworkpolicy.cilium.io/fqdn configured

確認する。

$ kubectl exec -it mediabot -- curl https://help.twitter.com
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
...
$ kubectl exec -it mediabot -- curl http://help.twitter.com
^Ccommand terminated with exit code 130

クリーンアップ

$ kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/kubernetes-dns/dns-sw-app.yaml
pod "mediabot" deleted
$ kubectl delete cnp fqdn
ciliumnetworkpolicy.cilium.io "fqdn" deleted