やりたいこと
- Proxy(SSL Intercept)が提示してくる証明書の検証をIstioでやりたい
- SSL interceptしてくるProxyというのは、サーバ証明書を提示してくるようなやつ
- ブラウザでHTTPSのサイトにアクセスすると、プロキシの証明書を提示してくるやつ
- 以下のようなやつ
-
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
手順
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の設定
# 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
起動
systemctl start squid
以降の手順に向けた仕込み。
export PROXY_IP=xxx.xxx.xxx.xxx #ProxyサーバのIPを入力
export PROXY_PORT=8080
クライアントコンテナの起動
前提条件
- 手元にistioのサンプルのマニフェスト(sleep.yaml)があること
手順
起動して、envoyを挿入して、PODのIDを変数にセット。
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の証明書をクライアントコンテナ内に置く
kubectl cp bump.crt $SOURCE_POD:/tmp/
テスト
CA証明書の提示をしたとき
きれいにアクセスできる。
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>
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証明書の提示をしないとき
アクセスに失敗する。
$ kubectl exec -it $SOURCE_POD -c sleep -- sh -c "HTTPS_PROXY=tps://example.com" | grep -o "<title>.*</title>"
command terminated with exit code 60
1650125999.900 370 192.168.50.62 NONE/200 0 CONNECT example.com:443 - HIER_DIRECT/93.184.216.34 -
Service Entryを追加して、変化をみる
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
location: MESH_EXTERNAL
EOF
テスト
CA証明書の提示をしたとき
宛先がseのhostsの名前になっている。(リクエストは成功)
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>
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
[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の名前になっている。(けどリクエストには失敗)
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
1650126684.213 333 192.168.50.62 NONE/200 0 CONNECT example.com:443 - HIER_DIRECT/93.184.216.34 -
[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を定義する
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をセットする
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証明書の提示をしたとき
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>
[2022-04-16T16:50:22.170Z] "- - -" 0 NC - - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 192.168.50.3:8080 172.17.0.17:46492 - -
#### ログなし
#### ログなし
CA証明書の提示をしないとき
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>
[2022-04-16T16:52:54.507Z] "- - -" 0 NC - - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 192.168.50.3:8080 172.17.0.17:49014 - -
#### ログなし
#### ログなし
SE + DRでやってみる
Service Entry
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への注入
kubectl create secret generic proxy-cert --from-file=/home/minikube/bump.pem
sidecar.istio.io/userVolume: '[{"name":"proxy-cert", "secret":{"secretName":"proxy-cert"}}]'
sidecar.istio.io/userVolumeMount: '[{"name":"proxy-cert", "mountPath":"/tmp/",
egress gatewayにもbump.pemを注入
...ToDo...
テスト
CA証明書の提示をしないとき
うまくsidecarとegress gateway上はうまく言ってそうなんだけど、始端・終端でうまくいっていない。
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
[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 - -
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
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にしても動いてなさそう。
明日調べる
- 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.