[ English | Indonesia | Deutsch | 日本語 ]

Object Storage (Swift) ミドルウェアのカスタマイズ

OpenStack Object Storage, known as swift when reading the code, is based on the Python Paste framework. The best introduction to its architecture is on Read The Docs. Because of the swift project's use of this framework, you are able to add features to a project by placing some custom code in a project's pipeline without having to change any of the core code.

お使いのコンテナーの 1 つにパブリックにアクセスできるシナリオを想像してください。しかし、本当にやりたいことは、ホワイトリストに基づいてアクセスできる IP を制限することです。この例では、コンテナーのメタデータ項目により決められるよう、ある IP アドレス群だけからコンテナーにアクセスを許可する、swift 向けのミドルウェア部品を作成します。コンテナーのメタデータを使用して、明示的にホワイトリストに入っている IP アドレスのみが、コンテナーにアクセスできます。

警告

この例は実証目的のみのためにあります。さらなる作りこみと広範なセキュリティテストなしにコンテナのIPホワイトリスト・ソリューションとして使用するべきではありません。

stack.shscreen -r stack で作成したセッションに join すると、動作中の各サービスのスクリーンを参照できます。これは、DevStack が実行するよう設定したサービスの数に依存して、いくつかあるでしょう。

アスタリスク (*) は、表示している screen ウィンドウを表しています。この例は、keystone 用の key という screen ウィンドウを表示していることを表しています。

0$ shell  1$ key*  2$ horizon  3$ s-proxy  4$ s-object  5$ s-container  6$ s-account

screen ウィンドウの目的は、以下のとおりです。

shell

作業を行うためのシェル

key*

keystone サービス

horizon

horizon dashboard Web アプリケーション

s-{name}

swift サービス

ミドルウェアを作成して Paste の環境設定を通して組み込むためには:

すべての OpenStack のコードは /opt/stack にあります。 shell セッションの screen の中で swift ディレクトリに移動し、あなたのミドルウェアモジュールを編集してください。

  1. Object Storage がインストールされるディレクトリーを変更します。

    $ cd /opt/stack/swift
    
  2. ip_whitelist.py Python ソースコードファイルを作成します。

    $ vim swift/common/middleware/ip_whitelist.py
    
  3. 以下の示すコードを ip_whitelist.py にコピーします。以下のコードは、このセクションの初めに説明されたように、IP アドレスに基づいてコンテナーへのアクセスを制限するミドルウェアの例です。ミドルウェアは、他のアプリケーションへのリクエストを通過させます。この例は、swift "swob" ライブラリーを使用して、swift が通信するオブジェクトに関する Web Server Gateway Interface (WSGI) のリクエストとレスポンスをラップします。これを実行したとき、ファイルを保存して閉じます。

    # vim: tabstop=4 shiftwidth=4 softtabstop=4
    # Copyright (c) 2014 OpenStack Foundation
    # All Rights Reserved.
    #
    #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    #    not use this file except in compliance with the License. You may obtain
    #    a copy of the License at
    #
    #         http://www.apache.org/licenses/LICENSE-2.0
    #
    #    Unless required by applicable law or agreed to in writing, software
    #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    #    License for the specific language governing permissions and limitations
    #    under the License.
    
    import socket
    
    from swift.common.utils import get_logger
    from swift.proxy.controllers.base import get_container_info
    from swift.common.swob import Request, Response
    
    class IPWhitelistMiddleware(object):
        """
        IP Whitelist Middleware
    
        Middleware that allows access to a container from only a set of IP
        addresses as determined by the container's metadata items that start
        with the prefix 'allow'. E.G. allow-dev=192.168.0.20
        """
    
        def __init__(self, app, conf, logger=None):
            self.app = app
    
            if logger:
                self.logger = logger
            else:
                self.logger = get_logger(conf, log_route='ip_whitelist')
    
            self.deny_message = conf.get('deny_message', "IP Denied")
            self.local_ip = socket.gethostbyname(socket.gethostname())
    
        def __call__(self, env, start_response):
            """
            WSGI entry point.
            Wraps env in swob.Request object and passes it down.
    
            :param env: WSGI environment dictionary
            :param start_response: WSGI callable
            """
            req = Request(env)
    
            try:
                version, account, container, obj = req.split_path(1, 4, True)
            except ValueError:
                return self.app(env, start_response)
    
            container_info = get_container_info(
                req.environ, self.app, swift_source='IPWhitelistMiddleware')
    
            remote_ip = env['REMOTE_ADDR']
            self.logger.debug("Remote IP: %(remote_ip)s",
                              {'remote_ip': remote_ip})
    
            meta = container_info['meta']
            allow = {k:v for k,v in meta.iteritems() if k.startswith('allow')}
            allow_ips = set(allow.values())
            allow_ips.add(self.local_ip)
            self.logger.debug("Allow IPs: %(allow_ips)s",
                              {'allow_ips': allow_ips})
    
            if remote_ip in allow_ips:
                return self.app(env, start_response)
            else:
                self.logger.debug(
                    "IP %(remote_ip)s denied access to Account=%(account)s "
                    "Container=%(container)s. Not in %(allow_ips)s", locals())
                return Response(
                    status=403,
                    body=self.deny_message,
                    request=req)(env, start_response)
    
    
    def filter_factory(global_conf, **local_conf):
        """
        paste.deploy app factory for creating WSGI proxy apps.
        """
        conf = global_conf.copy()
        conf.update(local_conf)
    
        def ip_whitelist(app):
            return IPWhitelistMiddleware(app, conf)
        return ip_whitelist
    

    envconf には、リクエストについて何をするのか判断するのに使える有用な情報が多数含まれています。どんなプロパティが利用可能なのかを知るには、以下のログ出力文を __init__ メソッドに挿入してください。

    self.logger.debug("conf = %(conf)s", locals())
    

    そして以下のログ出力分を __call__ メソッドに挿入してください。

    self.logger.debug("env = %(env)s", locals())
    
  4. このミドルウェアを swift Paste のパイプラインに組み込むには、設定ファイル /etc/swift/proxy-server.conf を編集します。

    $ vim /etc/swift/proxy-server.conf
    
  5. /etc/swift/proxy-server.conf[filter:ratelimit] セクションを探し、その後ろに以下の環境定義セクションを貼り付けてください。

    [filter:ip_whitelist]
    paste.filter_factory = swift.common.middleware.ip_whitelist:filter_factory
    # You can override the default log routing for this filter here:
    # set log_name = ratelimit
    # set log_facility = LOG_LOCAL0
    # set log_level = INFO
    # set log_headers = False
    # set log_address = /dev/log
    deny_message = You shall not pass!
    
  6. /etc/swift/proxy-server.conf [pipeline:main] セクションを探し、このように ip_whitelist リストを ratelimit の後ろに追加してください。完了したら、ファイルを保存して閉じてください。

    [pipeline:main]
    pipeline = catch_errors gatekeeper healthcheck proxy-logging cache bulk tempurl ratelimit ip_whitelist ...
    
  7. swift proxy にこのミドルウェアを使わせるために、Swift プロキシサービスを再起動します。swift-proxy の screen セッションに切り替えてはじめてください。

    1. Ctrl+A に続けて 3 を押します。

    2. Ctrl+C を押し、サービスを強制停止します。

    3. 上矢印キー を押し、最後のコマンドを表示させます。

    4. Enter キーを押し、実行します。

  8. swift``の CLI でミドルウェアのテストをしてください。shell screen セッションに切り替えてテストを開始し、 ``swift-proxy の screen セッションにもどってログ出力をチェックして終了します。

    1. Ctrl+A に続けて 0 を押します。

    2. devstack ディレクトリーにいることを確認します。

      $ cd /root/devstack
      
    3. openrc を読み込み、CLI の環境変数を設定します。

      $ . openrc
      
    4. middleware-test という名前のコンテナーを作成します。

      $ swift post middleware-test
      
    5. Ctrl+A に続けて 3 を押して、ログ出力を確認します。

  9. ログの中に以下の行があるでしょう。

    proxy-server Remote IP: my.instance.ip.address (txn: ...)
    proxy-server Allow IPs: set(['my.instance.ip.address']) (txn: ...)
    

    これらの2行は、このミドルウェアによって出力されており、リクエストが DevStack インスタンスから送られており、許可されていることを示しています。

  10. Test the middleware from outside DevStack on a remote machine that has access to your DevStack instance:

    1. ローカルマシンに keystoneswif クライアントをインストールします。

      # pip install python-keystoneclient python-swiftclient
      
    2. middleware-test コンテナーにあるオブジェクトを一覧表示しようとします。

      $ swift --os-auth-url=http://my.instance.ip.address:5000/v2.0/ \
        --os-region-name=RegionOne --os-username=demo:demo \
        --os-password=devstack list middleware-test
      Container GET failed: http://my.instance.ip.address:8080/v1/AUTH_.../
          middleware-test?format=json 403 Forbidden   You shall not pass!
      
  11. Ctrl+A に続けて 3 を押して、ログ出力を確認します。再び swift のログを確認すると、ログの中に以下の行があるでしょう。

    proxy-server Authorizing from an overriding middleware (i.e: tempurl) (txn: ...)
    proxy-server ... IPWhitelistMiddleware
    proxy-server Remote IP: my.local.ip.address (txn: ...)
    proxy-server Allow IPs: set(['my.instance.ip.address']) (txn: ...)
    proxy-server IP my.local.ip.address denied access to Account=AUTH_... \
       Container=None. Not in set(['my.instance.ip.address']) (txn: ...)
    

    ここで、リモートIPアドレスが、許可されたIPアドレスの中になかったため、リクエストが拒否されていることがわかります。

  12. シェル画面において DevStack 用インスタンスに戻り、リモートマシンからのリクエストを許可するようなコンテナのメタデータを追加します。

    1. Ctrl+A に続けて 0 を押します。

    2. メタデータをコンテナーに追加して、IP を許可します。

      $ swift post --meta allow-dev:my.local.ip.address middleware-test
      
    3. ここで手順 10 のコマンドを再び試みて、続行します。コンテナーにオブジェクトがありません。そのため、一覧には何もありません。しかしながら、レポートするエラーもありません。

      警告

      このような機能試験は、正しいユニットテストと結合テストの代わりになるものではありませんが、作業を開始することはできます。

Python Paste フレームワークを使う他のすべてのプロジェクトで、類似のパターンに従うことができます。単純にミドルウェアモジュールを作成し、環境定義によって組み込んでください。そのミドルウェアはプロジェクトのパイプラインの一部として順番に実行され、必要に応じて他のサービスを呼び出します。プロジェクトのコア・コードは一切修正しません。Paste を使っているプロジェクトを確認するには、 /etc/<project> に格納されている、プロジェクトの conf または ini 環境定義ファイルの中で pipeline 変数を探してください。

When your middleware is done, we encourage you to open source it and let the community know on the OpenStack mailing list. Perhaps others need the same functionality. They can use your code, provide feedback, and possibly contribute. If enough support exists for it, perhaps you can propose that it be added to the official swift middleware.