[Python] kerberos認証でHDFSへリクエストする

## 概要
pythonで、kerberos認証でhdfsに接続する方法についてです。

pythonのhdfsパッケージを利用してリクエストする方法と、
hdfsパッケージを使わずにrequestsパッケージでWebHDFS REST APIを叩く方法の２つを書きます。

1. **[事前準備（共通）](#事前準備共通)**
1. **[hdfs + requests_kerberosを使う方法](#hdfs--requests_kerberosを使う方法)**
1. **[requestsパッケージでWebHDFS REST APIを叩く方法](#requestsパッケージでwebhdfs-rest-apiを叩く方法)**

以下の２つの環境で動作確認済です。

- Python 2.7.5
- Python 3.6.5


## ケルベロス認証とは
手順に入る前に、ケルベロス認証について、以下のサイトがとても参考になるので貼っておきます。
http://www.infraexpert.com/study/security18.html

表をそのまま拝借しますが、以下の用語は覚えておいたほうがよいです。

| ケルベロスの用語	| 説明 |
|:--|:--|
| KDC（Key Distribution Center） | サーバとユーザに関する信頼関係の情報を一括管理する中央データベース |
| AS（Authentication Server） | 認証サーバ。ユーザからの認証を受け付けるサーバ |
| TGS（Ticket Granting Server） | チケット発行サーバ。各サーバを利用するためのチケットを発行するサーバ |
| プリンシパル（principal) | KDCが認証を行うユーザやサーバのこと |
| レルム(realm) | 同じKDCの配下にあるシステムをグループとして定義する論理ネットワーク |


## 事前準備（共通）
こちらは、どちらの方法でも必要になります。

以下のファイルが必要になります。

- kerberos認証を利用するためのkeytabファイル
- SSLのCA証明書が必要な場合はCA証明書（必要な場合のみ）


## krb5のインストール
kerberosのインストールをします。

```shell-session
$ sudo yum install -y krb5-workstation
$ sudo yum install -y krb5-devel
$ sudo yum install -y krb5-libs
```

### 設定ファイル
/etc/krb5/krb5.confの設定を行います。

```conf:/etc/krb5.conf
[logging]
 # kdcのログ
 kdc = FILE:/var/log/krb5kdc.log
 # admin_serverのログ
 admin_server = FILE:/var/log/kadmind.log
 # その他のログ
 default = FILE:/var/log/krb5libs.log

# Kerberos 認証のデフォルト値を設定します。default_realm を設定する必要があります。
[libdefaults]
 default_realm = MYDOMAIN.COM
 dns_lookup_realm = false
 dns_lookup_kdc = false
 ticket_lifetime = 24h
 renew_lifetime = 7d
 forwardable = true

# 各 Kerberos レルムの KDC を設定します。1 つのレルムに複数の kdc を設定できます。
# デフォルトのポート 88 を使用する場合はポートの指定を省略できます。
# ドメイン名を大文字にしたものをレルム名にするのが慣例です。
[realms]
 MYDOMAIN.COM = {
  kdc = mykdc.mydomain.com
  # administrative server。通常はKerberosのマスターサーバー
  admin_server = mykdc.mydomain.com
  # プリンシパルとlocal user nameのマッピングルールを作れる
　　　　auth_to_local = RULE:[1:$1@$0](.*@MYDOMAIN.COM)s/@.*//
 }

 # 複数ある場合は更に記述
 EXAMPLE.COM = {
  kdc = kerberos.example.com
  admin_server = kerberos.example.com
 }


# ドメインやホストをrealmと結びつける設定。
# Active Directory ドメインを Kerberos レルムにマップします。
[domain_realm]
 .example.com = MYDOMAIN.COM
 example.com = MYDOMAIN.COM
```

各項目の説明をだいたい書いてありますが、詳しくは以下のサイトを参考にしてください。

- 参考
    - [Kerberos 認証の設定方法](https://docs.oracle.com/cd/E23941_01/E26841/html/kerberos-auth.html)
    - [Kerberos を使ってみる (RHEL/CentOS/Ubuntu編)](http://masahir0y.blogspot.com/2012/12/kerberos-v5.html)

- 各設定値の参考
    - [krb5.conf](https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-admin/krb5_002econf.html#krb5_002econf)
    - [logging](https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-admin/logging.html#logging)
    - [libdefaults](https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-admin/libdefaults.html#libdefaults)
    - [realms](https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-admin/realms-_0028krb5_002econf_0029.html#realms-_0028krb5_002econf_0029)


### kinit（kerobers認証をする）
以下のコマンドを叩くと、kdc.conf ファイルに指定されている最長有効期限値 (max_life)の時間、kerberos認証されている状態が続きます。
定期的に実行するために、cronなどに設定しておくと便利です。

```shell-session
$ kinit -kt /secrets/keytab username
```

### SSL CA証明書のパスを通す（必要な場合のみ）
接続にCA証明書が必要な場合は、以下の環境変数名でパスを設定しておく必要があります。

```shell-session
$ export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
```

これで事前準備は完了です。

## hdfs + requests_kerberosを使う方法
### hdfsパッケージのインストール
参考： http://hdfscli.readthedocs.io/en/latest/

hdfsパッケージ

```shell-session
$ pip install hdfs
```

kerberos認証に必要

```shell-session
$ pip install requests_kerberos
```

依存パッケージ

```shell-session
$ pip install requests
```

### ~/.hdfscli.cfgの設置
環境ごとに認証方式を変更したりできます。
以下の例では、prodではkerberos認証を使い、devでは使わない設定にしてあります。

参考： http://hdfscli.readthedocs.io/en/latest/api.html#module-hdfs.ext.kerberos

```ini:~/.hdfscli.cfg
# 全体的な設定
[global]
# defaultのalias
default.alias = dev
# kerberos moduleをautoloadする
autoload.modules = hdfs.ext.kerberos

# devの設定
[dev.alias]
# devのhdfsのurl
url = https://dev.namenode:port

# prodの設定
[prod.alias]
# prodのhdfsのurl
url = https://prod.namenode:port
# kerberos認証をする
client = KerberosClient
# pathを指定するときのTOP階層の定義
# この場合はhttps://prod.namenode:port/webhdfs/v1/がトップになる
root = /
```

### サンプルコード
ディレクトリの中身を確認する処理を作ります。
以下のコードを実行することで、結果を得られます。

環境によってはSSLのSubjectAltNameWarningを表示され場合がありますが、一時的にwarningを非表示にするには、`SSLのSubjectAltNameWarningを非表示にする場合は追記`の部分をコメントインしてください。

```py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import logging
import hdfs
from hdfs import Config

# SSLのSubjectAltNameWarningを非表示にする場合は追記
'''
import requests
from requests.packages.urllib3.exceptions import SubjectAltNameWarning
requests.packages.urllib3.disable_warnings(SubjectAltNameWarning)
'''

# logger
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

try:
    '''
    https://prod.namenode:port/webhdfs/v1/ の下から定義する
    ~/.hdfscli.cfgのrootの定義によって変わる
    '''
    path = 'user/sample'

    '''
    ~/.hdfscli.cfgで定義した環境のdev, prodのどちらかを入力
    prodを指定した場合、これだけでkerberos認証になる
    '''
    client = Config().get_client('prod')
    result = client.list(path)
    logger.debug(result)

except hdfs.util.HdfsError as e:
    logger.error(str(e))
```


## requestsパッケージでWebHDFS REST APIを叩く方法
こちらはhdfsパッケージは使わずに、requestsパッケージで[WebHDFS REST API](https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/WebHDFS.html)を叩く方法です。

### サンプルコード
以下もディレクトリの中身を確認する処理です。
こちらを実行することで結果が得られます。

```py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import logging
import os
import requests
from requests_kerberos import HTTPKerberosAuth

# SSLのSubjectAltNameWarningを非表示にする場合は追記
'''
from requests.packages.urllib3.exceptions import SubjectAltNameWarning
requests.packages.urllib3.disable_warnings(SubjectAltNameWarning)
'''

# logger
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)


method = 'GET'
url = 'https://prod.namenode:port/webhdfs/v1/user/sample?op=liststatus'

session = requests.Session()
response = session.request(
    method=method,
    url=url,
    timeout=60,
    headers={'content-type': 'application/octet-stream'},
    # これが必要
    auth=HTTPKerberosAuth()
)

logger.debug(response.status_code)
logger.debug(response.text)
```




## 参考
- http://hdfscli.readthedocs.io/en/latest/
- https://pypi.org/project/hdfs/