最小構成で OAuth2 Proxy を使ったHTTPなページでユーザー名を表示する


# はじめに
OAuth2 Proxy を利用してみたいのだが、dockerやらkubernetesやら準備が大変そうだったので、バイナリを利用して動作させてみた。GitHubアカウントとインターネットからアクセスできるLinuxがあれば大丈夫。
作業履歴なので、解説は少なめ。

HTTPなサイトでPHP/Perlを動作させて、OAuth2でGitHub認証したユーザー名を取得して表示させるサンプルを作成する。権限設定とかよりもユーザーの識別って部分をやってるから認可よりも認証っていう意味のほうが強いのかな。

公式ドキュメント
https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview

今回はこれの左側の構成になるはず。
で、Auth Provider としては GitHub を利用する。OpenID Provider / Id Provider とか言うらしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/06b82a64-4bf2-f512-7036-03bec7b3dd44.png)

出典：https://github.com/oauth2-proxy/oauth2-proxy/blob/master/README.md#docs


# GitHub側の作業

## アプリケーション作成

以下にアクセスしてOAuthアプリケーションを作成する
https://github.com/settings/applications/new

Application name : 適当な名前
Homepage URL : このアプリを適用するURL
Application description : 任意（なくてもいい）
Authorization callback URL : Auth Providerへ認可アクセスしたときのコールバック先
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/e4c79e04-8355-c285-1ddd-952dacfdf951.png)

コールバックURLは、アプリと同一サイトの /oauth2/callback を指定するのが基本らしい。
今回は同一サイトで待ち受けているnginxのlocationで /oauth2/ の proxy_pass に ローカルの `:4180` を指定している。（上の図の oauth2_proxy の枠の部分）


こんな画面になるので、Generate a new client secretをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/13d3bafd-a6de-ec8f-7124-a1d75341c42f.png)

ここのClientIDとClientSecretsをメモっておく（Client Secretsは画面を遷移させると見えなくなる）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/b7b76ef4-30b0-39b9-a8a7-6693c7cdb028.png)


# Webサーバーでの作業

今回はVPSのCentOS7で作業していきます。


## LinuxでPHPかPerlの動作するNginxを準備

phpかperlが動作するnginxを準備する。
CentOS7で作る場合の具体的な作業は以下に分離。

https://qiita.com/bashaway/items/5210acec8b42a0059d00


## ポート開放

認証がないHTTPサイトに対してOAuth2で認証するのでHTTPは閉じて、HTTPSを開けておく。

```shell:ポート開放
firewall-cmd --add-service=https --zone=public --permanent
firewall-cmd --remove-service=http --zone=public --permanent
firewall-cmd --reload
```


## サーバー証明書の準備

動作検証のみなので、自己署名証明書でやる。
面倒なのでnginxの設定ディレクトリにSSL関連のディレクトリを作成して、そこに全部いれる。
自己署名なのでどうせ警告がでるが、テストで利用するURLをCNに設定しておく。

```shell:サーバー証明書の作成
mkdir /etc/nginx/ssl
cd /etc/nginx/ssl

CN=webarena01.prosper2.org
openssl req -new -newkey rsa:2048 -sha256 -nodes -outform PEM -keyform PEM -out ${CN}.csr -keyout ${CN}.key -subj "/C=JP/ST=Tokyo/O=${CN}/CN=${CN}"
openssl x509 -req -days 30 -signkey ${CN}.key < ${CN}.csr > ${CN}.crt

```

## NginxのSSL設定

SSLのための設定

```shell:SSL共通設定
cat <<EOF > /etc/nginx/ssl/ssl.conf
ssl_certificate "/etc/nginx/ssl/${CN}.crt";
ssl_certificate_key "/etc/nginx/ssl/${CN}.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout  10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
EOF

```


## NginxのOAuth2

nginxの設定は公式ドキュメントをそのまま利用
https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#configuring-for-use-with-the-nginx-auth_request-directive

```shell:OAuth2-Proxy用の設定
cat <<'EOF' > /etc/nginx/conf.d/oauth2-proxy.conf
server {
  listen 443 ssl;
  #server_name ...;

  include ssl/ssl.conf;

  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 $request_uri;
    # 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/;
    # or "root /path/to/site;" or "fastcgi_pass ..." etc
  }
}
EOF

```


で、Nginxを再起動しておく

```
systemctl restart nginx

```



# OAuth2-Proxyの設定

## OAuth2-Proxyのバイナリを準備

公式ドキュメントはこれ
https://oauth2-proxy.github.io/oauth2-proxy/docs/

適当なディレクトリにバイナリをダウンロードしておく

```shell:oauth2-proxyのダウンロード
mkdir -p /opt/oidc
cd /opt/oidc
OAUTH2_VERSION=v7.2.0
curl -sSL https://github.com/oauth2-proxy/oauth2-proxy/releases/download/${OAUTH2_VERSION}/oauth2-proxy-${OAUTH2_VERSION}.linux-amd64.tar.gz | tar zx --strip=1
```


## OAuth2-Proxyの設定

OAuth2-proxyを動作させるためのコマンドライン引数、設定ファイルは公式ドキュメントを参照して作成する。
https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview
https://github.com/oauth2-proxy/oauth2-proxy/blob/master/contrib/oauth2-proxy.cfg.example

今回は設定ファイルとして `oauth2-proxy.cfg` を作成する。

COOKIE_SECRETは公式ドキュメントのPythonをそのまま利用。
client_id / client_secret は GitHubでOAuth2アプリケーションを作成したときに表示されていたもの。
cookie-expire で再認証がかかるまでの時間を設定している。（以下サンプルでは１時間とした）

```shell:oauth2-proxyの設定
COOKIE_SECRET=`python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'`

cat <<EOF > /opt/oidc/oauth2-proxy.cfg
#デフォルトのまま
http_address = "127.0.0.1:4180"

#なくても動作したけどいれておく
redirect_url="https://${HOSTNAME}/oauth2/callback"
upstreams = "http://127.0.0.1/"

#IdPはgithubを使う
provider = "github"
email_domains = "*" 

#ユーザーを制限したいときはこれを追加
#github_users = [
#    "bashaway"
#]

#HTTP_X_USERのヘッダを利用するのに必要
set_xauthrequest = true

cookie_secret = "$COOKIE_SECRET"
client_id = "fdf334e5b5fdcea8985e"
client_secret = "83bc5f57cb8dc5ee60d8f2676a59519d3fd633b9"

#デフォルトは 168h0m0s らしい
cookie_expire = "1h"
EOF

```

ここまでで、いったん動作するか確認しておく

```shell-session:oauth2-proxyの動作確認
# ./oauth2-proxy --config=/opt/oidc/oauth2-proxy.cfg
[2021/11/07 14:12:40] [proxy.go:89] mapping path "/" => upstream "http://127.0.0.1/"
[2021/11/07 14:12:40] [oauthproxy.go:148] OAuthProxy configured for GitHub Client ID: fdf334e5b5fdcea8985e
[2021/11/07 14:12:40] [oauthproxy.go:154] Cookie settings: name:_oauth2_proxy secure(https):true httponly:true expiry:1m0s domains: path:/ samesite: refresh:disabled

```

設定ファイルに問題があればエラーが表示されプロンプトにもどる。プロンプトに戻らなければ大丈夫。


## OAuth2-Proxyのサービス化

バイナリをコマンドラインから動作させるのは面倒なので、サービス化する。そのためのサンプルも公式ドキュメントにあるので、これを参考にする。
https://github.com/oauth2-proxy/oauth2-proxy/blob/master/contrib/oauth2-proxy.service.example

サンプルからの変更点はユーザーとグループを nginx にして `ExecStart` のバイナリパスを前述のバイナリをダウンロードしたパスにしたことくらい。

```shell:oauth2-proxyのサービス化
cat <<EOF > /etc/systemd/system/oauth2_proxy.service
# Systemd service file for oauth2-proxy daemon
#
# Date: Feb 9, 2016
# Author: Srdjan Grubor <sgnn7@sgnn7.org>

[Unit]
Description=oauth2-proxy daemon service
After=syslog.target network.target

[Service]
# www-data group and user need to be created before using these lines
User=nginx
Group=nginx

ExecStart=/opt/oidc/oauth2-proxy --config=/opt/oidc/oauth2-proxy.cfg
ExecReload=/bin/kill -HUP $MAINPID

KillMode=process
Restart=always

[Install]
WantedBy=multi-user.target
EOF

```

これで、サービス登録とサービス起動する。

```
systemctl enable oauth2_proxy
systemctl start oauth2_proxy

```


# PHP/Perlスクリプトでユーザー情報を取得する

OAuth2で認証したユーザーの情報を表示するために、Webサーバー上にPHPとPerlスクリプトを配置する。
ヘッダ情報に `HTTP_X_USER` にユーザー名が `HTTP_X_EMAIL` にメールアドレスが入るので、これを表示させればよい。

## PHPスクリプト

PHPは `$_SERVER` にいろんな情報が入ってるので、ここから取得する。

```shell:PHPスクリプト
cat <<'EOF' > /usr/share/nginx/html/php-oidc-header.php
<?php
print "<html><body>\n";
print "Hello PHP Script!<br>\n";

$user = $_SERVER['HTTP_X_USER'];
$email = $_SERVER['HTTP_X_EMAIL'];
print "USERNAME : $user<br>\n";
print "E-MAIL   : $email<br>\n";

print "<hr>\n";
ksort($_SERVER);
foreach($_SERVER as $key => $value){
    if ( preg_match( '/^HTTP/', $key ) ) {
        print "[\"$key\"] => $_SERVER[$key]<br>\n";
    }
}

print "</body></html>\n";
?>
EOF

```

## Perlスクリプト

いまだに `%headers = map { $_ => $q->http($_) } $q->http();` の意味がよくわかってない。

```shell:Perlスクリプト
cat <<'EOF' > /usr/share/nginx/html/perl-oidc-header.pl
#!/usr/bin/perl
use CGI;

print "Content-type: text/html\n\n";
print "<html><body>\n";
print "Hello Perl Script!<br>\n";

$q = CGI->new;
%headers = map { $_ => $q->http($_) } $q->http();

$user = $headers{'HTTP_X_USER'};
$email = $headers{'HTTP_X_EMAIL'};
print "USERNAME : $user<br>\n";
print "E-MAIL   : $email<br>\n";

print "<hr>\n";
foreach( sort keys %headers ) {
    print "[\"$_\"] => $headers{$_}<br>\n";
}

print "</body></html>\n";
exit;
EOF
chmod 755 /usr/share/nginx/html/perl-oidc-header.pl

```

# 動作確認

配置したスクリプトにアクセスするとOAuth2Proxyの画面が表示されるので、「Sign in with GitHub」をクリック
https://webarena01.prosper2.org/php-oidc-header.php
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/39e429da-3ef4-7748-0fd0-3a72b61aa395.png)

アプリケーションを認可してよいかの画面が表示されるので、「Authorize XXX」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/9c69e6cc-63b5-653f-a35e-955cbb9be75c.png)

作成したページが表示され、ユーザー名とemailが取得できていることが確認できた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/5f68972d-85b0-ac6f-d9e5-82151b9cb7ca.png)


ローカル上で同じページをHTTPとして取得すると、こんな感じ。
ユーザー名ありきで作成すると、HTTPでアクセスされたときに動作しないので、外からはHTTPアクセスはできなくしておくことが必要になる。

```shell-session:Curlでローカルのページとして取得
# curl http://localhost/php-oidc-header.php
<html><body>
Hello PHP Script!<br>
USERNAME : <br>
E-MAIL   : <br>
<hr>
["HTTP_ACCEPT"] => */*<br>
["HTTP_HOST"] => localhost<br>
["HTTP_USER_AGENT"] => curl/7.29.0<br>
</body></html>
```


# そのほか
設定ファイルのデフォルトに助けられてるような気がして、ちゃんと必要な要素が取り込めてるのかよくわからない。redirect_urlとかcookie-secretとかなんだか使えてないような、でも動作してるように見えるし。
むつかしい。。。

GitHubのアプリは削除しているので、このページのURLにアクセスしてもGitHubに転送されて404になる。


# 出典
https://github.com/oauth2-proxy/oauth2-proxy