Kubernetes Istio

この例では、Next-Gen WAF エージェントが Docker のサイドカーコンテナとして実行され、アプリケーションにデプロイされた Istio サービスメッシュと直接統合されます。この設定では、従来の north/south (クライアント-サーバー間) 通信に加えて、east/west (サービス間) 通信の Web リクエストも Next-Gen WAF で検査できるようになります。

Next-Gen WAF エージェントを統合する

Next-Gen WAF エージェントは、各 Pod にサイドカーとしてインストールすることも、特定の要件向けに Service としてインストールすることもできます。

Kubernetes における Next-Gen WAF エージェントの推奨インストール方法は、sigsci-agent を Pod にサイドカーとして統合することです。これは、sigsci-agent を Kubernetes Pod の追加コンテナとして追加することを意味します。サイドカーとして動作することで、エージェントは Pod 内のアプリケーション/Service と共にスケーリングすることになり、個別にスケーリングする必要がなくなります。ただし、状況によっては sigsci-agent コンテナを Service としてインストールし、アプリケーションとは別にスケーリングした方が適している場合もあります。

sigsci-agentコンテナは、使用するインストールタイプやモジュールによってさまざまな方法で構成できます。

コンテナフック preStop を使用すると、Pod のシャットダウンを遅らせ、ドレインタイムアウトを確実に満たすことができます。

preStop:
exec:
command:
- sleep
- "30"

デフォルトでは、エージェントは予備的な検査のために迅速な起動とパフォーマンス準備を優先します。ただし、ルールや設定データを読み込んだ後にのみトラフィックを検査したい場合、迅速な起動が必ずしも望ましいとは限りません。その場合は、Startup Probe を設定してエージェントの起動を遅延させることを検討してください。

エージェントコンテナイメージを取得および更新する

公式の signalsciences/sigsci-agent コンテナイメージが Docker Hub で利用可能です。

独自のイメージをビルドしたい場合や、イメージをカスタマイズする必要がある場合は、sigsci-agent のビルド手順に従ってください。

これらの手順では、latest バージョンのエージェントを imagePullPolicy: Always で参照しており、ローカルにすでにエージェントが存在する場合でも最新のエージェントバージョンを取得します。これは、ドキュメントが古くならないようにし、このドキュメントを参照するすべての人が古い状態のままのエージェントを使うことを防ぐためです。ただし、インストールの一貫性を維持する必要がある場合や、特定バージョンのエージェントを維持する必要がある場合は、この方法が適さない可能性があります。このような場合は、エージェントのバージョンを指定する必要があります。Docker Hub 上のイメージにはバージョンがタグ付けされており、バージョンのリストは Docker Hub で確認できます

latest イメージを使用するか特定のバージョンを使用するかにかかわらず、エージェントを最新の状態に保つために考慮すべき項目がいくつかあります。

latestコンテナイメージを使用する

latest イメージを使用することを選択した場合は、エージェントを最新の状態に保つ方法を検討する必要があります。

  • imagePullPolicy: Always オプションを使用した場合、起動のたびに最新のイメージが取得され、エージェントは継続的に更新を受け取ります。

  • あるいは、起動時に常にプルするのではなく、定期的に手動でローカルキャッシュを更新する方法もあります。

    $ docker pull signalsciences/sigsci-agent:latest

    次に、latestimagePullPolicy: Never 設定で使用して、起動時にプルが実行されないようにします (上記のように手動でのみ)。

    - name: sigsci-agent
    image: signalsciences/sigsci-agent:latest
    imagePullPolicy: Never
    ...

バージョン管理されたコンテナイメージを使用する

特定のバージョンのエージェントを使用するには、latest をエージェントのバージョン (ここでは x.xx.x で表されます) に置き換えてください。この場合、イメージは更新されるべきではないため、imagePullPolicy: IfNotPresent に変更することもおすすめします。

- name: sigsci-agent
image: signalsciences/sigsci-agent:x.xx.x
imagePullPolicy: IfNotPresent
...

この方法では、指定したエージェントバージョンがプルされ、ローカルにキャッシュされます。この方法を使用する場合、後からエージェントイメージを更新しやすくするため、Helm などを使ってエージェントイメージをパラメータ化することを推奨します。

コンテナイメージにカスタムタグを使用する

ローカルエージェントイメージにカスタムタグを適用することも可能です。これを行うには、エージェントイメージを (バージョンまたは latest で) プルし、カスタムタグを適用し、そのカスタムタグを設定で使用します。ローカルイメージは手動でのみ更新されるように imagePullPolicy: Never を指定する必要があります。その後、エージェントを最新の状態に保つために、ローカルイメージを定期的に更新する必要があります。

例:

$ docker pull signalsciences/sigsci-agent:latest
$ docker tag signalsciences/sigsci-agent:latest signalsciences/sigsci-agent:testing

次に、このイメージタグを設定で使用します。

- name: sigsci-agent
image: signalsciences/sigsci-agent:testing
imagePullPolicy: Never
...

エージェントコンテナを設定する

エージェントの設定は通常、環境変数を通じて行います。ほとんどの設定オプションは環境変数として利用可能です。環境変数名は、設定オプション名をすべて大文字にし、SIGSCI_ を接頭辞として付け、ダッシュ (-) をアンダースコア (_) に置き換えたものになります。たとえば、max-procs オプションは SIGSCI_MAX_PROCS 環境変数になります。利用可能なオプションの詳細については、エージェント設定ドキュメントを参照してください。

sigsci-agent コンテナには設定が必要ないくつかのオプションがあります。

  • エージェント認証情報 (Agent Access KeyAgent Secret Key)。

  • 一時ファイルを書き込むためのボリューム。

エージェントの認証情報

sigsci-agent の認証情報は、2つの環境変数で設定されます。これらの変数が設定されていない場合、エージェントは起動しません。

  • SIGSCI_ACCESSKEYID : Agent Access Key は、エージェントが設定されている Next-Gen WAF コントロールパネル上のサイト (ワークスペース) を識別します。

  • SIGSCI_SECRETACCESSKEY : Agent Secret Key はエージェントを認証・認可するための共有秘密鍵です。

これらの値は非常に機密性が高いため、Kubernetes の組み込み secrets 機能を使用することを推奨します。この構成では、エージェントは Deployment 設定内にハードコードされた値を読み取るのではなく、Secrets データから値を取得します。これにより、エージェント認証情報のローテーションも、1か所を変更するだけで管理しやすくなります。

secrets 機能を使うには、value オプションではなく valueFrom オプションを使用します。例 :

env:
- name: SIGSCI_ACCESSKEYID
valueFrom:
secretKeyRef:
# Update my-site-name-here to the correct site (workspace) name or similar identifier
name: sigsci.my-site-name-here
key: accesskeyid
- name: SIGSCI_SECRETACCESSKEY
valueFrom:
secretKeyRef:
# Update my-site-name-here to the correct site (workspace) name or similar identifier
name: sigsci.my-site-name-here
key: secretaccesskey

secrets 機能は、Kubernetes 内のさまざまなストアにシークレットを保持します。このガイドでは、例として汎用のシークレットストアを使用していますが、同等のストアであればどれでも使用できます。エージェントのシークレットは、以下の例のような YAML を使用して汎用のシークレットストアに追加できます。

apiVersion: v1
kind: Secret
metadata:
name: sigsci.my-site-name-here
stringData:
accesskeyid: 12345678-abcd-1234-abcd-1234567890ab
secretaccesskey: abcdefg_hijklmn_opqrstuvwxy_z0123456789ABCD

これは、次のように kubectl を使用してコマンドラインから作成することもできます。

$ kubectl create secret generic sigsci.my-site-name-here \
--from-literal=accesskeyid=12345678-abcd-1234-abcd-1234567890ab \
--from-literal=secretaccesskey=abcdefg_hijklmn_opqrstuvwxy_z0123456789ABCD

Kubernetes の secrets 機能に関する詳細は、Kubernetes のドキュメントを参照してください。

エージェント用の一時ボリューム

セキュリティ強化のため、sigsci-agent コンテナはルートファイルシステムを読み取り専用としてマウントして実行することを推奨します。ただし、エージェントは RPC 通信用のソケットファイルのような一時ファイルや、位置情報データなどの定期的に更新されるファイルを書き込む必要があります。

読み取り専用のルートファイルシステムでこれを実現するには、書き込み可能なボリュームをマウントする必要があります。この書き込み可能なボリュームは、同じポッド内の他のコンテナにRPCソケットファイルを公開するために共有することもできます。

書き込み可能なボリュームを作成する推奨方法は、組み込みの emptyDir ボリュームタイプを使用することです。これは通常、次の例のように Deployment の volumes セクションで設定します。

volumes:
- name: sigsci-tmp
emptyDir: {}

その後、コンテナはこのボリュームを /sigsci/tmp にマウントします。

volumeMounts:
- name: sigsci-tmp
mountPath: /sigsci/tmp

公式エージェントコンテナイメージのデフォルト設定では、一時ボリュームが /sigsci/tmp にマウントされます。エージェントコンテナ用にこれを変更する必要がある場合、次のエージェント設定オプションもデフォルトから新しいマウント場所に合わせて変更する必要があります。

  • rpc-address デフォルトは /sigsci/tmp/sigsci.sock

  • shared-cache-dir (デフォルト : /sigsci/tmp/cache)

外部認可を使用してNext-Gen WAFエージェントを統合する

Istio v1.9 以降では、アクセス制御を外部認可システムに委任する Authorization Policy を設定できるようになりました。

以下のスニペットは Istio のサンプルに沿ったもので、例として使用されている ext-authz サービスを Next-Gen WAF エージェントに置き換えるよう拡張しています。以下のスニペット内で参照されている初期の名前空間やテスト用ワークロードについては Istio のドキュメントを参照してください。特に記載がない限り、すべてのファイルは foo ネームスペースに適用されます。

外部オーソライザーをデプロイしてください

シークレットがすでに適用されていることを前提とします。

apiVersion: v1
kind: Service
metadata:
name: sigsci-agent
labels:
app: sigsci-agent
spec:
ports:
- name: grpc
port: 9999
targetPort: 9999
selector:
app: sigsci-agent
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sigsci-agent
spec:
replicas: 1
selector:
matchLabels:
app: sigsci-agent
template:
metadata:
labels:
app: sigsci-agent
spec:
containers:
- name: sigsci-agent
image: signalsciences/sigsci-agent:latest
imagePullPolicy: IfNotPresent
# Configure the agent to use Envoy gRPC on port 9999
env:
- name: SIGSCI_ACCESSKEYID
valueFrom:
secretKeyRef:
# This secret needs added (see docs on sigsci secrets)
name: sigsci-agent-accesskey
key: accesskeyid
- name: SIGSCI_SECRETACCESSKEY
valueFrom:
secretKeyRef:
# This secret needs added (see docs on sigsci secrets)
name: sigsci-agent-accesskey
key: secretaccesskey
# Configure the Envoy to expect response data (if using a gRPC access log config for Envoy)
- name: SIGSCI_ENVOY_EXPECT_RESPONSE_DATA
value: "1"
- name: SIGSCI_ENVOY_GRPC_ADDRESS
value: :9999
ports:
- containerPort: 9999
securityContext:
# The sigsci-agent container should run with its root filesystem read only
readOnlyRootFilesystem: true
---

エージェントが実行されていることを確認します。

$ kubectl logs "$(kubectl get pod -l app=sigsci-agent -n foo -o jsonpath={.items..metadata.name})" -n foo -c sigsci-agent

外部認可サービスを定義する

以下のコマンドでメッシュ設定を編集し、拡張プロバイダーの定義を追加します。

$ kubectl edit configmap istio -n istio-system
data:
mesh: |-
# Add the following content to define the external authorizers.
extensionProviders:
- name: "sigsci-agent-ext-authz"
envoyExtAuthzGrpc:
service: "sigsci-agent.foo.svc.cluster.local"
port: "9999"
timeout: 0.2s
failOpen: true
includeRequestBodyInCheck:
packAsBytes: true
# use `allowPartialMessage: false` if you want to inspect larger payloads
allowPartialMessage: true
maxRequestBytes: 8192
- name: "sigsci-agent-access-log"
envoyHttpAls:
service: "sigsci-agent.foo.svc.cluster.local"
port: "9999"
additionalRequestHeadersToLog:
- "x-sigsci-request-id"
- "x-sigsci-waf-response"
- "accept"
- "content-type"
- "content-length"
additionalResponseHeadersToLog:
- "date"
- "server"
- "content-type"
- "content-length"

外部認可を有効化する

外部認可を有効化し、ログ記録を適用します。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz
spec:
selector:
matchLabels:
app: httpbin
action: CUSTOM
provider:
# The provider name must match the extension provider defined in the mesh config.
name: sigsci-agent-ext-authz
rules:
# The rules specify when to trigger the external authorizer.
- to:
- operation:
paths: ["/headers"]
# kubectl apply -f logging.yaml
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: mesh-default
namespace: istio-system
spec:
accessLogging:
- providers:
- name: sigsci-agent-access-log
# In another terminal curl the httpbin app:
$ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl -v "http://httpbin.foo:8000/headers" -s
# tail the logs
$ kubectl logs -f "$(kubectl get pod -l app=sigsci-agent -n foo -o jsonpath={.items..metadata.name})" -n foo -c sigsci-agent

EnvoyFilterを使用してNext-Gen WAFエージェントを統合する

Istio は内部で Envoy プロキシを使用しています。このため、Istio では一般的な Envoy のインストールと同様に、Next-Gen WAF エージェントを gRPC モードで利用できます。Next-Gen WAF エージェントのインストールおよび設定方法は、一般的な Envoy のインストールとほぼ同じですが、Envoy プロキシが自動的にサイドカーとしてデプロイされる点が異なります。その後、Envoy は Istio の EnvoyFilter を使用して設定されます。EnvoyFilter に対する拡張が必要なため、完全な Istio 統合は Istio v1.3 以降でのみ可能です。

Istio ベースのアプリケーションデプロイに Next-Gen WAF サポートを追加するには、以下の手順を実行する必要があります。

  • Envoy gRPC リスナーモードで設定された sigsci-agent コンテナを Pod に追加します。

  • emptyDir{} ボリュームを追加して、sigsci-agent が一時的なデータを書き込む場所とします。

  • 必要な Envoy 設定を生成される istio-proxy 設定に注入できるよう、アプリケーション用の Istio EnvoyFilter を追加します。

Envoy gRPC サービスとして Next-Gen WAF エージェントを追加する

...
containers:
# Example helloworld app running on port 8000 without sigsci configured
- name: helloworld
image: signalsciences/example-helloworld:latest
imagePullPolicy: IfNotPresent
args:
# Address for the app to listen on
- localhost:8080
ports:
- containerPort: 8080
# Next-Gen WAF agent running in Envoy gRPC mode (SIGSCI_ENVOY_GRPC_ADDRESS configured)
- name: sigsci-agent
image: signalsciences/sigsci-agent:latest
imagePullPolicy: IfNotPresent
# Configure the agent to use Envoy gRPC on port 9999
env:
- name: SIGSCI_ACCESSKEYID
valueFrom:
secretKeyRef:
# This secret needs added (see docs on sigsci secrets)
name: sigsci.my-site-name-here
key: accesskeyid
- name: SIGSCI_SECRETACCESSKEY
valueFrom:
secretKeyRef:
# This secret needs added (see docs on sigsci secrets)
name: sigsci.my-site-name-here
key: secretaccesskey
# Configure the Envoy to expect response data (if using a gRPC access log config for Envoy)
- name: SIGSCI_ENVOY_EXPECT_RESPONSE_DATA
value: "1"
# Configure the Envoy gRPC listener address on any unused port
- name: SIGSCI_ENVOY_GRPC_ADDRESS
value: localhost:9999
ports:
- containerPort: 9999
securityContext:
# The sigsci-agent container should run with its root filesystem read only
readOnlyRootFilesystem: true

Deployment に Next-Gen WAF エージェント用の一時ボリュームの定義を追加する

エージェント用の一時ボリュームは、Pod 内の他のコンテナから利用できるよう、組み込みの emptyDir: {} ボリュームタイプを使用して定義する必要があります。

...
volumes:
# Define a volume where sigsci-agent will write temp data and share the socket file,
# which is required with the root filesystem is mounted read only
- name: sigsci-tmp
emptyDir: {}

必要な Envoy 設定を Istio プロキシに注入するための Istio EnvoyFilter オブジェクトを追加する

Istio の EnvoyFilter オブジェクトは、istio-proxy 向けに Envoy の設定を柔軟にカスタマイズできる仕組みです。

以下の例では、EnvoyFilter metadata.name フィールドおよび spec.workloadSelector.labels.app フィールドを、対象となるアプリケーション名に設定する必要があります。追加の Envoy 設定オプションについては、Envoy のインストールガイドを参照してください。これらのセクションは、サンプル YAML 内でコメントとして示されています。

example-helloworld_sigsci-envoyfilter.yaml:

# The following adds the required Envoy configuration into the istio-proxy configuration
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
# This needs adjusted to be the app name protected by sigsci
name: helloworld
spec:
workloadSelector:
labels:
# This needs adjusted to be the app name protected by sigsci
app: helloworld
# Patch the Envoy configuration, adding in the required sigsci config
configPatches:
# Adds the ext_authz HTTP filter for the sigsci-agent ext_authz API
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
name: virtualInbound
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_BEFORE
value:
# Configure the ext_authz filter here:
name: envoy.filters.http.ext_authz
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz"
transport_api_version: "V3"
grpc_service:
# NOTE: *SHOULD* use envoy_grpc as ext_authz can use dynamic clusters and has connection pooling
envoy_grpc:
cluster_name: sigsci-agent-grpc
timeout: 0.2s
failure_mode_allow: true
with_request_body:
max_request_bytes: 8192
allow_partial_message: true
# Adds the access_log entry for the sigsci-agent http_grpc_access_log API
- applyTo: NETWORK_FILTER
match:
context: SIDECAR_INBOUND
listener:
name: virtualInbound
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: "envoy.filters.network.http_connection_manager"
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
access_log:
# Configure the envoy.http_grpc_access_log here:
- name: "envoy.http_grpc_access_log"
typed_config:
"@type": "type.googleapis.com/envoy.extensions.access_loggers.grpc.v3.HttpGrpcAccessLogConfig"
common_config:
log_name: "sigsci-agent-grpc"
transport_api_version: "V3"
grpc_service:
# NOTE: *MUST* use google_grpc as envoy_grpc cannot handle a dynamic cluster for ALS (yet)
google_grpc:
# The address *MUST* be 127.0.0.1 so that communication is intra-pod
# Configure the sigsci-agent port number here:
target_uri: 127.0.0.1:9999
stat_prefix: "sigsci-agent"
timeout: 0.2s
additional_request_headers_to_log:
# These are required:
- "x-sigsci-request-id"
- "x-sigsci-waf-response"
# These are additional you want recorded:
- "accept"
- "content-type"
- "content-length"
additional_response_headers_to_log:
# These are additional you want recorded:
- "date"
- "server"
- "content-type"
- "content-length"
# Adds a dynamic cluster for the sigsci-agent via CDS for sigsci-agent ext_authz API
- applyTo: CLUSTER
patch:
operation: ADD
value:
name: sigsci-agent-grpc
type: STRICT_DNS
connect_timeout: 0.5s
http2_protocol_options: {}
load_assignment:
cluster_name: sigsci-agent-grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# The address *MUST* be 127.0.0.1 so that communication is intra-pod
address: 127.0.0.1
# Configure the agent port here:
port_value: 9999

その後、Istio を使用して通常どおりアプリケーションをデプロイできます。例 :

$ istioctl kube-inject -f example-helloworld-sigsci.yaml | kubectl apply -f -
service/helloworld created
deployment.apps/helloworld created
$ kubectl apply -f example-helloworld-sigsci_envoyfilter.yaml
envoyfilter.networking.istio.io/helloworld created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
helloworld-7954bb57bc-pfr22 3/3 Running 2 33s
$ kubectl get pod helloworld-7954bb57bc-pfr22 -o jsonpath='{.spec.containers[*].name}'
helloworld sigsci-agent istio-proxy
$ kubectl logs helloworld-7954bb57bc-pfr22 sigsci-agent | head
2019/10/01 21:04:57.540047 Signal Sciences Agent 4.39.0 starting as user sigsci with PID 1, Max open files=1048576, Max data size=unlimited, Max address space=unlimited, Max stack size=8388608
2019/10/01 21:04:57.541987 =====================================================
2019/10/01 21:04:57.542028 Agent: helloworld-7954bb57bc-pfr22
2019/10/01 21:04:57.542034 System: alpine 3.9.4 (linux 4.9.184-linuxkit)
2019/10/01 21:04:57.542173 Memory: 1.672G / 3.854G RAM available
2019/10/01 21:04:57.542187 CPU: 6 MaxProcs / 12 CPU cores available
2019/10/01 21:04:57.542257 =====================================================
2019/10/01 21:04:57.630755 Envoy gRPC server on 127.0.0.1:9999 starting

Pod 内では、以下の3つのコンテナが実行されている点に注意してください : app=helloworldsigsci-agentistio-proxy