kakakakakku blog

Weekly Tech Blog: Keep on Learning!

CKS (Certified Kubernetes Security Specialist) に合格した : 勉強方法をまとめる

Kubernetes の資格 CKS (Certified Kubernetes Security Specialist) に合格した 🎉 やったー!Kubernetes 関連は3個目!

今回の記事では「勉強方法」をまとめようと思う.

www.cncf.io

時系列で書くと,2021年2-3月に CKAD (Certified Kubernetes Application Developer)CKA (Certified Kubernetes Administrator) に合格していて,詳しくは以下の記事にまとめてある.せっかくなら CKS まで連続で受験しようと思っていたけど,仕事に忙殺されて時間が捻出できなくなったのと,当時はまだ CKS を日本語で受験することができず,結果的に受験するタイミングを逃してしまった.なお,2021年7月に日本語化されている!

そこで今月(2022年4月)にやるぞ!と決めて,3週間ほど平日夜にコツコツ勉強を続けた.週末はもっと時間を捻出したと思う.また個人用だけど毎日学んだメモをブログとは別に TIL (Today I Learned) としてまとめたのも効果的だったと思う.うまく学習サイクルを回しながら自信を持って合格できた!CKADCKA のときにも感じたけど,試験中は本当に楽しくハッカソンに参加している気持ちになる.

kakakakakku.hatenablog.com

前提と結果 🐳

CKAD / CKA の記事にも書いたけど,僕は技術講師として研修で Kubernetes と Amazon EKS を教える機会があったり,Kubernetes 上のアプリケーション開発の経験も少しある.逆に言えば,プロダクションワークロードでの大規模な運用経験はなかったりする.またセキュリティ関連の知識も少なく,CKS で出題される技術トピックの多くは未経験だった.よって,あくまで個人的には「CKAD < CKA < CKS」の順番で難しく感じたし,最初は CKS の出題範囲の広さにも圧倒された.

結果は以下の通りだった.1問(6点)は確実に間違えたけど,他にもう1問どこを間違えたんだろう...!正直2時間では時間ギリギリで見直しは5問しかできなかった.そして,今回 CKS を受験して(勉強過程も含めて)1番良かったのは「"知らないこと" に気付けた」ことだった!

受験日 試験名 得点 結果 バージョン
2022/04/27 CKS (Certified Kubernetes Security Specialist) 88 点 合格 Kubernetes v1.23

出題範囲 🐳

カリキュラム(出題範囲)は GitHub に PDF として公開されている.

Domain Weight
Cluster Setup 10 %
Cluster Hardening 15 %
System Hardening 15 %
Minimize Microservice Vulnerabilities 20 %
Supply Chain Security 20 %
Monitoring, Logging, and Runtime Security 20 %

github.com

とは言え,出題範囲だけだと勉強するべき具体的な技術トピックを把握しにくくもある.以下の GitHub リポジトリ walidshaari/Certified-Kubernetes-Security-Specialistechoboomer/k8s-cks-notes はとてもよくまとまっている.おすすめ!

github.com

github.com

CKS 勉強方法 🐳

今回は大きく「3種類」の勉強方法を組み合わせた.詳しく紹介する!

  • [A] 座学("知らないこと" に気付くためにインプットする)
  • [B] 検証 & ブログ(気になったら検証をしてブログにアウトプットする)
  • [C] 実践演習(演習環境を使ってとにかく実践する)

[A] 座学("知らないこと" に気付くためにインプットする)

A-1. Udemy : Kubernetes CKS 2022 Complete Course

まず,有名な Udemy コース「Kubernetes CKS 2022 Complete Course」を1.5倍速でザッと視聴した(正確には購入したときは 2021 だった).e-Learning は本当にわかりやすくて,デモも多くて,学習項目ごとにコツコツ学べた.とにかく「"知らないこと" に気付く」ために1番有効だったと思う.2021年に購入したときには Exam Simulator も同梱されていたけど,現在は同梱されなくなっている(後述する).

www.udemy.com

講師をしている Kim のブログに Udemy Discount Code も載っているため,購入する前に確認すると良いかと!

wuestkamp.medium.com

A-2. 書籍 : Docker/Kubernetes 開発・運用のためのセキュリティ実践ガイド

並行して書籍「Docker/Kubernetes 開発・運用のためのセキュリティ実践ガイド」も読んだ.CKS 対策本ではないけど,学べる技術トピックは近く,何よりも日本語で読めるのは素晴らしかった.今度書評記事も書く予定!

英語の書籍だと「Learn Kubernetes Security」CKS 対策として使えるという話を聞いたこともある.今回は読まなかった.

[B] 検証 & ブログ(気になったら検証をしてブログにアウトプットする)

B-1. ブログ

座学で大量にインプットをして,気になったり,理解があやふやに感じたところはどんどん検証をしてブログにまとめた.個人的にはブログにまとめることで,客観的に学び直せて理解が深まる感覚がある.また単純にブログネタも増やせたのも良かった.実は2021年にも少し CKS を勉強していた時期があり,以下に CKS に関連する記事の一覧をまとめておく.

[C] 実践演習(演習環境を使ってとにかく実践する)

C-1. Killercoda

演習環境「Killercoda」は本当に価値があった.Udemy コース「Kubernetes CKS 2022 Complete Course」と繋がっていて,e-Learning を観た後に Killercoda で演習をする流れになっている.とすると Udemy 購入者限定のように思うけど,そんなことはなく,登録すれば無料で何度でも使える.絶対にやるべき!現時点で以下の「40種類」もある!全て完璧に正解できるようにしておくと良いと思う.

  • Playground
  • Vim Setup
  • Apiserver Crash
  • Apiserver Misconfigured
  • Apiserver NodeRestriction
  • AppArmor
  • Auditing Enable Audit Logging
  • CIS Benchmarks fix Controlplane
  • CertificateSigningRequests sign manually
  • CertificateSigningRequests sign via API
  • Container Hardening
  • Container Image Footprint User
  • Container Namespaces Docker
  • Container Namespaces Podman
  • Falco Change Rule
  • Image Use Digest
  • Image Vulnerability Scanning Trivy
  • ImagePolicyWebhook Setup
  • Immutability Readonly Filesystem
  • Ingress Create
  • Ingress Secure
  • NetworkPolicy Create Default Deny
  • NetworkPolicy Metadata Protection
  • NetworkPolicy Namespace Selector
  • Privilege Escalation Containers
  • Privileged Containers
  • RBAC ServiceAccount Permissions
  • RBAC User Permissions
  • Sandbox gVisor
  • Secret Access in Pods
  • Secret ETCD Encryption
  • Secret Read and Decode
  • Secret ServiceAccount Pod
  • ServiceAccount Token Mounting
  • Static Manual Analysis Docker
  • Static Manual Analysis K8s
  • Syscall Activity Strace
  • System Hardening Close Open Ports
  • System Hardening Manage Packages
  • Verify Platform Binaries

killercoda.com

C-2. A Cloud Guru : Certified Kubernetes Security Specialist (CKS)

A Cloud Guru「Certified Kubernetes Security Specialist (CKS)」は e-Learning を使わず,演習環境「HANDS-ON LAB」を中心に使った.特に最後にある Practice Exam には「計12種類」の演習を試せて RBAC / Pod Security Policies / CIS Kubernetes Benchmarks / Network Policy / ImagePolicyWebhook / Trivy / AppArmor / Falco などを試せる.手順や回答例が充実してるのはメリットだけど「答え合わせ」の機能はなく,受験直前だと使いにくく感じた.

acloudguru.com

C-3. Kodekloud : CKS – Challenges

有料コースの Kodekloud から2021年4月に新しくリリースされた「無料の」演習環境として「CKS – Challenges」がある.タイミング良く受験前にリリースされたため試した.現時点だと Challenges 1-3 まで公開されていて,Challenges 4-5 は公開されていなかった.難易度は高く複雑な演習に取り組めて良かった.個人的には UI が使いにくく,問題を確認するために「コンポーネント」「矢印」をクリックする必要があると理解するまでに時間がかかった.

kodekloud.com

C-4. Kubernetes Exam Simulator

最後は Kubernetes Exam Simulator で,もともとは Udemy コースに同梱されていて,Killer Shell (Killer.sh) という名前だった.現在は資格自体に「公式に」同梱されているため,試験を購入すれば模擬試験として最大2回まで受験できる.試験日を決める前に受験できるので,ある程度準備できたと感じたら受験してみるのが良いと思う.

killer.sh

Kubernetes Exam Simulator はドキュメントにも書かれている通り,実際の試験よりも難しく作られている.実際に「2時間」を計測して受験したら「41 / 75(55点ぐらい)」でガッカリした(´・ω・`) その後に答えを見ずにドキュメントを使って続けたら「53 / 75(70点ぐらい)」まで上がった.残りは難しく,答えを見て理解を深めた.また Kubernetes Exam Simulator「36時間」の制限時間内なら何度でも環境をリセットできるため,もう一度「2時間」で再挑戦したら「72 / 75(96点ぐらい)」まで取れるようになった.

唯一残念だったのは Kubernetes Exam Simulator は日本語化されてなく,受験後のアンケートにフィードバックを入れておいた.

training.linuxfoundation.org

まとめ

受験しようと思って後回しにしていた CKS (Certified Kubernetes Security Specialist) に合格できて良かった 🎉

本記事が何かの参考になれば✌️

Kubernetes と Falco を組み合わせて脅威検出に入門する

Kubernetes でコンテナワークロードの脅威検出として使える「Falco」に入門する.CNCF (Cloud Native Computing Foundation) で Incubating に位置しているプロジェクトで,今回は実際に Falco を使って Pod に対する操作(振る舞い)を検出したり,独自ルールを作る.Falco はコンテナに限らず,ホスト側のプロセスも監視できる.

falco.org

インストールをする 🦅

以下の検証環境を使う.ドキュメントに書いてある Ubuntu の手順をそのまま使って,ワーカーノードに簡単に Falco v0.31.1 をインストールできた.また Helm を使って Kubernetes 上に Falco を DaemonSet としてインストールをする選択肢もある.Falco は日本語ドキュメントがとても充実しててイイ✨

  • Kubernetes v1.23.0
  • ワーカーノード : Ubuntu 20.04.3 LTS
node01 $ curl -s https://falco.org/repo/falcosecurity-3672BA8F.asc | apt-key add -
node01 $ echo "deb https://download.falco.org/packages/deb stable main" | tee -a /etc/apt/sources.list.d/falcosecurity.list
node01 $ apt-get update -y
node01 $ apt-get -y install linux-headers-$(uname -r)
node01 $ apt-get install -y falco

node01 $ systemctl start falco
node01 $ systemctl status falco

falco.org

/etc/falco を確認すると falco.yaml(設定ファイル)や falco_rules.yaml(ルール)や falco_rules.local.yaml(独自ルール)など Falco 関連のファイルがあった.

node01 $ ls -l /etc/falco
total 200
-rw-r--r-- 1 root root  12400 Mar  9 17:20 aws_cloudtrail_rules.yaml
-rw-r--r-- 1 root root  11384 Mar  9 17:20 falco.yaml
-rw-r--r-- 1 root root   1136 Mar  9 17:20 falco_rules.local.yaml
-rw-r--r-- 1 root root 133017 Mar  9 17:20 falco_rules.yaml
-rw-r--r-- 1 root root  27287 Mar  9 17:20 k8s_audit_rules.yaml
drwxr-xr-x 2 root root   4096 Apr 16 12:14 rules.available
drwxr-xr-x 2 root root   4096 Mar  9 17:42 rules.d

動作確認をする 🦅

まず,動作確認をする.今回は Pod(コンテナ)に対してシェル接続が発生したことを検出する.運用上必要になる場面以外では不正な操作の可能性があり,検出できるのは便利だと思う.さっそく kubectl run コマンドを使って nginx Pod を起動して,kubectl exec コマンドを使って Pod にシェル接続をする.

controlplane $ kubectl run nginx --image nginx:1.21
pod/nginx created

controlplane $ kubectl exec -it nginx -- sh
# 

syslog を確認すると A shell was spawned in a container with an attached terminal という振る舞いが検出されている.container_idimage など,詳細情報も確認できる.

  • container_id=aaee21e42016
  • image=docker.io/library/nginx
node01 $ grep falco /var/log/syslog
(中略)

Apr 16 14:00:00 node01 falco: 14:00:00.000000000: Notice A shell was spawned in a container with an attached terminal (user=kc-internal user_loginuid=-1 nginx (id=aaee21e42016) shell=sh parent=runc cmdline=sh terminal=34817 container_id=aaee21e42016 image=docker.io/library/nginx)

設定ファイルを確認する 🦅

まず /etc/falco/falco.yaml を確認すると Falco で検出した結果を syslog に出力する設定になっている.

# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: true
log_syslog: true

次に /etc/falco/falco_rules.yaml を確認すると,A shell was spawned in a container からはじまる今回検出された振る舞いがルールとして設定されている.

- rule: Terminal shell in container
  desc: A shell was used as the entrypoint/exec point into a container with an attached terminal.
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
    and container_entrypoint
    and not user_expected_terminal_shell_in_container_conditions
  output: >
    A shell was spawned in a container with an attached terminal (user=%user.name user_loginuid=%user.loginuid %container.info
    shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository)
  priority: NOTICE
  tags: [container, shell, mitre_execution]

ルールを一部変更する 🦅

検証のため /etc/falco/falco_rules.yaml を直接変更して,output として tag=%container.image.tag(コンテナイメージタグ)と pod_name=%k8s.pod.name(Pod 名)を追加する.以下のドキュメントを読むと,Falco は他にも多くのフィールドをサポートしている.

falco.org

- rule: Terminal shell in container
  desc: A shell was used as the entrypoint/exec point into a container with an attached terminal.
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
    and container_entrypoint
    and not user_expected_terminal_shell_in_container_conditions
  output: >
    A shell was spawned in a container with an attached terminal (user=%user.name user_loginuid=%user.loginuid %container.info
    shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository tag=%container.image.tag pod_name=%k8s.pod.name)
  priority: NOTICE
  tags: [container, shell, mitre_execution]

Falco を再起動して,もう一度 kubectl exec コマンドを使って Pod にシェル接続をする.

node01 $ systemctl restart falco

controlplane $ kubectl exec -it nginx -- sh
# 

すると期待した通りに tagpod_name も確認できるようになった.

  • tag=1.21
  • pod_name=nginx
node01 $ grep falco /var/log/syslog
(中略)

Apr 16 14:00:00 node01 falco: 14:00:00.000000000: Notice A shell was spawned in a container with an attached terminal (user=kc-internal user_loginuid=-1 nginx (id=aaee21e42016) shell=sh parent=runc cmdline=sh terminal=34817 container_id=aaee21e42016 image=docker.io/library/nginx tag=1.21 pod_name=nginx)

デフォルトマクロ 🦅

Falco ルールの condition には条件が書いてある.今回だと spawned_processcontainer など,簡易的な表現になっていて,これは Falco の「デフォルトマクロ」と言う.実際に /etc/falco/falco_rules.yaml を確認すると spawned_process(新規プロセスが生成された)や container(コンテナオブジェクトである)というよく使う条件がプリセットされている.

- macro: spawned_process
  condition: evt.type in (execve, execveat) and evt.dir=<

- macro: container
  condition: (container.id != host)

独自ルールを作る 🦅

検証のため,簡単な独自ルールを作る.今回は Pod 上で mkdir コマンドを使って「新規ディレクトリを作ったこと」を検出する.以下のルールを /etc/falco/falco_rules.local.yaml に追加した.

- rule: mkdir_in_container
  desc: mkdir_in_container
  condition: container and mkdir
  output: mkdir_in_container
  priority: NOTICE
  tags: [container, shell]

Falco を再起動して,もう一度 kubectl exec コマンドを使って Pod にシェル接続をしてから mkdir コマンドを実行した.

node01 $ systemctl restart falco

controlplane $ kubectl exec -it nginx -- sh
# mkdir falco
# 

すると,簡単ではあるけど,期待した通りに mkdir コマンドの実行を検出できた.

node01 $ grep falco /var/log/syslog
(中略)

Apr 16 14:00:00 node01 falco: 14:00:00.000000000: Notice mkdir_in_container

まとめ

Kubernetes でコンテナワークロードの脅威検出として使える「Falco」を試した.今回はお手軽に Pod への「シェル接続」mkdir コマンドの実行」を検出した.他にもyum などのパッケージマネージャの実行」「ホスト側に危険性のあるディレクトリのマウント」などを検出できたりもする.「Docker/Kubernetes 開発・運用のためのセキュリティ実践ガイド」 にも Falco の紹介が載っていて参考になった.

Flux v2 で Image Ops を実現する「自動イメージ更新機能」を試した

GitOps ソフトウェアの Flux v1 にはイメージレジストリを監視してイメージタグを自動的に最新化する「Automate image updates(自動イメージ更新)機能」が組み込まれている.別名で「Image Ops」と言ったりもする.具体的には Deployment YAML などの annotationsfluxcd.io/automated: "true" を設定すると有効化できていた.

現在使われている Flux v2 にも「Automate image updates(自動イメージ更新)機能」は組み込まれている.しかし Flux v1 と互換性はなく,完全に異なる仕組みになっている.今回は以下のドキュメントを参考にしながら Flux v2「Automate image updates(自動イメージ更新)機能」を試していく!

fluxcd.io

fluxcd.io

環境

今回は kubeadm を使って構築した Kubernetes v1.23.1 クラスタを使う.

  • Kubernetes v1.23.1

全体構成

全体構成をまとめておく.まず,Flux v2「Automate image updates(自動イメージ更新)機能」CRD (Custom Resource Definitions) を使う.以下の2種類のコントローラーでイメージレジストリを監視したり,GitHub リポジトリを更新したりする.そして ImageRepository ImageUpdateAutomation などの CRD も組み合わせて使う.公式ドキュメントから引用した図がわかりやすい!

  • Image Reflector controller(イメージレジストリを監視する)
  • Image Automation controller(Git リポジトリを更新する)

Image reflector and automation controllers より引用

Flux CLI をセットアップする

準備として Flux CLI をセットアップしておく.さらに今回使う代表的なサブコマンドも以下にまとめておく.

$ curl -s https://fluxcd.io/install.sh | sudo bash
$ flux --version
flux version 0.28.5

fluxcd.io

Flux v2 をセットアップする

最初は Kubernetes クラスタに Flux v2 をセットアップする.flux bootstrap github コマンドを使うと Flux v2 のセットアップと Flux の設定を管理する GitHub リポジトリを自動的に作れる.今回はドキュメントの通りに GitHub リポジトリ flux-image-updates を使うことにする.セットアップをするときは既に紹介した「Automate image updates(自動イメージ更新)機能」のコントローラもセットアップする必要があるため --components-extra=image-reflector-controller,image-automation-controller オプションも追加する.

  • image-reflector-controller(イメージレジストリを監視する)
  • image-automation-controller(Git リポジトリを更新する)
$ export GITHUB_USER=kakakakakku

$ flux bootstrap github \
  --components-extra=image-reflector-controller,image-automation-controller \
  --owner=${GITHUB_USER} \
  --repository=flux-image-updates \
  --branch=main \
  --path=clusters/my-cluster \
  --read-write-key \
  --personal

セットアップ後に確認すると flux-system Namespace にコントローラが起動されていた.

$ kubectl get pod image-reflector-controller-86db8b6f78-n62lf -n flux-system
NAME                                          READY   STATUS    RESTARTS   AGE
image-reflector-controller-86db8b6f78-n62lf   1/1     Running   0          10m

$ kubectl get pod image-automation-controller-77fd9657c6-7ft2m -n flux-system
NAME                                           READY   STATUS    RESTARTS   AGE
image-automation-controller-77fd9657c6-7ft2m   1/1     Running   0          10m

そして,自動的に作られた GitHub リポジトリ flux-image-updates は以下のようなディレクトリ構成になっている.Flux v2 は kustomize を使って構成されるため,設定ファイルとして gotk-components.yamlgotk-sync.yaml も追加されている.

.
└── clusters
    └── my-cluster
        └── flux-system
            ├── gotk-components.yaml # Flux v2 コンポーネント群 (Service, Deployment, CustomResourceDefinition など)
            ├── gotk-sync.yaml # Flux v2 同期コンポーネント群 (GitRepository, Kustomization)
            └── kustomization.yaml # Kustomize 設定 (Kustomization)

アプリケーションを追加する

さっそくアプリケーションを追加する.今回はサンプルとして stefanprodan/podinfo を使う.

github.com

作った GitHub リポジトリ flux-image-updates に Deployment YAML を ./clusters/my-cluster/podinfo-deployment.yaml として追加する.ポイントは Pod のイメージタグで spec.containers.imageGitHub Container Registryghcr.io/stefanprodan/podinfo:5.0.0 を指定している.5.0.0 を覚えておく!

$ git clone https://github.com/${GITHUB_USER}/flux-image-updates
$ cd flux-image-updates

$ cat <<EOF > ./clusters/my-cluster/podinfo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: podinfo
  namespace: default
spec:
  selector:
    matchLabels:
      app: podinfo
  template:
    metadata:
      labels:
        app: podinfo
    spec:
      containers:
      - name: podinfod
        image: ghcr.io/stefanprodan/podinfo:5.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 9898
          protocol: TCP
EOF

GitHub リポジトリに Deployment YAML を追加して少し待つ(もしくは flux reconcile kustomization コマンドを使って同期する)と Flux v2 によって Pod がデプロイされる.実際にイメージタグは 5.0.0 になっている.

$ git add -A && \
git commit -m "add podinfo deployment" && \
git push origin main

$ flux reconcile kustomization flux-system --with-source

$ kubectl get deployments.apps podinfo
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
podinfo   1/1     1            1           10s

$ kubectl get deployments.apps podinfo -o yaml | grep 'image:'
      - image: ghcr.io/stefanprodan/podinfo:5.0.0

ImageRepository オブジェクトを追加する

次に flux create image repository コマンドを使って ImageRepository オブジェクトを追加する.

$ flux create image repository podinfo \
--image=ghcr.io/stefanprodan/podinfo \
--interval=1m \
--export > ./clusters/my-cluster/podinfo-registry.yaml

./clusters/my-cluster/podinfo-registry.yaml は以下となる.ImageRepository オブジェクトは指定したイメージリポジトリを監視して「新しいタグ」があるかを確認する.

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  image: ghcr.io/stefanprodan/podinfo
  interval: 1m0s

ImagePolicy オブジェクトを追加する

さらに flux create image policy コマンドを使って ImagePolicy オブジェクトを追加する.

$ flux create image policy podinfo \
--image-ref=podinfo \
--select-semver=5.0.x \
--export > ./clusters/my-cluster/podinfo-policy.yaml

./clusters/my-cluster/podinfo-policy.yaml は以下となる.ImagePolicy オブジェクトは「どういうポリシーで新しいイメージタグを探すか」を指定する.今回は policy.semver.range: 5.0.x なので「セマンティックバージョニング」「パッチバージョンの最新」を探す.

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: podinfo
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: podinfo
  policy:
    semver:
      range: 5.0.x

GitHub Container Registry を確認すると 5.0.x の最新は 5.0.3 だった.

ちなみに「セマンティックバージョニング」 以外に <branch>-<sha1>-<timestamp> などのフォーマットを指定して,任意の「並べ替え可能な条件」もできる.詳しくはドキュメントに載っている.

fluxcd.io

ImageRepository と ImagePolicy を適用する

GitHub リポジトリに ImageRepository YAML と ImagePolicy YAML を追加して少し待つ(もしくは flux reconcile kustomization コマンドを使って同期する)と Flux v2 によってデプロイされる.準備 OK!

$ git add -A && \
git commit -m "add podinfo image scan" && \
git push origin main

$ flux reconcile kustomization flux-system --with-source

$ flux get image repository podinfo
NAME    LAST SCAN               SUSPENDED       READY   MESSAGE                        
podinfo 2022-04-18T00:00:00Z    False           True    successful scan, found 33 tags

$ flux get image policy podinfo
NAME    LATEST IMAGE                            READY   MESSAGE                                                                
podinfo ghcr.io/stefanprodan/podinfo:5.0.3      True    Latest image tag for 'ghcr.io/stefanprodan/podinfo' resolved to: 5.0.3

ImageUpdateAutomation オブジェクトを追加する

Flux v2 の「Automate image updates(自動イメージ更新)機能」を有効化するためには Deployment YAML の spec.containers.image# {"$imagepolicy": "flux-system:podinfo"} のようにコメントを書く必要がある.

spec:
  containers:
  - name: podinfod
    image: ghcr.io/stefanprodan/podinfo:5.0.0 # {"$imagepolicy": "flux-system:podinfo"}

そして flux create image update コマンドを使って ImageUpdateAutomation オブジェクトを追加する.

$ flux create image update flux-system \
--git-repo-ref=flux-system \
--git-repo-path="./clusters/my-cluster" \
--checkout-branch=main \
--push-branch=main \
--author-name=fluxcdbot \
--author-email=fluxcdbot@users.noreply.github.com \
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}" \
--export > ./clusters/my-cluster/flux-system-automation.yaml

./clusters/my-cluster/flux-system-automation.yaml は以下となる.ImageUpdateAutomation オブジェクトは「どういう設定で GitHub リポジトリにコミットをするか」を指定する.

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: flux-system
  update:
    path: ./clusters/my-cluster
    strategy: Setters

自動イメージ更新を待つ

GitHub リポジトリに ImageUpdateAutomation YAML を追加して少し待つ(もしくは flux reconcile kustomization コマンドを使って同期する)と Flux v2 によってデプロイされる.少し待っていると 5.0.0 から 5.0.3 に更新された!👏

$ git add -A && \
git commit -m "add image updates automation" && \
git push origin main

$ kubectl get deployments.apps podinfo -o yaml | grep 'image:'
      - image: ghcr.io/stefanprodan/podinfo:5.0.0

(少し待つ...)

$ kubectl get deployments.apps podinfo -o yaml | grep 'image:'
      - image: ghcr.io/stefanprodan/podinfo:5.0.3

GitHub の podinfo-deployment.yaml も Flux v2 によって更新されていた.

spec:
  containers:
  - name: podinfod
    image: ghcr.io/stefanprodan/podinfo:5.0.3 # {"$imagepolicy": "flux-system:podinfo"}

GitHub の差分も載せておく!

マイナーバージョンとパッチバージョンの最新を探す

今度は ImagePolicy オブジェクトを更新して「マイナーバージョンとパッチバージョンの最新」を探す.policy.semver.range: 5.0.x から policy.semver. range: ^5 にした.

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: podinfo
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: podinfo
  policy:
    semver:
      range: ^5

GitHub リポジトリに ImageUpdateAutomation YAML を追加して少し待つ(もしくは flux reconcile kustomization コマンドを使って同期する)と Flux v2 によってデプロイされる.少し待っていると 5.0.3 から 5.2.1 に更新された!👏

$ git add -A && \
git commit -m "update image policy" && \
git push origin main

$ kubectl get deployments.apps podinfo -o yaml | grep 'image:'
      - image: ghcr.io/stefanprodan/podinfo:5.0.0

(少し待つ...)

$ kubectl get deployments.apps podinfo -o yaml | grep 'image:'
      - image: ghcr.io/stefanprodan/podinfo:5.2.1

まとめ

Flux v2 でも「Automate image updates(自動イメージ更新)機能」を使って「Image Ops」を実現できることを確認できた.しかし Flux v1 と互換性はなく,多くの CRD (Custom Resource Definitions) を組み合わせた構成になっている点は覚えておくと良さそう.以下のドキュメントに Flux v1 → Flux v2 にマイグレーションする手順が載っている.

fluxcd.io

fluxcd.io

Pod でルートファイルシステムを読み取り専用にする securityContext.readOnlyRootFilesystem

Kubernetes で Pod(正確にはコンテナ単位)に securityContext.readOnlyRootFilesystem: true を設定すると,ルートファイルシステムを読み取り専用にして書き込み操作を抑止できる.アプリケーションのセキュリティ対策として使える.補足をすると Kubernetes 固有の機能ではなく,Docker にも docker run コマンドに --read-only オプションが用意されている.

securityContext:
  readOnlyRootFilesystem: true

検証 : BusyBox

まず securityContext.readOnlyRootFilesystem: true を設定して BusyBox を起動するサンプルマニフェストを作る.

apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: busybox
    image: busybox:1.34
    command: ['sh', '-c', 'sleep 3600']
    securityContext:
      readOnlyRootFilesystem: true

実際に Pod を起動して,書き込み操作(とファイル削除)をすると Read-only file system というエラーになった.

$ kubectl apply -f busybox.yaml 
$ kubectl exec -it busybox -- /bin/sh

/ # touch hello.txt
touch: hello.txt: Read-only file system

/ # rm /bin/sh
rm: remove '/bin/sh'? y
rm: can't remove '/bin/sh': Read-only file system

検証 : nginx

次は securityContext.readOnlyRootFilesystem: true を設定して nginx を起動するサンプルマニフェストを作る.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.21
    securityContext:
      readOnlyRootFilesystem: true

今度は Pod を起動することができなかった.Pod のログを確認すると Read-only file system というエラーで nginx を起動すらできていなかった.実際にアプリケーションに securityContext.readOnlyRootFilesystem: true を設定するのは簡単ではなく,書き込みをする可能性のあるディレクトリを洗い出しておく必要がある.

$ kubectl apply -f nginx.yaml 

$ kubectl logs nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: can not modify /etc/nginx/conf.d/default.conf (read-only file system?)
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/04/13 00:00:00 [emerg] 1#1: mkdir() "/var/cache/nginx/client_temp" failed (30: Read-only file system)
nginx: [emerg] mkdir() "/var/cache/nginx/client_temp" failed (30: Read-only file system)

nginx の例だと,Docker Hub の nginx ドキュメントに載っている Running nginx in read-only mode が参考になる.デフォルト構成の nginx では /var/cache/var/run に書き込み操作をすると書いてある.他にもログを確認しながらサンプルマニフェストを修正する.

hub.docker.com

最終的に以下のサンプルマニフェストでうまく Pod を起動できるようになった.今回は emptyDir でボリュームを作って,以下の 3 ディレクトリにマウントした.

  • /etc/nginx/conf.d
  • /var/cache/nginx
  • /var/run
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.21
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
      - name: etc-nginx
        mountPath: /etc/nginx/conf.d
      - name: var-cache
        mountPath: /var/cache/nginx
      - name: var-run
        mountPath: /var/run
  volumes:
    - name: etc-nginx
      emptyDir: {}
    - name: var-cache
      emptyDir: {}
    - name: var-run
      emptyDir: {}

参考 : EKS Best Practices Guides

EKS Best Practices Guides にも securityContext.readOnlyRootFilesystem: true を設定する件が載っている.

aws.github.io

kubelet が他ノードのラベルを操作しないように制限できる NodeRestriction を試した

Kubernetes で有効化できる Admission Plugin である「NodeRestriction」の動作確認をした.簡単にまとめておく!

「NodeRestriction」kubelet に対して Node / Pod の操作範囲を制限できる.具体例を挙げると kubelet によるラベル操作を自ノードに制限し,他ノードを拒否できる.また特定のプレフィックスを付けたラベルは kubelet によって操作できないようにも制限できる.詳しくは以下のドキュメントに載っている.

kubernetes.io

検証環境

今回は kubeadm を使って構築した Kubernetes v1.23.1 クラスタを使う.以下のように controlplanenode01 ノードで構成している.

$ kubectl get nodes
NAME           STATUS   ROLES                  AGE   VERSION
controlplane   Ready    control-plane,master   10d   v1.23.1
node01         Ready    <none>                 10d   v1.23.1

そして kube-apiserver--enable-admission-plugins オプションを確認すると「NodeRestriction」も有効化してある.

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --authorization-mode=Node,RBAC
    - --enable-admission-plugins=NodeRestriction

(中略)

さらに今回試す構成図とラベル操作の関係を図にまとめた!

  • my-label=my-value
  • node-restriction.kubernetes.io/my-label=my-value

f:id:kakku22:20220412134309p:plain

検証 1 : ラベル操作

まず,controlplane ノードの kubelet の操作を前提にするために /etc/kubernetes/kubelet.conf を読み込んでおく.

controlplane $ export KUBECONFIG=/etc/kubernetes/kubelet.conf

さっそくラベル my-label=my-valuecontrolplanenode01 ノードに設定する.「NodeRestriction」を有効化しているため controlplane ノードは成功する.しかし node01 ノードは失敗する.

controlplane $ kubectl label node controlplane my-label=my-value
node/controlplane labeled

controlplane $ kubectl label node node01 my-label=my-value
Error from server (Forbidden): nodes "node01" is forbidden: node "controlplane" is not allowed to modify node "node01"

検証 2 : ラベル操作 (node-restriction.kubernetes.io/)

「NodeRestriction」を有効化していると node-restriction.kubernetes.io/ プレフィックスを付けたラベルの更新は「自ノードだとしても」拒否される.ドキュメントには「ワークロードを隔離するために予約されている」と書かれている.以下のように controlplane ノードでも失敗する.

controlplane $ kubectl label node controlplane node-restriction.kubernetes.io/my-label=my-value
Error from server (Forbidden): nodes "controlplane" is forbidden: is not allowed to modify labels: node-restriction.kubernetes.io/my-label

検証 3 : NodeRestriction を無効化する

最後は「NodeRestriction」を無効化する./etc/kubernetes/manifests/kube-apiserver.yamlkube-apiserver--enable-admission-plugins オプションを削除する.すると以下のように controlplanenode01 ノードで全てのラベル操作ができてしまう.

controlplane $ export KUBECONFIG=/etc/kubernetes/kubelet.conf

controlplane $ kubectl label node controlplane my-label=my-value
node/controlplane labeled

controlplane $ kubectl label node node01 my-label=my-value
node/node01 labeled

controlplane $ kubectl label node controlplane node-restriction.kubernetes.io/my-label=my-value
node/controlplane labeled

参考 : kube-bench

参考として「NodeRestriction」CIS Kubernetes Benchmark でも検知できる.kube-apiserver「NodeRestriction」を有効化していないと kube-bench で FAIL になる.

controlplane $ kube-bench

(中略)

[FAIL] 1.2.16 Ensure that the admission control plugin NodeRestriction is set (Automated)

(中略)

1.2.16 Follow the Kubernetes documentation and configure NodeRestriction plug-in on kubelets.
Then, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml
on the master node and set the --enable-admission-plugins parameter to a
value that includes NodeRestriction.
--enable-admission-plugins=...,NodeRestriction,...

github.com

関連記事

kakakakakku.hatenablog.com