BIG-IPで、Virtual ServerにBASIC認証を仕掛ける

# はじめに
このページでは、BIG-IPで、Virtual Server（HTTP or HTTPS)へのアクセスにBASIC認証を仕掛けるための方法について記載します。BASIC認証は、セキュリティ的にどうだろうかと思わなくもありませんが、調べてみると、まだまだ需要はあるようです。少なくとも通信経路は必ず暗号化しましょう。

BASIC認証は、Apacheやnginxならばconfファイルで簡単にできることですが、BIG-IPではiRuleが必要です。また、ユーザ名とパスワードの保管のために、Data Group Listも使います。

# 参考情報
 - [DevCentral: HTTP Basic Access Authentication iRule Style](https://devcentral.f5.com/articles/http-basic-access-authentication-irule-style)
 - [DevCentral: iRules 101 - #17 – Mapping Protocol Fields with the Binary Scan Command](https://devcentral.f5.com/articles/irules-101-17-mapping-protocol-fields-with-the-binary-scan-command)
 - [DevCentral: Advanced iRules: Getting Started with iRules Procedures ](https://devcentral.f5.com/articles/getting-started-with-irules-procedures)
 - [DevCentral: iRules Wiki: HTTP::username](https://devcentral.f5.com/wiki/iRules.HTTP__username.ashx)
 - [DevCentral: iRules Wiki: HTTP::password](https://devcentral.f5.com/wiki/irules.http__password.ashx)

# 環境
このページに記載のiRuleはv12.1.2で動作を確認してます。

# iRule
## 動作
このiRuleは、以下のような動作を行います。わざわざ箇条書きにしていますが、ごく普通のBASIC認証です。

1. BIG-IPのVirtual Serverの、特定のディレクトリ配下（ここでは/mgmt/）へのアクセス時に、BASIC認証を行う
2. BIG-IPには、認証に必要なユーザ名とパスワード（パスワードをsha256でハッシュした後の文字列）をあらかじめData Group Listとして登録しておく
3. BIG-IPは、/mgmt/で開始するURLへのアクセス時に対して、BASIC認証の情報（ユーザ名とパスワード）が含まれていない場合、401 Unauthorizedを応答する
4. BASIC認証の情報がBIG-IPのData Group Listに登録されているユーザ名とパスワードに一致する場合は、Poolへ通信を振り分ける

本ページのサンプルでは、認証用のユーザ名とパスワードは「ユーザ名：admin、パスワード：admin」としています。

## コード

```tcl
proc basic_auth {} {
    binary scan [ sha256 [HTTP::password]] H* password
    if { [class lookup [HTTP::username] authorized_users] ne $password } {
        log local0. "User [HTTP::username], has been denied access to virtual server [virtual name]."
        HTTP::respond 401 WWW-Authenticate "Basic realm=\"Secured Area\""
    }
}

when HTTP_REQUEST {
    if {[HTTP::uri] starts_with "/mgmt/"} {
        call basic_auth
    }
}
```

## Data Group List
Data Group list名"authorized_users"を作成して、ユーザ名と、パスワード”admin"をsha256でハッシュ化した値を保存します。

GUI画面（Configuration Utility）では、このような画面になります。
<img width="661" alt="basic_auth_dgl_sha256.png" src="https://qiita-image-store.s3.amazonaws.com/0/157891/e473d984-7652-a961-8bde-a0287ad2ba7c.png">

複数のユーザを登録する場合は、このData Group Listにレコードを追加します。

パスワードのSHA256ハッシュ変換は、BIG-IPのbashを使う場合、以下のようなコマンドで可能です。

```bash
$ echo -n "admin" | sha256sum 
8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918  -

```

## Virtual Server
このiRuleをVirtual Serverに適用します。これについては特に書くべきことはありません。

# 動作確認
まず、通常のページ(/index.html)にアクセスした場合。

```bash
$ curl -i http://203.0.113.1/index.html

HTTP/1.1 200 OK
Content-Type: text/html
Accept-Ranges: bytes
Content-Length: 23
Server: lighttpd/1.4.45

This is a test server.
```

認証なく、問題なくアクセスができています。

次に、BASIC認証が必要なディレクトリ配下（/mgmt/index.html)に、認証なしでアクセスした場合。

```bash
$ curl -i http://203.0.113.1/mgmt/index.html

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="Secured Area"
Server: BigIP
Connection: Keep-Alive
Content-Length: 0
```

BIG-IPが応答して、401を応答してます。

次は認証用のユーザ名とパスワードを入れますが、間違ったパスワードでアクセスした場合。

```bash
$ curl -i -u admin:admin2 http://203.0.113.1/mgmt/index.html

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="Secured Area"
Server: BigIP
Connection: Keep-Alive
Content-Length: 0
```

依然、401が応答し続けます。ちなみに上記2回のアクセスは、BIG-IPの/var/log/ltmに、次のような感じで記録されます。

```text
info tmm[17378]: Rule /Common/Sample_Basic_Auth <HTTP_REQUEST>: User , has been denied access to virtual server /Common/vs_public.
info tmm[17378]: Rule /Common/Sample_Basic_Auth <HTTP_REQUEST>: User admin, has been denied access to virtual server /Common/vs_public.
```

**Sample_Basic_Auth**はiRuleの名前で、**vs_public**はVirtual Serverの名前です。

最後に、正しいユーザ名とパスワードでアクセスした場合。

```bash
$ curl -i -u admin:admin http://203.0.113.1/mgmt/index.html

HTTP/1.1 200 OK
Content-Type: text/html
Accept-Ranges: bytes
Content-Length: 32
Server: lighttpd/1.4.45

This page needs authentication.
```

BIG-IPでの認証が通り、サーバからのレスポンスが得られました。

# 解説
## procについて
**proc**は、tclでサブルーチン(関数)を作るコマンドです。tclではサブルーチンのことを「プロシジャ」と呼ぶそうなので、ここではプロシジャと呼ぶことにします。

プロシジャは次のような文法となります。

```tcl
proc プロシジャ名 {引数1 引数2 ...} {
    プロシジャ内の処理...

    # 特定の値を返したい場合はreturnを使う
}

# プロシジャを実行する際はcallを使う
call プロシジャ名 引数1 引数2 ...
```

一般的なプログラミング言語の関数と同様に、引数や戻り値が利用できますが、今回使用しているプロシジャでは不要なので、使用していません。returnを使わない場合、プロシジャの終了時、プロシジャ内で最後に実行したコマンドの戻り値が、プロシジャの戻り値になります。

引数が0個の時、プロシジャの定義時に"*proc プロシジャ名 {} {*"と、引数に空のリスト**{}**をつけるべきなのか気になりましたが、[このページ（wiki.tcl.tk)](http://wiki.tcl.tk/463)を参考にして、空のリストをつけることにしました。ただし、DevCentralのページには、引数のないプロシジャ（単に {}を書かないだけ）のサンプルコードが普通にあるので、少なくともiRuleでは空リストは書かなくとも良さそうです。

procの面白いのは、これがBIG-IPに実装されたのは、案外最近（v11.4以降）ということです。参照情報にあげたprocの解説ページには**"Ladies and gentlemen, procs are now supported in iRules!"**とデカデカと書かれていて、それまでどれだけ待ち望まれていたのかと思います。プログラミング言語では、サブルーチンを作れるのは当然のことのように思いますが、iRuleにそれを実装するのは、簡単なことではなかったようです。

procで作成したプロシジャは、同一iRule内からだけでなく、外部のiRuleからもcallすることができます。procについては、いずれ別ページを作成したいと思います。

## [HTTP::username] と [HTTP::password]について
これらのコマンドにより、HTTP Basic認証のユーザ名とパスワードを取得できます。詳細はDevCentralを参照してください。

HTTP Basic認証では、これらの情報はクライアントからサーバへの通信時、BASE64でエンコードされていますが、[HTTP::username] と [HTTP::password]はデコードした値を返すので、デコード処理を作成する必要はありません。

##パスワードをハッシュ化した値で保存する意味
Data Group Listにはパスワードをハッシュ化した値を保存してますが、そもそも、Basic認証ではパスワードは単にBase64エンコードされた文字列に過ぎず、通信経路での傍受については、ハッシュ化は何の意味もありません。通信経路での安全性は、HTTPS化で対応すべきと思います。

Apacheやnginxでは、パスワード情報は一般的にハッシュ化してるので、このiRuleでもハッシュ化するようにしてます。

「パスワード情報（Apacheで言うところの.htpasswd）が漏洩したらどうするんだ」という点で、ハッシュ化する意味は確かにあるのですが、そもそもこのData Group Listが保存されるのは、bigipの設定ファイルであるbigip.confであり、このファイルが外部に漏れたら、Basic認証のパスワードどころの騒ぎではないような気もします。が、それでもやらないよりはマシかと思います。

Data Group Listを外部ファイル参照（external）にするようなケースでは、ハッシュする価値はよりあるのではないでしょうか。

DevCentralにあるサンプルコードではmd5でハッシュしていましたが、時代の流れと思ってsha256でハッシュ化することにしました。これについては、サンプルコードの"md5"を"sha256"に置き換えただけです。


# 免責事項
本ページの内容に誤り等があり、参考にされた方がなんらかの損害を被った場合、一切の責任は負いません。