作って学ぶリバースプロキシ⑥LuaとredisでACLぽいものを作る

## 前回

https://qiita.com/sigma_devsecops/items/7726c643b5c65f654d9a

前回basic認証をLuaで実装し，動くところまでやりました。

今回はACLぽいものをredisで作ってLuaから操作しました。

---

## ACLとは

Access Control Listの略です。

> ACL（Access Control List）とは、システムやファイル、ネットワーク上のリソースなどへのアクセス可否の設定をリストとして列挙したものです。
> 特にネットワークの場合、宛先と送信元のIPアドレスおよびポート番号を条件とした上で、その条件に合致した通信の可否をACLとして設定します。このACLを使ってネットワークアクセスを制御することにより、特定のサーバーのIPアドレス宛のパケットのみを許可する、あるいは特定の送信元IPアドレスからのパケットはすべて破棄するなどといった設定が可能になります。[NTTコムの記事](https://www.ntt.com/bizon/glossary/e-a/acl.html)より

---

## 今回作るもの

[GitHubのリポジトリ](https://github.com/RyosukeDTomita/GateKeeper)

### 作る理由

前回basic認証を作り，今後いろいろな認証を実装したいと思いました。
その際に，どのパスにアクセスした時にどの認証が使われるのかを管理する機能がほしいなと思い，簡易ACLをredisで立てることにしました。

### 実装方針

- ACLテーブルは`ACL|`から始め，受け取ったパスをそのままキーにする。e.g. localhost/basicにアクセスシた際のACLは`ACL|basic`にする
- valueはredisのハッシュにする。
    - proxy_passの転送先
    - 認証方法

```shell
redis-cli -h redis_app HGETALL "ACL|basic"
1) "proxy_pass"
2) "https://example.com"
3) "authentication_type"
4) "basic"
```
- `hgetall`で取得すると配列になるので，`acl.proxy_pass`から`https://example.com`が返せるようなハッシュ型に変換して使う。

```lua
local resty_redis = require "resty.redis"
local redis = resty_redis:new()

local basic_auth = require "basic_auth"

-- redisからACLを取得して認証方式を選択する
local function get_acl(request_uri)
    -- redisに接続。 compose.yamlのサービス名で名前解決できる
    local ok, err = redis:connect("redis_app", 6379)
    if not ok then
        -- redisに接続できない場合
        ngx.log(ngx.ERR, "failed to connect Redis: ", err)
        return ngx.exit(500)
    end
    local acl_hash, err = redis:hgetall("ACL|" .. request_uri)
    -- acl_hashが{}の場合は404を返す
    if not next(acl_hash) then
        ngx.log(ngx.INFO, "acl_hash is empty")
        return ngx.exit(404)
    end
    -- TODL: ディレクトリの存在が露呈しないように修正
    if err then
        ngx.log(ngx.ERR, "failed to get ACL: ", err)
        return ngx.exit(500)
    end

    local acl = redis:array_to_hash(acl_hash)
    redis:close()
    return acl
end

local request_uri = ngx.var.request_uri:gsub("/", "")
local acl = get_acl(request_uri)
ngx.var.pass = acl.proxy_pass

-- localhost/basicにアクセスした場合
if acl["authentication_type"] == "basic" then basic_auth.auth() end

ngx.log(ngx.INFO, "PROXY_PASS: ", ngx.var.pass, "REQUEST_URI: ",
        ngx.var.request_uri, "AUTH_TYPE: ", acl["authentication_type"]);
ngx.log(ngx.INFO, "STATUS CODE PROXY_PASS: ", ngx.var.status_code);

```

### ハマりポイント: redisに初期データを追加する

毎回redisへのACLの投入をターミナルから実行するのは面倒なのでdocker build時にやってもらおうと思ったら，意外と詰まったので共有。

redisのimageに対するDockerfileに`redis-cli`コマンドを書いて実装しようとしましたが，接続エラーになってしまいました。これはdocker build中にはredis-serverが起動していないからです。
MySQLとかだと初期データimportする機能とかあったりすると思うのですが，redisにそのような機能はない?

こちらのQiita以外に情報がなかったので同じ方針で実装しました(大変感謝しております)。

https://qiita.com/igossou/items/a51180e03f6cbc62f8be

方針としては，先にredisのサーバを建てた上で，外部からredis-cliを使って接続し，コマンドを叩いて初期データを挿入するという方法です。自分の環境では上のQiitaにあった`--pipe`がうまくうごかなかったので普通にshell scriptを書いてます。

```compose.yaml
services:
  reverse_proxy_app:
    build:
      context: ./reverse_proxy
      dockerfile: Dockerfile
    image: lua-reverse-proxy:latest
    container_name: reverse_proxy_container
    ports:
      - 80:80 # localport:dockerport
  redis_app:
    build:
      context: ./redis
      dockerfile: Dockerfile
    image: redis-img:latest
    container_name: redis_container
    ports:
      - 6379:6379 # localport:dockerport
  # redis_appに初期パスワー投入するためのクライアント
  redis_client:
    image: redis:latest
    volumes:
      - ./redis/initial_data_redis.sh:/tmp/initial_data_redis.sh
    command: >
      /bin/bash -c 'source /tmp/initial_data_redis.sh'
    depends_on:
      - redis_app

```

```shell:initial_data_redis.sh
#!/bin/bash
redis-cli -h redis_app -p 6379 HSET "ACL|basic" "proxy_pass" "https://example.com" "authentication_type" "basic"
```

---

次回はACLを作ってべつの認証を作る準備を始めたいと思います。
手始めにBasic認証に似ていそうなDigest認証について調べてみます。

https://qiita.com/sigma_devsecops/items/f4f3fbd2138b2b67d8ca