selmertsxの素振り日記

ひたすら日々の素振り内容を書き続けるだけの日記

Local環境のアプリケーションに対してAzure ADでOpenID Connectの認証をしてみる (その2)

TL;DR

  • Azure ADでOpenID Connectの認証ができるか確認をした
  • 確認環境は森岡がテスト用に作ったアプリケーション
  • 上記環境はLocal環境で動いていて、Dockerで構成されている
  • lua-resty-openidcを使ってnginxレイヤーだけで認証ができた
  • まだ本番への反映をしていないので、完成度は30%くらい
    • sessionが切れたときに、/login に強制的にredirectさせる方法
    • dockerでない環境での反映手順も考えられていない
  • Azure AD側にアプリケーションを登録しなければ使えないので、ある程度セキュアではなかろうか

Azure ADの設定

Azure AD側の設定は、基本的にAzure AD公式ドキュメントの方法に従う。

  • Azure Potalにサインインする
  • Azure Active Directoryからアプリの登録を行う
  • サインオンURLの設定をする
    • 今回はlocalで確認するので http://127.0.0.1:8080 とした
  • アプリケーションのプロパティから、アプリケーションIDを確認し控えておく
  • 「キー」から鍵を作成して控えておく
  • このアプリケーションにアクセス可能な人をAzure AD上で設定しておく

f:id:selmertsx:20180710140021p:plain

アプリの登録

f:id:selmertsx:20180710140036p:plain

鍵の登録

実装

Docker Build

今回、サクッと確認するためにDockerで環境を構築。 nginxのlua pluginでOIDCができるやつがいたので、こいつを利用させて貰う。

FROM openresty/openresty:1.13.6.2-0-centos
ADD conf.d/app.conf /etc/nginx/conf.d/app.conf
ADD nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
ADD src /usr/local/openresty/nginx/src
RUN opm install zmartzone/lua-resty-openidc
CMD ["/usr/bin/openresty", "-g", "daemon off;"]

openrestyのdocker imageには、opm(openresty package manager)がデフォルトで入っているので、そのopmを使ってlua-resty-openidcをinstallすることができる。

証明書の設定

luasslを利用する際は、lua_ssl_trusted_certificate ディレクティブに証明書の設定をする必要がある。この証明書だけれども、実はopenrestyのdocker imageの中にデフォルトで入っているので、今回はそれを利用する。

➜  pomodoro git:(master) ✗ docker exec -it pomodoro-web /bin/bash
[root@bba9dae7baca /]# ls -al /etc/ssl/certs/
total 8
drwxr-xr-x 2 root root 4096 Apr  2 18:38 .
drwxr-xr-x 5 root root 4096 Apr  2 18:38 ..
lrwxrwxrwx 1 root root   49 Apr  2 18:38 ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
lrwxrwxrwx 1 root root   55 Apr  2 18:38 ca-bundle.trust.crt -> /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt

これをnginxの設定に記載して終了。実際のコードはここ を参照

Luaコードの設定

-- https://github.com/selmertsx/pomodoro/blob/master/docker/nginx/src/test.lua
local opts = {
  redirect_uri_path = "/",
  discovery = os.getenv("DISCOVERY"),
  client_id = os.getenv("CLIENT_ID"),
  client_secret = os.getenv("CLIENT_SECRET"),
}

local res, err = require("resty.openidc").authenticate(opts)

if err then
  ngx.status = 500
  ngx.say(err)
  ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

ngx.req.set_header("X-USER", res.id_token.sub)

環境変数の渡し方

discovery, client_id, client_secretはシークレットな情報なので環境変数で渡す。 nginxでは環境変数luaに渡すためには、env directiveを利用する必要がある。

The NGINX environment variable is used internally by nginx and should not be set directly by the user.

公式ドキュメントには上記のように記載されているので、nginx.confの一番上でenv directiveを利用する。

worker_processes  1;
env DISCOVERY;
env CLIENT_ID;
env CLIENT_SECRET;
events {
  worker_connections  1024;
}

なお CLIENT_IDやCLIENT_SECRETはAzure AD設定時に取得したアプリケーションIDや鍵を設定すれば良い。

環境変数DISCOVERYの設定

discovery urlの取得方法については、公式ドキュメントに記載されている通りであるが、少しばかりハマりポイントがあるので解説。ここだけはちゃんと読んでほしい。

  • https://login.microsoftonline.com/{tanent_name}/.well-known/openid-configuration でアクセスし、tanent_idを取得
  • https://login.microsoftonline.com/{tanent_id}/.well-known/openid-configuration の文字列をnginxに設定する

上記設定の理由

ここは興味があれば見るくらいで良い。

まず前提として、Azure ADにおいて、Discoveryは下記2つのフォーマットで取得することができる

  1. https://login.microsoftonline.com/{tanent_name}/.well-known/openid-configuration
  2. https://login.microsoftonline.com/{tanent_id}/.well-known/openid-configuration

lua-resty-openidcを利用する場合、2番のフォーマットでDiscoveryを指定しなければならない。その理由は、lua-resty-openidcの実装上の問題である。上記ライブラリにおいて、openidcの認証は下記のような実装になっている。

-- https://github.com/zmartzone/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L469-L509
      ngx.log(ngx.DEBUG, "response data: "..res.body)
      json, err = openidc_parse_json_response(res)
      if json then
        if string.sub(url, 1, string.len(json['issuer'])) == json['issuer'] then
          openidc_cache_set("discovery", url, cjson.encode(json), exptime or 24 * 60 * 60)
        else
          err = "issuer field in Discovery data does not match URL"
          ngx.log(ngx.ERR, err)
          json = nil
        end
      end

DiscoveryのURLと、discovery urlから返されるissuerのURLの一部が一致しているか見ている。Discoveryから得られるissuerのURLは、https://sts.windows.net/{tanent_id}/ という値になる。そのため、tanent_nameでURLを指定すると一致しないという問題が起きる。よって、Discovery取得用のURLは、tanent_idで指定する必要がある。

認証結果

http://127.0.0.1:8080/test にアクセスすると下記のページにredirectされる

f:id:selmertsx:20180710135840p:plain

認証が終わるとこんな感じ

f:id:selmertsx:20180710135916p:plain