ngrok + nginx + oauth2_proxy + auth0で認証機能を持つWEBアプリを外部公開する

# はじめに
クラウドリソースを使うとお金がかかる。
ローカルにシステム構築を行い、クラウドにはシステムへのエンドポイントを設置するだけの構成にすることで費用削減を狙う。

## 使用するもの

- oauth2_proxy
IDaaSとの中継を行う

- nginx
リバースプロキシ

- ngrok
ネットワーク内部にあるローカルサーバのポート（ローカルホスト）に外部から直接アクセスすることを可能にするトンネリング／リバース・プロキシツール
https://ngrok.com/

- auth0
IDaaS
https://auth0.com/

## システム構成

app1(8001) / app2(8002) -> oauth_proxy(4180) -> nginx(8081, 8082) -> ngrok -> endpoint(randam1, randam2)

```
endpoint
└─nginx
   └─oauth2_proxy
      ├─auth0
      └─app
```

## 環境構築

- Windows 10

## 構築方法

- 最終的に下記のフォルダ構成となる。

```
C:.
└─Project
   ├─app1
   │  └─index.html
   ├─app2
   │  └─index.html
   ├─nginx(必要な部分のみ記載)
   │  ├─conf
   │  │  └─nginx.conf
   │  └─nginx.exe
   ├─oauth2_proxy
   │  ├─oauth2-proxy.exe
   │  └─oauth2-proxy.cfg
   └─ngrok
      └─ngrok.exe

"%HOMEPATH%\AppData\Local\ngrok\ngrok.yml"
>https://ngrok.com/docs/ngrok-agent/config
```
- app1やapp2は簡単に用意する

```app1/index.html
<!doctype html>
<html>
  <head>
    <title>app1</title>
  </head>
  <body>
    <p>本文</p>
  </body>
</html>
```

```app2/index.html
<!doctype html>
<html>
  <head>
    <title>app2</title>
  </head>
  <body>
    <p>本文</p>
  </body>
</html>
```

- コマンドプロンプト(cmd1, cmd2)を二つ起動し、HTTPサーバー(app)を二つ起動する

```cmd1
C:\Project\app1> python -m http.server 8001
```

```cmd2
C:\Project\app2> python -m http.server 8002
```

>Python 3.x
>$ python -m http.server 8000
>参考：https://gist.github.com/willurd/5720255

- nginxをダウンロードし、上記のフォルダに配置する。
- nginx.confを設定する。

```nginx.conf

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       8081;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

		  location /oauth2/ {
		    proxy_pass       http://127.0.0.1:4180;
		    proxy_set_header Host                    $host;
		    proxy_set_header X-Real-IP               $remote_addr;
		    proxy_set_header X-Scheme                $scheme;
		    proxy_set_header X-Auth-Request-Redirect http://127.0.0.1:8001;
		    # or, if you are handling multiple domains:
		    # proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
		  }
		  location = /oauth2/auth {
		    proxy_pass       http://127.0.0.1:4180;
		    proxy_set_header Host             $host;
		    proxy_set_header X-Real-IP        $remote_addr;
		    proxy_set_header X-Scheme         $scheme;
		    # nginx auth_request includes headers but not body
		    proxy_set_header Content-Length   "";
		    proxy_pass_request_body           off;
		  }

		  location / {
		    auth_request /oauth2/auth;
		    error_page 401 = /oauth2/sign_in;

		    # pass information via X-User and X-Email headers to backend,
		    # requires running with --set-xauthrequest flag
		    auth_request_set $user   $upstream_http_x_auth_request_user;
		    auth_request_set $email  $upstream_http_x_auth_request_email;
		    proxy_set_header X-User  $user;
		    proxy_set_header X-Email $email;

		    # if you enabled --pass-access-token, this will pass the token to the backend
		    auth_request_set $token  $upstream_http_x_auth_request_access_token;
		    proxy_set_header X-Access-Token $token;

		    # if you enabled --cookie-refresh, this is needed for it to work with auth_request
		    auth_request_set $auth_cookie $upstream_http_set_cookie;
		    add_header Set-Cookie $auth_cookie;

		    # When using the --set-authorization-header flag, some provider's cookies can exceed the 4kb
		    # limit and so the OAuth2 Proxy splits these into multiple parts.
		    # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
		    # so if your cookies are larger than 4kb, you will need to extract additional cookies manually.
		    auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;

		    # Extract the Cookie attributes from the first Set-Cookie header and append them
		    # to the second part ($upstream_cookie_* variables only contain the raw cookie content)
		    if ($auth_cookie ~* "(; .*)") {
		        set $auth_cookie_name_0 $auth_cookie;
		        set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1";
		    }

		    # Send both Set-Cookie headers now if there was a second part
		    if ($auth_cookie_name_upstream_1) {
		        add_header Set-Cookie $auth_cookie_name_0;
		        add_header Set-Cookie $auth_cookie_name_1;
		    }

		    proxy_pass http://127.0.0.1:8001/;
		}

        server {
            listen       8082;
            server_name  localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
    		  location /oauth2/ {
    		    proxy_pass       http://127.0.0.1:4180;
    		    proxy_set_header Host                    $host;
    		    proxy_set_header X-Real-IP               $remote_addr;
    		    proxy_set_header X-Scheme                $scheme;
    		    proxy_set_header X-Auth-Request-Redirect http://127.0.0.1:8002;
    		    # or, if you are handling multiple domains:
    		    # proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
    		  }
    		  location = /oauth2/auth {
    		    proxy_pass       http://127.0.0.1:4180;
    		    proxy_set_header Host             $host;
    		    proxy_set_header X-Real-IP        $remote_addr;
    		    proxy_set_header X-Scheme         $scheme;
    		    # nginx auth_request includes headers but not body
    		    proxy_set_header Content-Length   "";
    		    proxy_pass_request_body           off;
    		  }
    
    		  location / {
    		    auth_request /oauth2/auth;
    		    error_page 401 = /oauth2/sign_in;
    
    		    # pass information via X-User and X-Email headers to backend,
    		    # requires running with --set-xauthrequest flag
    		    auth_request_set $user   $upstream_http_x_auth_request_user;
    		    auth_request_set $email  $upstream_http_x_auth_request_email;
    		    proxy_set_header X-User  $user;
    		    proxy_set_header X-Email $email;
    
    		    # if you enabled --pass-access-token, this will pass the token to the backend
    		    auth_request_set $token  $upstream_http_x_auth_request_access_token;
    		    proxy_set_header X-Access-Token $token;
    
    		    # if you enabled --cookie-refresh, this is needed for it to work with auth_request
    		    auth_request_set $auth_cookie $upstream_http_set_cookie;
    		    add_header Set-Cookie $auth_cookie;
    
    		    # When using the --set-authorization-header flag, some provider's cookies can exceed the 4kb
    		    # limit and so the OAuth2 Proxy splits these into multiple parts.
    		    # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
    		    # so if your cookies are larger than 4kb, you will need to extract additional cookies manually.
    		    auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;
    
    		    # Extract the Cookie attributes from the first Set-Cookie header and append them
    		    # to the second part ($upstream_cookie_* variables only contain the raw cookie content)
    		    if ($auth_cookie ~* "(; .*)") {
    		        set $auth_cookie_name_0 $auth_cookie;
    		        set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1";
    		    }
    
    		    # Send both Set-Cookie headers now if there was a second part
    		    if ($auth_cookie_name_upstream_1) {
    		        add_header Set-Cookie $auth_cookie_name_0;
    		        add_header Set-Cookie $auth_cookie_name_1;
    		    }
    
    		    proxy_pass http://127.0.0.1:8002/;
    		}

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
```

- ngrokにアカウント登録する。

```
説明後日追記
```

- ngrokをダウンロードし、上記のフォルダに配置する。
- ngrok.ymlを下記のように修正する。

```%HOMEPATH%\AppData\Local\ngrok\ngrok.yml
version: "2"
authtoken: *******************************************
tunnels:
  app1:
    addr: 8081
    proto: http
  app2:
    addr: 8082
    proto: http
```

- auth0にアカウント登録する。

```
説明後日追記
```

- oauth0_proxyをダウンロードする。

```oauth2-proxy.cfg
## <addr>:<port> to listen on for HTTP/HTTPS clients
http_address = "127.0.0.1:4180"

provider = "oidc"

# oidc_issuer_url = "https://${Auth0＞Applications＞Settings＞Basic Information＞Domain}"
oidc_issuer_url = "https://xxxxx.jp.auth0.com/"

## the OAuth Redirect URL.
# redirect_url = "https://${ngrokで公開したドメイン名}/oauth2/callback"
redirect_url = "https://xxxxx.jp.ngrok.io/oauth2/callback"

## the http url(s) of the upstream endpoint. If multiple, routing is based on path
upstreams = [
    "https://${ngrokで公開したドメイン名}"
]

email_domains = [
    "*"
]

## The OAuth Client ID, Secret
# client_id = "${Auth0＞Applications＞Settings＞Basic Information＞Client ID}"
client_id = "***************"
# client_secret = "${Auth0＞Applications＞Settings＞Basic Information＞Client Secret}"
client_secret = "************************************************"

## Cookie Settings
cookie_name = "_oauth2_proxy"
cookie_secret = "**************************************"
```

## 試してみる

```cmd3
C:\Project\nginx>start nginx
```

```cmd4
C:\Project\ngrok>ngrok start app1 app2
```

```cmd5
C:\Project\oauth2_proxy> oauth2_proxy.exe --config=oauth2_proxy.cnf
```

## サービスに登録する
サービスに登録することで、システムが稼働しているPCの予期せぬシャットダウンがあった場合に
電源投入で再稼働させることができる。

- nssmをダウンロードする。PATHが通った位置に配置

```cmd
nssm install nginx
nssm set nginx Application C:\Project\nginx\nginx.exe
nssm set nginx AppDirectory C:\Project\nginx
nssm set nginx AppParameters "start nginx"

nssm install ngrok
nssm set ngrok Application C:\Project\ngrok\ngrok.exe
nssm set ngrok AppDirectory C:\Project\ngrok

nssm install oauth2_proxy
nssm set oauth2_proxy Application C:\Project\oauth2_proxy\oauth2_proxy.exe
nssm set oauth2_proxy AppDirectory C:\Project\oauth2_proxy
nssm set oauth2_proxy AppParameters "--config='C:\Project\oauth2_proxy\oauth2_proxy.cfg'"
```

## その他
ngrokが無料プランだとngrok起動のたびに外部IPが変わってしまうため、
その都度ngrokのWEBダッシュボードよりURLを確認し、下記を設定する必要がある。

- Auth0＞Applications＞Settings＞Application URIs＞Application Login URI
- Auth0＞Applications＞Settings＞Application URIs＞Allowed Callback URLs
- oauth2_proxy.cfg＞redirect_url