さくらのオブジェクトストレージを独自のドメイン経由且つプライベートなオブジェクトを取得する方法 

# TL;DR

`Lua-nginx-module` を利用することで、特別なバックエンドサーバを用意せずとも、プライベートなオブジェクトにアクセス可能になります。

事前に下記を用意して下さい。

* 対象となるオブジェクトを格納しているバケットへアクセス可能なアクセスキー
* 上記アクセスキーと対になるシークレットアクセスキー

アクセスキー、シークレットアクセスキーの発行は、さくらのオブジェクトストレージコンパネ＞パーミッション＞アクセスキーの発行 から行うことができます。

# Nginxのインストール

私はFreeBSDを利用しているので、パッケージ管理ツール等ではインストールせず、下記のようにnginxのソースコードをダウンロードし、ビルド及びインストールしました。
パッケージ管理ツールを利用する場合、少なくともlua mouduleの入ったnginxをインストールして下さい。

`config.sh` については後述します（必要に応じてオプションを修正して下さい）。

```
# 関連パッケージのダウンロード
doas git clone https://github.com/openresty/lua-nginx-module.git
doas git clone https://github.com/vision5/ngx_devel_kit.git
doas git clone https://github.com/nbs-system/naxsi.git 

# Nginxソースのダウンロード
doas wget http://nginx.org/download/nginx-1.21.4.tar.gz
doas tar -xzf nginx-1.21.4.tar.gz

# インストール
cd nginx-1.21.4/
doas sh config.sh
doas make && doas make install
```

`config.sh` はnginxのビルドに必要なオプションを自分なりに設定したのもので、下記にその内容を示します。
利用する環境に合わせて、オプションを変更して下さい。

```
#!/bin/sh

export LUAJIT_LIB=/usr/local/lib
# Not use at 20211215.
#export LUAJIT_INC=/usr/local/include/luajit-2.0
export LUAJIT_INC=/usr/local/include/luajit-2.1

./configure \
  --prefix=/usr/local/etc/nginx \
  --sbin-path=/usr/local/sbin/nginx \
  --modules-path=/usr/local/libexe/nginx \
  --conf-path=/usr/local/etc/nginx/nginx.conf \
  --error-log-path=/var/log/nginx/error.log \
  --http-log-path=/var/log/nginx/access.log \
  --pid-path=/var/run/nginx.pid \
  --lock-path=/var/run/nginx.lock \
  --http-client-body-temp-path=/var/tmp/nginx/client_body_temp \
  --http-proxy-temp-path=/var/tmp/nginx/proxy_temp \
  --http-fastcgi-temp-path=/var/tmp/nginx/fastcgi_temp \
  --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi_temp \
  --http-scgi-temp-path=/var/tmp/nginx/scgi_temp \
  --user=www \
  --group=www \
  --with-http_ssl_module \
  --with-openssl=/usr/local/misc/openssl \
  --with-http_realip_module \
  --with-http_addition_module \
  --with-http_sub_module \
  --with-http_dav_module \
  --with-http_flv_module \
  --with-http_mp4_module \
  --with-http_gunzip_module \
  --with-http_gzip_static_module \
  --with-http_random_index_module \
  --with-http_secure_link_module \
  --with-http_stub_status_module \
  --with-http_auth_request_module \
  --with-http_xslt_module=dynamic \
  --with-http_image_filter_module \
  --with-http_perl_module=dynamic \
  --with-threads \
  --with-stream \
  --with-stream_ssl_module \
  --with-stream_ssl_preread_module \
  --with-http_slice_module \
  --with-mail \
  --with-mail_ssl_module \
  --with-file-aio \
  --with-http_v2_module \
  --with-google_perftools_module \
  --with-cc-opt='-I /usr/local/include' \
  --with-ld-opt='-L /usr/local/lib' \
  --add-module=../ngx_devel_kit \
  --add-module=../lua-nginx-module \
  --add-module=../naxsi/naxsi_src \
  --add-module=../ngx_http_geoip2_module
```

# Nginxの設定

以下のようなconfファイル（ここでは `/usr/local/etc/ngixn/conf.d/objstorage.aintek.xyz.conf` とします）を作成し、nginxで読み込みます。

このファイルを利用する場合、下記の項目を修正する必要があります。

* `$aws_access_key` : アクセスキーの値を入力して下さい
* `$aws_secret_access_key` : シークレットアクセスキーの値を入力して下さい
* `$bucket` : さくらのオブジェクトストレージにあるバケット名を入力して下さい

```
map $sent_http_content_type $expires {
    "text/html"                 epoch;
    "text/html; charset=utf-8"  epoch;
    default                     off;
}

server {
    listen 80;
    server_name _;
    #ssl_certificate /etc/letsencrypt/live/objectstorage.aintek.xyz/fullchain.pem;
    #ssl_certificate_key /etc/letsencrypt/live/objectstorage.aintek.xyz/privkey.pem;

    access_log  /var/log/nginx/objectstorage.aintek.xyz_access.log;
    error_log   /var/log/nginx/objectstorage.aintek.xyz_error.log;

    gzip            on;
    gzip_types      text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location ~ /(.*) {
        resolver 8.8.8.8;
        expires $expires;

        proxy_redirect                      off;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout          1m;
        proxy_connect_timeout       1m;

        set $aws_access_key "AKIAIOSFODNN7EXAMPLE";
        set $aws_secret_access_key "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
        set $s3endpoint "s3.isk01.sakurastorage.jp"; # AWS: "s3.amazonaws.com", Sakura: "s3.isk01.sakurastorage.jp"
        set $bucket "ene97036-sakura-micro-community-with-webacc"; # Your bucket name
        set $region "jp-north-1"; # Bucket region
        set $key $1; # Object name

        # --- AWS spec
        # https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/API/sig-v4-header-based-auth.html#canonical-request
        # --- lua implementations
        # https://github.com/jobteaser/lua-resty-aws-signature/blob/master/lib/resty/aws-signature.lua
        # https://github.com/paragasu/lua-resty-aws-auth/blob/09cec6d94c572aa83f6ae96aa15c3f057a81030e/lib/resty/aws_auth.lua
        set_by_lua_block $a {
            local resty_sha256 = require 'resty.sha256'
            local hmac = require 'resty.hmac'
            local str  = require 'resty.string'
            
            local timestamp = tonumber(ngx.time()) 
            local date = os.date('!%Y%m%d', timestamp)
            local date_tz = os.date('!%Y%m%dT%H%M%SZ', timestamp)
            local canonical_uri = "/" .. ngx.var.key
            local canonical_querystring = ""
            
            -- Create a Hashed payload before Creating a Canonical request
            local h = resty_sha256.new()
            h:update(ngx.var.request_body or '')
            local hashed_payload = str.to_hex(h:final())
            -- Debug
            -- ngx.log(ngx.ERR, "HASHED_PAYLOAD: " .. hashed_payload)
            
            local canonical_headers = "host:" .. ngx.var.bucket .. "." .. ngx.var.s3endpoint .. "\n"
              .. "x-amz-content-sha256:" .. hashed_payload .. "\n"
              .. "x-amz-date:" .. date_tz .. "\n"
            -- Debug
            -- ngx.log(ngx.ERR, "CANONICAL HEADER: " .. canonical_headers)
            local signed_headers = "host;x-amz-content-sha256;x-amz-date"
             
            -- Create a Canonical request
            local canonical_request = ngx.var.request_method .. "\n"
              .. canonical_uri .. "\n"
              .. canonical_querystring .. "\n"
              .. canonical_headers .. "\n"
              .. signed_headers .. "\n"
              .. hashed_payload
            -- Debug
            -- ngx.log(ngx.ERR, "BEFORE: canonical_request " .. canonical_request)
            
            -- Create a String to Sign before
            local algorithm = "AWS4-HMAC-SHA256"
            local scope = date .. "/" .. ngx.var.region .. "/" .. "s3" .. "/aws4_request"
            -- Create a Hashed canonical request for string_to_sign
            local h = resty_sha256.new()
            h:update(tostring(canonical_request))
            local canonical_request = str.to_hex(h:final())
            -- Create a String to Sign
            local string_to_sign = algorithm .. "\n" .. date_tz .. "\n" .. scope .. "\n" .. canonical_request
            -- Debug
            -- ngx.log(ngx.ERR, "AFTER: canonical_request " .. canonical_request)
            -- ngx.log(ngx.ERR, "STRING_TO_SIGN: " .. string_to_sign)
            
            -- Calculate Signature
            local k_date = hmac:new("AWS4" .. ngx.var.aws_secret_access_key, hmac.ALGOS.SHA256):final(date, false)
            local k_region = hmac:new(k_date, hmac.ALGOS.SHA256):final(ngx.var.region, false)
            local k_service = hmac:new(k_region, hmac.ALGOS.SHA256):final("s3", false)
            local k_signing = hmac:new(k_service, hmac.ALGOS.SHA256):final("aws4_request", false)
            local signature = str.to_hex(hmac:new(k_signing, hmac.ALGOS.SHA256):final(string_to_sign, false))
            -- Debug
            -- ngx.log(ngx.ERR, "signature " .. signature)

            ngx.req.set_header("host", ngx.var.s3endpoint)
            ngx.req.set_header("x-amz-content-sha256", hashed_payload)
            ngx.req.set_header("x-amz-date", date_tz)
            ngx.req.set_header("Authorization", "AWS4-HMAC-SHA256 Credential=" .. ngx.var.aws_access_key .. "/" .. scope .. ", SignedHeaders=" .. signed_headers .. ", Signature=" .. signature)

            -- Debug
            -- local debug_headers = ngx.req.get_headers()
            -- local h_str = "##"
            -- for k, v in pairs(debug_headers) do
            --  h_str = h_str .. k .. ":" .. v .. "\t"
            -- end
            -- h_str = h_str .. "##"
            -- ngx.log(ngx.ERR, "HEADER: " .. h_str)
        }
        proxy_pass https://$bucket.$s3endpoint;
    }
}
```

# Nginxの起動

```
doas service nginx start
```

リロードする場合は、下記を入力して下さい。

```
doas service nginx -s reload
```

# privateなオブジェクにアクセスしてみる

こんな感じでアクセスして確認してみて下さい。

```
curl localhost/index-from-my-nginx-with-key.html
<html>
  <head>
    <title>Object storage/Aintek.xyz</title>
  </head>
  <body>
    <h1>Object storage accessed from my nginx WITH KEY/Aintek.xyz</h1>
    <p>Example!</p>
  </body>
</html>
```

# 独自ドメインの設定

最後に、ご自身が持つDNSサーバのレコードとして、AレコードやCNAMEレコードを登録します。

```
bar.example.com.        A      192.168.1.100
bar.example.com.        CNAME  foo.example.com.
```

これで、`bar.example.com` というドメイン名でアクセスできます。