NGINX Ingress Controllerの柔軟なアプリケーション制御、具体的なユースケースと設定方法を理解する

2021年9月22日(水)
松本 央

    高度な攻撃からWebアプリケーションを守る
    NGINX App Protect

    最後に外部からの攻撃に備え、Kubernetesで動作するアプリケーション、ひいてはプラットフォーム全体をセキュアに保つ方法について見てみましょう。昨今、WebアプリケーションはHTTP・HTTPSをベースにWebページやファイルだけでなく様々なデータをやり取りする仕組みとして活用されるようになりました。インターネット上にWebアプリケーションを公開することで大変便利な機能を提供すること可能ですが、それは同時に正常なユーザに扮した悪意あるユーザを招き入れることになります。我々はサービスを公開すると同時に高いセキュリティを効率的に実現することが当たり前に求められる状況となっているのです。

    それでは実際の環境での手順を見てみましょう。対象となるGitHubのフォルダへ移動します。ブラウザでGitHub上の手順ご覧になられる場合にはWAFを参照してください。

    # cd ~/kubernetes-ingress/examples-of-custom-resources/waf/

    防御対象となるWebアプリケーションと、WAFのセキュリティログを転送するSyslogサーバをデプロイします。

    # kubectl apply -f webapp.yaml
    # kubectl apply -f syslog.yaml

    WAFで指定するSignature定義とセキュリティログポリシーをデプロイします。

    # kubectl apply -f ap-apple-uds.yaml
    # kubectl apply -f ap-dataguard-alarm-policy.yaml

    WAFに適用するセキュリティポリシーを変更します。Syslogの転送先としてIPアドレスを指定します。8行目をお客様の環境に合わせて適切に指定してください。

    # kubectl get svc | grep syslog
    syslog-svc   ClusterIP   10.105.40.208           514/TCP   48m
    
    # vi waf.yaml
    ** 省略 **
    waf:
        ** 省略 **
        logDest: "syslog:server=10.105.40.208:514"
    
    # kubectl apply -f ap-logconf.yaml

    WAFのポリシー、VirtualServerをデプロイします。

    # kubectl apply -f waf.yaml 
    # kubectl apply -f virtual-server.yaml

    作成したアプリケーションに対し疎通を確認したいと思います。WAFはすべての通信を対象にログを出力するように設定しております。Syslogサーバのログを画面に出力しながら、Curlコマンドで通信をテストしたいと思います。

    Syslogサーバのログを以下コマンドでターミナルに継続して出力する状態とします。4行目をお客様の環境に合わせて適切に指定してください。

    # kubectl get pods | grep syslog
    syslog-76c65fd9b9-frthw   1/1     Running   0          19h
    
    # kubectl exec -it syslog-76c65fd9b9-frthw -- tail -f /var/log/messages

    正常な通信を確認するため、「http://webapp.example.com/」を宛先に指定しCurlコマンドを実行します。

    # curl "http://webapp.example.com/"
    Server address: 10.244.0.17:8080
    Server name: webapp-7c6d448df9-k2cfd
    Date: 25/Aug/2021:05:32:55 +0000
    URI: /
    Request ID: a81af16a33b381d758e1dc1eb18bdcd0

    正しく接続することができました。Syslogサーバに接続しているターミナルには以下が出力されます。

    ログメッセージを見ると、通信をブロックせず転送(PASSED)していることが確認できます。NGINX App ProtectはBot Signatureの機能をもっておりますので、curlコマンドであることを“人によるブラウザの通信ではなくBot Clientである”という形で検知をしておりますが、即座に驚異であると判断される設定となっておりませんので適切な通信としてWebアプリケーションへ転送が行われております。

    次にクロスサイトスクリプティング(XSS)を想定した接続をします。Security Policyで攻撃を検知した場合には通信を拒否する設定となっておりますので、正しくブロックされることを確認します。今回はURLに「<script>」を追加し、通信を確認します。「http://webapp.example.com/<script>」を宛先に指定しCurlコマンドを実行します。

    # curl "http://webapp.example.com/<script>"
    <html><head><title>Request Rejected</title></head><body>The requested URL was rejected. Please consult with your administrator.<br><br>Your support ID is: 5115561320371363623<br><br><a href='javascript:history.back();'>[Go Back]</a></body></html>

    正しく通信がブロックされました。デフォルトのブロックページにSupport IDが出力されております。またSyslogサーバに接続しているターミナルには以下が出力されます。ブロックページのSupport IDとして表示された文字列がログのsupport_idの内容と一致しており、通信とログメッセージを一意に紐付けることができます。

    ログメッセージを見ると、URLに不正な文字列が含まれており、XSS script tag(URI)などのSignatureで検知、通信をブロック(REJECTED)していることが確認できます。

    参考の情報ですが、Curlコマンドの「<script>」を「?a=a?%27+OR+1=1--」などの文字列に入れ替えると、SQL Injectionのブロックを見ることができますのでご確認ください。

    次にUser Defined Signatureで指定した内容が正しく動作しているか確認します。Webアプリケーションに”apple”という文字を送信します。

    # curl -X POST -d "apple" "http://webapp.example.com/"
    Request RejectedThe requested URL was rejected. Please consult with your administrator.

    Your support ID is: 5115561320371365663

    [Go Back]r

    正しく通信がブロックされました。Syslogサーバに接続しているターミナルには以下が出力されます。

    ログメッセージを見ると、該当のログメッセージが、User Defined Signatureの“Apple_medium_acc”というSignature Nameで検知されブロック(REJECTED)されていることが確認できます。

    現在の状態は以下の図となります(図22)。

    図22:NGINX App Protectサンプルデプロイ後の構成

    今回の実装ではdefault namespaceに、WAFに必要となるリソースを実装しています。SyslogサーバはNGINX Ingress Controllerから内部ネットワークを通じて接続するため、外部への公開は行っていません。WebアプリケーションはWAFのセキュリティポリシーを参照し、VirtualServerの公開設定を行っています。これにより外部からWebアプリケーション宛のアクセスはセキュリティポリシーの内容に従って制御される構成となります。

    それでは、最後に作成したアプリケーションを削除します。

    # kubectl delete -f webapp.yaml
    # kubectl delete -f syslog.yaml
    # kubectl delete -f ap-apple-uds.yaml
    # kubectl delete -f ap-dataguard-alarm-policy.yaml
    # kubectl delete -f waf.yaml 
    # kubectl delete -f virtual-server.yaml

    おわりに

    本稿ではNGINX Plusが提供する様々な機能を用いて柔軟かつ簡単にアプリケーションに合わせた外部へのサービス公開方法を紹介しました。本番環境でアプリケーションを展開するためにはコンテナでアプリケーションを動作させるだけでなく、高い安定性やセキュリティを実現する必要があり、更に多様化するアプリケーションのニーズに合わせた通信制御が必要となります。

    ぜひともこの記事を参考にNGINX Ingress Controllerの柔軟なアプリケーション制御を試していただけますと幸いです。

    【補足】

    事象の切り分け方法

    各パートでKubernetes環境にデプロイしたアプリケーションの動作が期待した通りとならない場合の切り分けについてご紹介します。

    1. 各リソースのデプロイに失敗する
      ・「kubectl describe <resource type> <resource name> (-n <namespace>)」コマンドの結果を確認し、リソースのデプロイでエラーが発生していないか確認してください
      ・「kubectl logs <pod name> (-n <namespace>)」コマンドの結果を確認し、PODのログメッセージを確認し、エラーが発生していないか確認してください
    2. リソースは正しく作成できたが設定が反映されない
      ・1.の内容を参考にコマンドを実行してください
      ・特にリソースが正しく生成された場合でも、NGINXのコンフィグロードでエラーとなる場合があります。その場合には、「kubectl logs」コマンドでコンフィグロードに失敗する理由が表示されておりますのでそちらでご確認ください
      ・一度設定の反映に成功し、その後リソースの内容変更によりエラーが発生した場合には、リソースの変更前の設定で動作いたします。注意深くログを確認し、問題箇所を特定してください
    3. 期待した疎通ができない
      ・「kubectl logs <NGINX Ingress Controller pod> -n nginx-ingress」コマンドの結果を確認し、Access LogとError Logの内容を確認してください
      ・NGINX Ingress Controllerで通信を受け付けた場合には以下のようにログが出力されます。ログが出力されること、ログの内容をもとに期待した通信となっているか確認してください
      10.244.0.1 - - [24/Aug/2021:14:30:51 +0000] "GET / HTTP/1.1" 500 178 "-" "curl/7.68.0" "-"
      10.244.0.1 - - [25/Aug/2021:05:47:17 +0000] "POST / HTTP/1.1" 200 246 "-" "curl/7.68.0" "-"
      10.244.0.1 - - [25/Aug/2021:05:47:09 +0000] "POST / HTTP/1.1" 200 246 "-" "curl/7.68.0" "-"
    4. 設定の内容を確認したい
      ・NGINX Ingress Controllerは本書で紹介のVirtualServer / VirtualServerRoute / PolicyやIngressにより設定を行うと、それらの内容からNGINXの設定ファイルに変換し、その内容を反映しています
      ・「kubectl exec -it <NGINX Ingress Controller pod> -n nginx-ingress」 -- bash」コマンドを実行し、NGINX Ingress ControllerのShellを操作することが可能です
      ・以下フォルダの各ファイルが適切な設定となっているかご確認ください。意図したファイルが生成されていない場合にはリソースの作成に失敗している可能性がありますのでログをご確認ください。
      /etc/nginx/conf.d/ HTTP/HTTPSの主な設定が格納されます。複数のVirtualServerをデプロイした場合には複数のファイルが生成されます。
      ■生成されるファイル名
      vs_<namespace>_<object name>.conf
      /etc/nginx/stream-conf.d/ TCP/UDPの主な設定が格納されます。複数のTransportServerをデプロイした場合には複数のファイルが生成されます。合わせて必要となるGlobalConfigurationの作成も完了していることを確認してください。
      ■生成されるファイル名
      ts_<namespace>_<object name>.conf
      /etc/nginx/secrets/ 証明書・鍵のファイルが格納されます。複数のSecretをデプロイした場合には複数のファイルが生成されます。参照先のオブジェクトの生成が成功した際に、本ファイルが生成されます。
      ■生成されるファイル名
      <namespace>-<object name>
      /etc/nginx/waf/nac-policies/ WAFのセキュリティポリシーが格納されます。複数のAPPolicyをデプロイした場合には複数のファイルが生成されます。
      ■生成されるファイル名
      <namespace>_<object name>
      /etc/nginx/waf/nac-logconfs/ WAFのログポリシーが格納されます。複数のAPLogConfをデプロイした場合には複数のファイルが生成されます。ログポリシーの参照先となるWAFセキュリティポリシーの生成が成功した際に、本ファイルが生成されます。
      ■生成されるファイル名
      <namespace>_<object name>
      /etc/nginx/waf/nac-usersigs/ WAFのユーザ定義Signatureが格納されます。複数のAPUserSigをデプロイした場合には複数のファイルが生成されます。ログポリシーの参照先となるWAFセキュリティポリシーの生成が成功した際に、本ファイルが生成されます。
      ■生成されるファイル名
      <namespace>_<object name>
      /etc/nginx/oidc/ OIDCで参照するファイルが格納されています。

    OIDC KeycloakのAPI操作について

    OIDCで利用するKeycloakですがGUIを利用せずAPIで設定することが可能です。KeyacloakのAPIを利用した設定方法はGitHub上のKeycloak Setupに記載されています。

    APIではJSON形式のデータをやり取りします。予めjqコマンドをインストールしてください。

    まずKeycloakにアクセスするため、クライアント端末のhostsファイルで適切にアクセスできるよう設定してください。

    # grep host virtual-server-idp.yaml
    virtual-server-idp.yaml:  host: keycloak.example.com

    APIより操作するためAccess Tokenを取得します。Access Tokenの有効期限は大変短く設定されております(60秒)。ご注意ください。

    APIで利用するAccess Tokenの取得
    # TOKEN=`curl -sS -k --data "username=admin&password=admin&grant_type=password&client_id=admin-cli" https://keycloak.example.com/auth/realms/master/protocol/openid-connect/token | jq -r .access_token`
    
    nginx-userというユーザ名を持つユーザの作成
    # curl -sS -k -X POST -d '{ "username": "nginx-user", "enabled": true, "credentials":[{"type": "password", "value": "test", "temporary": false}]}' -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://keycloak.example.com/auth/admin/realms/master/users
    
    nginx-userの設定追加及びSECRETの取得
    # SECRET=`curl -sS -k -X POST -d '{ "clientId": "nginx-plus", "redirectUris": ["https://webapp.example.com/_codexch"] }' -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://keycloak.example.com/auth/realms/master/clients-registrations/default | jq -r .secret`
    
    取得したSECRETの確認
    # echo $SECRET
    ***SECRET***

    Keycloakの設定作業で正しく値が取得できない場合、以下コマンドを参考に切り分けを行い、適切にSECRETの取得が完了することを確認してください。

    指定の管理者ユーザ名、パスワードを用いて正しくTokenが取得できることを確認してください。APIではこちらのAccessTokenを利用します。
    # curl -sS -k --data "username=admin&password=admin&grant_type=password&client_id=admin-cli" https://keycloak.example.com/auth/realms/master/protocol/openid-connect/token | jq .
    {
      "access_token": "***ACCESS TOKEN***",
      "expires_in": 60,
      "refresh_expires_in": 1800,
      "refresh_token": "***REFRESH TOKEN***",
      "token_type": "Bearer",
      "not-before-policy": 0,
      "session_state": "f1cc69a8-2239-424a-9e48-f73ab29ef931",
      "scope": "profile email"
    }
    
    ユーザ情報を取得。管理者及び新規に作成する「nginx-plus」というユーザが表示されることを確認してください
    # curl -sS -k -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://keycloak.example.com/auth/admin/realms/master/users | jq .
    [
      {
        "id": "3d0cf453-5d72-494f-9e8f-f3c3aa8e279b",
        "createdTimestamp": 1629247264332,
        "username": "admin",
        "enabled": true,
        "totp": false,
        "emailVerified": false,
        "disableableCredentialTypes": [],
        "requiredActions": [],
        "notBefore": 0,
        "access": {
          "manageGroupMembership": true,
          "view": true,
          "mapRoles": true,
          "impersonate": true,
          "manage": true
        }
      },
      {
        "id": "4fd6d403-db91-4d84-9235-84a418a5b8ef",
        "createdTimestamp": 1629704326796,
        "username": "nginx-user",
        "enabled": true,
        "totp": false,
        "emailVerified": false,
        "disableableCredentialTypes": [],
        "requiredActions": [],
        "notBefore": 0,
        "access": {
          "manageGroupMembership": true,
          "view": true,
          "mapRoles": true,
          "impersonate": true,
          "manage": true
        }
      }
    ]
    
    登録されたクライアント情報を確認し、必要となる情報を確認してください。NGINX OIDCの設定で規定した動作とならない場合にはこちらに表示されるパラメータを参照し、確認を行ってください。
    # curl -sS -k -X GET -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://keycloak.example.com/auth/realms/master/clients-registrations/default/nginx-plus | jq .
    {
      "id": "32f41851-f0f7-4259-af19-68e11b5bb10c",
      "clientId": "nginx-plus",
      "surrogateAuthRequired": false,
      "enabled": true,
      "alwaysDisplayInConsole": false,
      "clientAuthenticatorType": "client-secret",
      "secret": "***SECRET***",
      "redirectUris": [
        "https://webapp.example.com:443/_codexch"
      ],
      "webOrigins": [
        "https://webapp.example.com:443"
      ],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "serviceAccountsEnabled": false,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {},
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": true,
      "nodeReRegistrationTimeout": -1,
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    }

    Keycloakで誤った情報、意図しない情報でオブジェクトを作成した場合には以下コマンドを参考に削除してください。

    ・ユーザの削除
    # curl -sS -k -X DELETE -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://keycloak.example.com/auth/admin/realms/master/users/ | jq .
    
    ・クライアントの削除
    # curl -sS -k -X DELETE -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://keycloak.example.com/auth/realms/master/clients-registrations/default/ | jq .
NGINX テクニカルソリューションズアーキテクト
ISPでネットワークエンジニアとしてキャリアをスタート。ネットワークのコアとして様々なプロトコルを扱うことに楽しみを感じF5ネットワークスジャパンでBIG-IPのコンサルタントやソリューションエンジニアとしての経験を経て、NGINX テクニカルソリューションズアーキテクトに。NGINXの紹介・技術支援を通じてクラウドネイティブで安定したインフラ環境の実現のため日々奮闘中。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています