Google Cloud Armor security policiesをKubernetesのCustom Resourcesで管理する

# はじめに

インフラの構成管理をしたかったのです。
インフラをただ単に自動的に作るという自動化が目的ではなく、「常にこのようになって欲しい」という仕様を書けば、その状態に簡単に復元してくれるツールを探してました。

- もし対象の設定が破壊されても、1アクションで元に戻る
- 設定を変更したい場合は、人間が理解しやすいドキュメント、もしくはコードを変えるだけ良い。1アクションでできる。
- 設定を削除したい場合は、明示的に削除する。1アクションで実行できる。

1アクションにこだわる理由は、その操作を簡単にしてなるべく複雑な自動化コードを管理・引き継ぎたくなかったから。つまり

- 手順書はいらない。管理するのは、その仕様と、CI/CDに書かれた1アクションのみ

という状態を作り出したかったのです。

# 最初に出会ったTerraformとAnsible

私は、メインはソフトウェアエンジニアなので、インフラ構成ツールはあまり詳しくありませんでした。

[最強のインフラチームを模索する人](https://event.shoeisha.jp/devsumi/20190702/session/2069/)が使っていたのが、[Terraform](https://www.terraform.io/) と [Ansible](https://www.ansible.com/)。
特に今回のやりたい事であれば、Terraformをおすすめされましたが、どうにも状態管理を自分で管理しなきゃならない `tfstate` の存在が気になってしまいます。

GCS、S3などで管理するみたいですが、構成管理の情報としては、全てGitで管理したいのです。
Gitリポジトリが **Single Source of Truth** であるべきであり、そこから全てが派生して管理されるべきという自分の信念に反しました。

Ansibleは・・・色々サポートバージョンが古くて断念しました。
[magic-modules](https://github.com/GoogleCloudPlatform/magic-modules)から自動生成するだけじゃないのかなぁ・・・と思いつつ色々issueが進んでいない状況をみて断念です。

# Kubernetesは良い

Kubernetesは、yamlでspecを管理できます。完全ではないにしろ、私が望んでいた下記の3つを満たしてくれます。

> - もし、対象の設定が破壊されても、1アクションで元に戻る
> - 設定を変更したい場合は、人間が理解しやすいドキュメント、もしくはコードを変えるだけ良い。1アクションでできる。
> - 設定を削除したい場合は、明示的に削除する。1アクションで実行できる。

# 結果

長い前置きはさておき、Kubernetesには、 [Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)の拡張定義が行えます。
結果、これでやりたい管理はほぼできました。

今回は、特に変更が発生しやすい [Google Cloud Armor security policies](https://cloud.google.com/armor/)を管理するためのOperatorを実装しました。

https://github.com/h-r-k-matsumoto/security-policy-operator

1アクションと言いつつOperatorでごにょごにょしてるじゃん！って思いますよね。
・・・良いんです！

# 利用シーンと使い方

## 開発環境のホワイトリスト管理

クラウド環境において、特定のサービスは、特定のIPアドレス(社内、パートナーさんなど)だけを許可したいという場合があります。しかもパートナーさんが増える事が度々あり、その都度ホワイトリストの編集が発生する・・・といった利用シーンです。

このような場合に利用できます。

### 前提

- [BackendConfig](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-armor-backendconfig)を利用している前提とします。
- Security Policyを操作するために、「Compute Security Admin」権限を持つサービスアカウントを用意する必要があります。<br>作り方は、[README.md](https://github.com/h-r-k-matsumoto/security-policy-operator/#requirements)に記載しています。

### インストール

最初にnamespace作ります。

```shell
$ kubectl apply -f https://raw.githubusercontent.com/h-r-k-matsumoto/security-policy-operator/master/dist/prepared.yaml
namespace/security-policy-operator-system created
$
```

次に、用意していたサービスアカウントのkeyを使ってsecretを作ります。名前は `security-policy-operator-key` 固定です。

```shell
$ kubectl create secret generic security-policy-operator-key -n security-policy-operator-system --from-file=key.json=PATH-TO-KEY-FILE.json
secret/security-policy-operator-key created
$ 
```

最後にoperatorやRBACの設定をインストールします。

```shell
$ kubectl apply -f https://raw.githubusercontent.com/h-r-k-matsumoto/security-policy-operator/master/dist/install.yaml
customresourcedefinition.apiextensions.k8s.io/securitypolicies.cloudarmor.matsumo.dev created
deployment.extensions/security-policy-operator-controller-manager created
service/security-policy-operator-controller-manager-metrics-service created
rolebinding.rbac.authorization.k8s.io/security-policy-operator-leader-election-rolebinding created
clusterrole.rbac.authorization.k8s.io/security-policy-operator-manager-role created
clusterrolebinding.rbac.authorization.k8s.io/security-policy-operator-manager-rolebinding created
clusterrole.rbac.authorization.k8s.io/security-policy-operator-proxy-role created
clusterrolebinding.rbac.authorization.k8s.io/security-policy-operator-proxy-rolebinding created
$ 
```

### ホワイトリストを設定する。

下記の仕様とします。

 - デフォルトは、403のエラーを返す。
 - 社内IPアドレス X.X.X.1と、X.X.X.2は許可する。
 - A社さんのIPアドレス Y.Y.Y.1は許可する。
 - B社さんのIPアドレス範囲 Z.Z.Z.0/24は許可する。

この場合は、下記のようになります。

```yaml:foo.yaml
apiVersion: cloudarmor.matsumo.dev/v1beta1
kind: SecurityPolicy
metadata:
  name: securitypolicy-sample
spec:
  description: "foo-serverへのホワイトリスト"
  name: foo-server-whitelist
  defaultAction: "deny(403)"
  rules:
    - action: "allow"
      description: "社内IPアドレス"
      priority: 100
      srcIpRanges:
        - "X.X.X.1"
        - "X.X.X.2"
    - action: "allow"
      description: "A社さんのIPアドレス"
      priority: 101
      srcIpRanges:
        - "Y.Y.Y.1"
    - action: "allow"
      description: "B社さんのIPアドレス"
      priority: 102
      srcIpRanges:
        - "Z.Z.Z.0/24"
```

反映方法(新規作成、変更) は、下記の通りです。これでSecurityPolicyが作成されます。

```shell
kubectl apply -f foo.yaml
```

削除する場合は、下記の通りです。

```shell
kubectl delete -f foo.yaml
```

これでgitのCI/CDの仕組みと、ものすごく連携しやすくなりました。
helmを使えば、もうちょっとスマートに管理もできるのですが、基本yamlで全部gitリポジトリで管理+masterへのマージで常に `apply`するという管理なのでこれで十分です。
operatorのインストールですら、yamlとapplyです。

### 全ての完全なサンプル

Ingress,Service,Deployments,BackendConfig,SecurityPolicy全部入りのyamlを下記場所に保管しています。

こちらはGoogleさんのBackendConfigの[How To](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-armor-backendconfig)に合わせて、ブラックリスト管理となります。

https://github.com/h-r-k-matsumoto/security-policy-operator/blob/master/config/samples/cloudarmor-full-package.yaml

SecurityPolicy以外の内容は、GoogleさんのBackendConfigの[How To](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-armor-backendconfig)の内容とほぼ同じです。


# 最後に

production環境で使うには、今回作った機能ではまだ機能が足りていない、validationもほとんど実装していない状態であり、不十分なのでちょっとした開発環境で使ってます。

そして、Googleさんは、 [BackendConfig](https://cloud.google.com/kubernetes-engine/docs/concepts/backendconfig)としてCustom Resourcesで色々管理できるようにしてくれている所です。
近い将来、今回作ったものの置きかわりがきっとできるでしょう！と期待しています。
なので、開発環境の管理がまずは楽になればいいかな。レベルで作っています。

また、今回のようなOperatorが[OperatorHub](https://operatorhub.io/)に色々あります。欲しいものがないか一度探してみましょう。
Custom Resourcesは夢が広がります！という紹介でした！

# 参考

- Terraform:https://www.terraform.io/
- Ansible:https://www.ansible.com/
- BackendConfig:https://cloud.google.com/kubernetes-engine/docs/concepts/backendconfig
- OperatorHub:https://operatorhub.io/