Proxy（SSL Intercept）が提示してくる証明書の検証をIstioでやりたい

## やりたいこと

* Proxy（SSL Intercept）が提示してくる証明書の検証をIstioでやりたい
    * **SSL interceptしてくるProxy**というのは、サーバ証明書を提示してくるようなやつ
    * ブラウザでHTTPSのサイトにアクセスすると、プロキシの証明書を提示してくるやつ
    * 以下のようなやつ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/9eb022f6-cdb4-4ebe-b550-017ea86aecb7.png)

* CA証明書をユーザワークロード（コンテナ）の中に埋め込みたくない。
    * プロキシが提示してくるサーバ証明書の検証用のCA証明書はクライアント側に埋め込まないと行けないのだけど、これをなんとかistioの層でやりたい

* ※注意※ まだうまくいっていない

## SSL Interceptするsquidの構築

### 環境

Squidは必ず4.xにすること。3.xとは諸々仕様が違いそうでした。（歪み顔）

* OS
    * CentOS Stream release 8
* Squid
    * 4.15

### 初期設定

SELinuxはOffにしないと、`security_file_certgen`で作ったファイルをsquidから参照できない。（戒め）

* SELinux
    * disabled
* firewalld
    * disabled

### 手順

```bash
cd /etc/squid/
openssl dhparam -outform PEM -out bump_dhparam.pem 2048
cd /etc/squid/
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -keyout bump.key -out bump.crt
mkdir -p /var/lib/squid
rm -rf /var/lib/squid/ssl_db
/usr/lib64/squid/security_file_certgen -c -s /var/lib/squid/ssl_db -M 20MB
chown -R squid:squid /var/lib/squid
```

### squidの設定

```apache
# ACLs
acl localnet src 0.0.0.1-0.255.255.255  # RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8             # RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10          # RFC 6598 shared address space (CGN)
acl localnet src 169.254.0.0/16         # RFC 3927 link-local (directly plugged) machines
acl localnet src 172.16.0.0/12          # RFC 1918 local private network (LAN)
acl localnet src 192.168.0.0/16         # RFC 1918 local private network (LAN)
acl localnet src fc00::/7               # RFC 4193 local private network range
acl localnet src fe80::/10              # RFC 4291 link-local (directly plugged) machines

acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
acl CONNECT method CONNECT

# SSL Bump
sslcrtd_program /usr/lib64/squid/security_file_certgen -s /var/lib/squid/ssl_db -M 4MB
sslproxy_cert_error allow all
acl step1 at_step SslBump1
ssl_bump peek step1
ssl_bump bump all

# Access rules
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

http_access allow localhost manager
http_access deny manager

http_access allow localnet
http_access allow localhost
http_access deny all

# Squid Access Pport
http_port 8080 tcpkeepalive=60,30,3 ssl-bump generate-host-certificates=on dynamic_cert_mem_cache_size=20MB tls-cert=/etc/squid/bump.crt tls-key=/etc/squid/bump.key cipher=HIGH:MEDIUM:!LOW:!RC4:!SEED:!IDEA:!3DES:!MD5:!EXP:!PSK:!DSS options=NO_TLSv1,NO_SSLv3,SINGLE_DH_USE,SINGLE_ECDH_USE tls-dh=prime256v1:/etc/squid/bump_dhparam.pem

# Cache directory
cache_dir ufs /var/spool/squid 100 16 256

# Core dump directory
coredump_dir /var/spool/squid

# Cache settings
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
refresh_pattern .               0       20%     4320
```

### 起動

```bash
systemctl start squid
```

### 以降の手順に向けた仕込み。

```bash
export PROXY_IP=xxx.xxx.xxx.xxx #ProxyサーバのIPを入力
export PROXY_PORT=8080
```

## クライアントコンテナの起動

### 前提条件

* 手元にistioのサンプルのマニフェスト（sleep.yaml）があること

### 手順

起動して、envoyを挿入して、PODのIDを変数にセット。

```bash
kubectl apply -f samples/sleep/sleep.yaml
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml)
export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
```

### Squidの証明書をクライアントコンテナ内に置く

```bash
kubectl cp bump.crt $SOURCE_POD:/tmp/
```


### テスト

#### CA証明書の提示をしたとき

きれいにアクセスできる。

```bash:curlのコマンド
kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://example.com --cacert /tmp/bump.crt" | grep -o "<title>.*</title>"
<title>Example Domain</title>
```

```:squidのログ
1650125956.296    323 192.168.50.62 NONE/200 0 CONNECT example.com:443 - HIER_DIRECT/93.184.216.34 -
1650125956.305      0 192.168.50.62 TCP_MEM_HIT/200 1840 GET https://example.com/ - HIER_NONE/- text/html
```

#### CA証明書の提示をしないとき

アクセスに失敗する。

```bash:curlのコマンド
$ kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=tps://example.com" | grep -o "<title>.*</title>"
command terminated with exit code 60
```

```:squidのログ
1650125999.900    370 192.168.50.62 NONE/200 0 CONNECT example.com:443 - HIER_DIRECT/93.184.216.34 -
```

## Service Entryを追加して、変化をみる

### seを作る

```bash
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: proxy
spec:
  hosts:
  - my-company-proxy.com # ignored
  addresses:
  - $PROXY_IP/32
  ports:
  - number: $PROXY_PORT
    name: tcp
    protocol: TCP
  location: MESH_EXTERNAL
EOF
```

### テスト

#### CA証明書の提示をしたとき

宛先がseのhostsの名前になっている。（リクエストは成功）

```bash:curlのコマンド
 kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://example.com --cacert /tmp/bump.crt" | grep -o "<title>.*</title>"
<title>Example Domain</title>
```

```:squidのログ
1650126621.290    360 192.168.50.62 NONE/200 0 CONNECT example.com:443 - HIER_DIRECT/93.184.216.34 -
1650126621.300      0 192.168.50.62 TCP_MEM_HIT/200 1840 GET https://example.com/ - HIER_NONE/- text/html
```

```:sidecarのログ
[2022-04-16T16:30:20.940Z] "- - -" 0 - - - "-" 1357 6063 374 - "-" "-" "-" "-" "192.168.50.3:8080" outbound|8080||my-company-proxy.com 172.17.0.17:54846 192.168.50.3:8080 172.17.0.17:54844 - -
```

#### CA証明書の提示をしないとき

宛先がseのhostsの名前になっている。（けどリクエストには失敗）

```bash:curlのコマンド
kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl ht
tps://example.com" | grep -o "<title>.*</title>"
command terminated with exit code 60
```

```:squidのログ
1650126684.213    333 192.168.50.62 NONE/200 0 CONNECT example.com:443 - HIER_DIRECT/93.184.216.34 -
```

```:sidecarのログ
[2022-04-16T16:31:23.890Z] "- - -" 0 - - - "-" 1165 3699 343 - "-" "-" "-" "-" "192.168.50.3:8080" outbound|8080||my-company-proxy.com 172.17.0.17:55906 192.168.50.3:8080 172.17.0.17:55904 - -
```

## Egress Gatewayを通す

### egress gatewayを通してトラフィックを指示するためのportを定義する

```bash
export EGRESS_GATEWAY_PROXY_PORT=7777
```

### seをつくる

```
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: proxy
spec:
  hosts:
  - my-company-proxy.com # ignored
  addresses:
  - $PROXY_IP/32
  ports:
  - number: $PROXY_PORT
    name: tcp
    protocol: TCP
  - number: $EGRESS_GATEWAY_PROXY_PORT
    name: tcp-gateway
    protocol: TCP
  location: MESH_EXTERNAL
EOF
```

### ExternalName service をつくる

```
kubectl apply -n istio-system -f - <<EOF
kind: Service
apiVersion: v1
metadata:
  name: myproxy
spec:
  type: ExternalName
  externalName: $PROXY_IP
  ports:
  - protocol: TCP
    port: $PROXY_PORT
    name: tcp
EOF
```

### PROXY_HOSTNAME に今作ったSVCのFQDNをセットする

```bash
export PROXY_HOSTNAME=myproxy.istio-system.svc.cluster.local
```

### プロキシ用のegress Gatewayを作成し、egress Gatewayを通るトラフィックとegress Gatewayからプロキシへのトラフィックを誘導するためのdrとvsを作成する
```
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway
  servers:
  - port:
      number: $EGRESS_GATEWAY_PROXY_PORT
      name: tcp
      protocol: TCP
    hosts:
    - my-company-proxy.com
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: egressgateway-for-proxy
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: proxy
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: myproxy
spec:
  host: $PROXY_HOSTNAME
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: direct-traffic-to-proxy-through-egress-gateway
spec:
  hosts:
  - my-company-proxy.com
  gateways:
  - mesh
  - istio-egressgateway
  tcp:
  - match:
    - gateways:
      - mesh
      destinationSubnets:
      - $PROXY_IP/32
      port: $PROXY_PORT
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: proxy
        port:
          number: $EGRESS_GATEWAY_PROXY_PORT
  - match:
    - gateways:
      - istio-egressgateway
      port: $EGRESS_GATEWAY_PROXY_PORT
    route:
    - destination:
        host: $PROXY_HOSTNAME
        port:
          number: $PROXY_PORT
      weight: 100
EOF
```

### テスト

#### CA証明書の提示をしたとき

```bash:curlのコマンド
kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://example.com --cacert /tmp/bump.crt" | grep -o "<title>.*</title>"
<title>Example Domain</title>
```

```:sidecarのログ
[2022-04-16T16:50:22.170Z] "- - -" 0 NC - - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 192.168.50.3:8080 172.17.0.17:46492 - -
```

```:egress gatewayのログ
#### ログなし
```


```:squidのログ
#### ログなし
```


#### CA証明書の提示をしないとき

```bash:curlのコマンド
kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://example.com" | grep -o "<title>.*</title>"
<title>Example Domain</title>
```

```:sidecarのログ
[2022-04-16T16:52:54.507Z] "- - -" 0 NC - - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 192.168.50.3:8080 172.17.0.17:49014 - -
```

```:egress gatewayのログ
#### ログなし
```

```:squidのログ
#### ログなし
```

## SE + DRでやってみる

Service Entry

```yaml:SE
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-company-proxy
spec:
  host: my-company-proxy.com
  trafficPolicy:
    tls:
      insecureSkipVerify: true
      caCertificates: /tmp/bump.pem
      mode: SIMPLE
```

bump.pemのistio-proxyへの注入

```bash:secretを作成
kubectl create secret generic proxy-cert --from-file=/home/minikube/bump.pem
```

```yaml:sleepのdeployにアノテーションを追加
        sidecar.istio.io/userVolume: '[{"name":"proxy-cert", "secret":{"secretName":"proxy-cert"}}]'
        sidecar.istio.io/userVolumeMount: '[{"name":"proxy-cert", "mountPath":"/tmp/",
```

egress gatewayにもbump.pemを注入

```:アノテーションでの注入方法がわからなかったので、一旦、execで注入
...ToDo...
```

### テスト

#### CA証明書の提示をしないとき

うまくsidecarとegress gateway上はうまく言ってそうなんだけど、始端・終端でうまくいっていない。

```bash:curlのコマンド
kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://example.com" | grep -o "<title>.*</title>"
command terminated with exit code 56
```

```:sidecarのログ
[2022-04-17T16:26:06.698Z] "- - -" 0 UF,URX - - "-" 0 0 2 - "-" "-" "-" "-" "192.168.50.3:8080" outbound|8080||my-company-proxy.com - 192.168.50.3:8080 172.17.0.4:54918 - -
```

```:egress gatewayのログ
2022-04-17T16:14:02.913212Z     info    ads     ADS: new connection for node:istio-egressgateway-7585765848-lg6ln.istio-system-29
2022-04-17T16:14:02.913383Z     info    cache   read certificate from file      resource=file-root:/tmp/bump.pem
2022-04-17T16:14:02.913466Z     info    ads     SDS: PUSH request for node:istio-egressgateway-7585765848-lg6ln.istio-system resources:1 size:1.3kB resource:file-root:/tmp/bump.pem
```

```:squidのログ
1650212766.693      0 192.168.50.62 NONE/400 3754 NONE error:invalid-request - HIER_NONE/- text/html
```

### insecureSkipVerify で乗り切れないか？

> InsecureSkipVerify は、プロキシがホストに対応するサーバ証明書の CA 署名と SAN の検証をスキップすべきかどうかを指定します。このフラグは、グローバルなCA署名の検証が有効で、環境変数VerifyCertAtClientが真に設定されていて、特定のホストに対して検証が必要ない場合のみ設定されるべきです。VerifyCertAtClientが有効であってもなくても、有効にすると、CA署名とSANの検証はスキップされます。
InsecureSkipVerifyはデフォルトでfalseです。VerifyCertAtClient は、Istio バージョン 1.9 ではデフォルトで false ですが、それ以降のバージョンではデフォルトで true となり、今後デフォルトで有効化される予定です。

ん～、trueにしても動いてなさそう。

## 明日調べる

https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/#ProxyConfig

* caCertificatesPem

> The PEM data of the extra root certificates for workload-to-workload communication. This includes the certificates defined in MeshConfig and any other certificates that Istiod uses as CA. The plugin certificates (the ‘cacerts’ secret), self-signed certificates (the ‘istio-ca-secret’ secret) are added automatically by Istiod.