2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セキュリティごった煮一人完走チャレンジAdvent Calendar 2024

Day 11

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

Posted at

前回

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

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


ACLとは

Access Control Listの略です。

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


今回作るもの

GitHubのリポジトリ

作る理由

前回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を書いてます。

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

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

次回は別の認証作りたいなあ。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?