nginxとS3を使って静的コンテンツを利用者を限定した形で利用できるようにする

2018.06.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

久しぶりのブログになりました。齋藤です。 今日はS3に関するブログです。

はじめに

今回はS3をホストしているファイルに対してアクセス出来る人を制限したい、という話です。

今回は以下の内容で説明していきます。

  • S3バケットを作る
  • S3に対して VPC エンドポイントを作成する
  • S3バケットにバケットポリシーを設定して、VPC内のみからのアクセスに制限する
  • EC2を起動して nginx を起動させる。
  • EC2のセキュリティグループを設定する
  • nginxでbasic認証を設定する

今回はVPCはデフォルトのVPCを使いました。 必要に応じて、VPCのセットアップを行ってください。

S3バケットを作る

とりあえずデフォルトで作りましょう。今回はマネージメントコンソールから作りました。

バケットの作成をやっていきます。

以下のように適切なバケット名をつけて作成します。(適切な設定をしてダイアログを進めてください。)

一応ここでは確認のために 確認ダイアログを表示しています。

これで、ダイアログからS3バケットの作成が出来ました。

S3に対して VPC エンドポイントを作成する

今回はマネージメントコンソールから作りました。 以下の流れで作成していきます。

  • マネージメントコンソールから Amazon VPCのダッシュボードを開く
  • ダッシュボードからEndpointの画面を開く
  • エンドポイントの作成からS3のVPCエンドポイントを作成する

Amazon VPCのダッシュボード画面から以下のEndpointの画面を開きます。

Endpointの画面からエンドポイントの作成をしていきます。 下の画像のように、ボタンをクリックするとVPCエンドポイントを作成するウィザードが開きます。

今回はS3に対してVPC エンドポイントを作成するので S3を選択しておきます。

VPCエンドポイントの作成に合わせて、ルートテーブルの設定も行います。

今回はこのポリシーの設定部分はデフォルトの権限設定にしておきます。 この部分は運用要件に従って適切に設定してください。

これで、VPCエンドポイントの作成は終了です。

以下のブログも参考にしました。

【新機能】S3がVPCのプライベートサブネットからアクセス可能になりました!

S3バケットにバケットポリシーを設定して、VPC内のみからのアクセスに制限する

ここではVPCレベルでの制限にしておきます。 IPアドレスで制限を掛けなかった理由としては、後述するnginxでBasic認証を掛けたいので nginxの前にELBを建てた時やECSなどにnginxを載せた時のことを考えると nginxの立っているリソースに依存したくなかったからです。 他に良い方法があったらコメントで教えていただけると助かります。

また、VPCエンドポイントレベルで制限を掛けても動くとは思います。 今回はバケットにもポリシーを当てているので 動きとしては変わりません。 以下のポリシーを適用します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<your-bucket>/*",
            "Condition": {
                "StringEquals": {
                    "aws:sourceVpc": "<your-vpc>"
                }
            }
        }
    ]
}

今回はマネージメントコンソールから設定しました。設定方法は以下の流れです。

  • S3の設定画面を開く
  • 設定したいバケットを開く
  • バケットの権限設定のタブをクリックする
  • バケットポリシーの設定画面を開いてポリシーを適用する

画像付きで説明します。

マネージメントコンソールからバケットをクリックします。 バケットの詳細な画面が表示されます。

次に権限設定のタブを開きます。

次にバケットポリシーの設定画面を開いてポリシーを適用します。 その後、保存してください。

これでバケットポリシーの設定は終了です。

また、作業している自分のPCからアクセスしてアクセス出来ないことを確認しておきましょう。 今回は/sql.mdのオブジェクトキーにmarkdownファイルを置いておきました。 これ以降はその想定で話を進めます。

作業端末からcurlとブラウザからアクセス出来ないことを確認しておきます。

$ curl https://s3-<your-region>.amazonaws.com/<your-bucket>/sql.md; # 手元のPCからだと Access Deniedが帰ってくるはず

ブラウザからアクセスしてみた場合 以下のような画面が表示されると思います。

EC2を起動して nginx を起動させる。

まずは nginxを起動するEC2の起動をしましょう。 sshで起動したEC2にログインします。

もちろん、VPCエンドポイントを設定したVPCにEC2を配置してください。 今回はデフォルトで用意されているVPCにEC2を配置しています。

ここは画像で流します。

ダッシュボードからインスタンスの作成を行ってウィザードを開きます。

AMIはAmazon Linuxを選択しました。

インスタンスタイプはひとまずt2.microにしておきます。

設定はデフォルト設定にしておきます。 PublicIPが割り当てられるように設定を確認しておきましょう。

あとで削除するので、判別しやすさのためにタグを設定しておきました。 この辺は運用要件に従って適切にタグを付与しましょう。

ここで今回は自分のIPアドレスからのアクセスを制限するために TCPの8080ポートをMy IPで設定しています。

今回はセットアップもsshで入って行うので sshのポートも一緒に開けています。 sshのポートを開けるかどうかは運用要件に従って決めましょう。(踏み台からのみsshアクセス可などの運用が考えられます。)

インスタンス設定を確認してから インスタンスを立ち上げましょう。

キーペアの設定は適切に確認、DL等してください。

これで、EC2インスタンスの起動が出来たはずです。

nginxの設定をする前に S3のバケットに設定したバケットポリシーによって 起動したEC2からS3へのアクセスが可能か調べておきましょう。 sshのポートを開けているので、そのまま立ち上げたEC2インスタンスに入って curlでs3にアクセスしてみましょう。

$ curl https://s3-<your-region>.amazonaws.com/<your-bucket>/sql.md; # 起動したEC2からだとアクセスできる

では、nginxのセットアップをしていきます。

$ sudo yum install nginx -y

設定を弄っておきます。

$ sudo vim /etc/nginx/nginx.conf

以下をベースに、を置き換えて設定としてファイルに保存しました。

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    server {
        listen       8080 default_server;
        listen       [::]:8080 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_pass      https://s3-<your-region>.amazonaws.com/<your-bucket>/;
            proxy_intercept_errors on;
        }

        # redirect server error pages to the static page /40x.html
        #
        error_page 403 404 /40x.html;
            location = /40x.html {
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

        # healthcheck用のエンドポイントも追加しておきました。
        location /health {
            empty_gif;
            access_log off;
            break;
        }
    }
}

nginxを起動しておきましょう。

$ sudo service nginx start

これで、S3へのリバースプロキシ単体が完成しました。

一応確認のため、S3へアクセス出来るか確認しておきましょう。 出来ない場合、バケットポリシーやVPCエンドポイントの有無などを確認してください。

$ curl http://localhost:8080/sql.md # EC2に建てたnginxに向けてcurlでアクセスするとS3に置いているファイルが見えるはず。

動作確認してみます

EC2の立ち上げ時にセキュリティグループの設定を追加しているので 作業しているPCから EC2のPublic IPの8080ポートにアクセスして S3に置いているファイルが返却されるか確認してみましょう。

最初はcurlから。

$ curl http://<your-ec2-public-ip>:8080/sql.md # 作業しているPCから

今度はブラウザから http://<your-ec2-public-ip>/sql.md を開いて確認してみましょう。 セキュリティグループを設定しているので、ブラウザからS3に置いているファイルが見えるはずです。 見えない場合は、セキュリティグループの設定やEC2のIPアドレス、などのEC2周りの設定を疑ってみてください。

nginxでbasic認証を設定する

ここまでで nginxのセットアップは大体終わりましたが おまけでBasic認証のセットアップをしてみましょう。

sudo htpasswd -c /etc/nginx/.htpasswd admin # ユーザーのセットアップ
# パスワードを対話的に入力する

また nginxの設定ファイルを編集します。

$ sudo vim /etc/nginx/nginx.conf

注意すべきは S3へのreverse proxyとして設定している部分で Authorizationヘッダを上書きしていることです。 これをしないとS3側で認証のヘッダと勘違いされてしまいます。

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    auth_basic "Restricted"; # 追加
    auth_basic_user_file /etc/nginx/.htpasswd; #追加

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    server {
        listen       8080 default_server;
        listen       [::]:8080 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_set_header Authorization ""; # 追加。これを追加しないと Basic認証のヘッダがS3の方に飛んでいってしまう。
            proxy_pass      https://s3-ap-northeast-1.amazonaws.com/nginx-test-7b0286d6-174b-43ff-8149-5e4055f8ab38/;
            proxy_intercept_errors on;
        }

        # redirect server error pages to the static page /40x.html
        #
        error_page 403 404 /40x.html;
            location = /40x.html {
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

        # healthcheck用のエンドポイントも追加しておきました。
        location /health {
            empty_gif;
            access_log off;
            break;
        }
    }
}

設定ファイルを書き換えた後は nginxを再起動しておきましょう。

$ sudo service nginx restart

これでBasic認証をやりつつIPアドレスで制限することができました。 以下のような形でアクセスするとS3に置いているコンテンツが返却されるのが確認できるはずです。

# 作業している自分のPCから
$ curl http://<your-ec2-public-ip>/sql.md # Basic認証のヘッダがないと見れない(上記設定例だと404)
$ curl -u $USER:$PASSWORD http://<your-ec2-public-ip>/sql.md # 見えるはず

まとめ

今回初めて触ったものが多く、大変勉強になりましたが プライベートな静的ファイルのホストはめちゃくちゃ簡単に出来そうです。

Lambdaだけでも出来そうですが nginxのセットアップをしてみたかったということもあり こんな感じの記事になりました。

今度はECSやLambdaでアクセス制限を掛けるような構成を検討してみます。

また、今回は1台だけなので利用しませんでしたが EC2インスタンスをALBに紐づけて、ALBのセキュリティグループの設定でIPアドレスの制限をすることも可能です。 高い負荷が予想される場合などにご検討ください。

加えて、今回のEC2インスタンスにはElatic IPを付与していないので EC2の再起動をした場合にPublic IPが変わってしまうので 運用要件に合わせて適切に設定をしてください。

それではまた次の記事でお会いしましょう。