前回
前回basic認証をLuaで実装し,動くところまでやりました。
今回はACLぽいものをredisで作ってLuaから操作しました。
ACLとは
Access Control Listの略です。
ACL(Access Control List)とは、システムやファイル、ネットワーク上のリソースなどへのアクセス可否の設定をリストとして列挙したものです。
特にネットワークの場合、宛先と送信元のIPアドレスおよびポート番号を条件とした上で、その条件に合致した通信の可否をACLとして設定します。このACLを使ってネットワークアクセスを制御することにより、特定のサーバーのIPアドレス宛のパケットのみを許可する、あるいは特定の送信元IPアドレスからのパケットはすべて破棄するなどといった設定が可能になります。NTTコムの記事より
今回作るもの
作る理由
前回basic認証を作り,今後いろいろな認証を実装したいと思いました。
その際に,どのパスにアクセスした時にどの認証が使われるのかを管理する機能がほしいなと思い,簡易ACLをredisで立てることにしました。
実装方針
- ACLテーブルは
ACL|
から始め,受け取ったパスをそのままキーにする。e.g. localhost/basicにアクセスシた際のACLはACL|basic
にする - valueはredisのハッシュにする。
- proxy_passの転送先
- 認証方法
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
が返せるようなハッシュ型に変換して使う。
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以外に情報がなかったので同じ方針で実装しました(大変感謝しております)。
方針としては,先にredisのサーバを建てた上で,外部からredis-cliを使って接続し,コマンドを叩いて初期データを挿入するという方法です。自分の環境では上のQiitaにあった--pipe
がうまくうごかなかったので普通にshell scriptを書いてます。
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
#!/bin/bash
redis-cli -h redis_app -p 6379 HSET "ACL|basic" "proxy_pass" "https://example.com" "authentication_type" "basic"
次回は別の認証作りたいなあ。