AWS環境 で Teleport クラスタの構築

この記事は [KLab Advent Calendar 2018](https://qiita.com/advent-calendar/2018/klab/) 18日目 の記事です。

## はじめに
みなさんはサーバーへのアクセス管理をどのように実施していますか？
SSHの鍵ファイルによるアクセス制限では、サーバー数やメンバー数の規模が大きくなるに連れて、公開鍵の設定など非常にコストがかかってきてしまうかと思います。
そこで今回は [Teleport](https://gravitational.com/teleport) を用いたサーバーアクセスと、その AWS 環境での構築方法をご紹介します。

※ : 実際に運用する際にはAnsibleなどで自動化することになるかと思いますが、この記事ではどういう設定を入れていくことになるのか備忘録も兼ねて一つづつ説明していきます。

## Teleport By GRAVIRATIONAL とは
『Gravitational Teleportは、SSHまたはKubernetes API経由でLinuxサーバーのクラスタへのアクセスを管理するためのゲートウェイです。従来のOpenSSHの代わりに使用することを目的としています。』
https://gravitational.com/teleport/docs/intro/

Teleportを利用すると、認証サーバでアカウントを登録するだけでアクセス開始時に各サーバへのセッション鍵が自動で配布されてアクセス可能になるため、アクセス管理が非常に簡単になります。
これは **ID**、**パスワード**、**ワンタイムパスワード** を用いたサーバーアクセスのデモです。
![ログイン＆セッション操作](https://qiita-image-store.s3.amazonaws.com/0/32726/fde1790c-e683-2c55-507b-b35398bda1f0.gif)
具体的には 「ログイン > ユーザ名を指定してサーバログイン > ファイル編集 > pythonインストール」 を行っています。

このようにクラスタへログインするだけで一元的にアクセス先のサーバーへログインし、コンソール操作が可能になります。
さらに、以下のように記録された操作のログを確認することが可能になります。
![ログ再生](https://qiita-image-store.s3.amazonaws.com/0/32726/2d343927-b420-5a04-da0c-b09e4d3947ca.gif)
上記の例ではWebからのアクセスでしたが、ssh同様コンソールからのアクセスや、コンソールでのログチェックも可能になります。

### Teleportのアーキテクチャ
Teleportではアクセス対象のサーバー(Nodeサーバー)を含めて３種類のサービスが動くサーバーで役割を分担してクラスタを構築します。
![アーキテクチャ](https://gravitational.com/teleport/docs/img/overview.svg)
https://gravitational.com/teleport/docs/architecture

- Authサーバ： 認証情報の保存やクラスタ情報、ログ管理など中心的なサービスを行います。
- Proxyサーバ: 外部からのアクセスを中継するサーバです。ログイン情報を受け取ってAuthサーバへ認証問い合わせをしたり、Nodeサーバへセッションをつなげたりします。
- Nodeサーバ: 実際に管理対象となる個別のサーバです。

また、今回の構築ではオープンソースの無料版を用いますが、[有償版](https://gravitational.com/teleport/docs/enterprise/)では以下の２点が強化されることでさらに便利になります。

- ロールベースでのユーザー権限及びアクセスサーバ制御
- シングルサインオンでGoogleやGitHubなど外部認証システムによるアカウント管理


Teleportについては先日の[こちら](https://blog.animereview.jp/tereport-howto/)の記事も参考になります。

## 構築手順
これからAWS環境での基本的なクラスタ構築方法について解説していきます。　
今回、認証サーバでのデータ保存については`DynamoDB` と `S3` を用います。これにより、認証情報のバックアップとともに、複数の認証サーバ同士を連携させることで認証サーバの冗長化を狙った構築を行います。

※: 手順において、VPCやセキュリティグループの設定方法などのAWSの操作方法については割愛します。

### ソフトウェアバージョン
想定環境としては以下のソフトウェアバージョンを利用します
EC2 : Ubuntu18.04 (ami-06e7b9c5e0c4dd014)
Teleport : v3.0.1


### セキュリティグループの作成
Teleportでは各サービスでいくつかのポートを利用します。クラスタの各サーバーへ割り当てるためのセキュリティグループを作成してください。
以下の表は、デフォルト設定で利用するポート番号を示しています。

|サービス|ソース|Port|説明|
|:---:|:---:|:---:|:--|
|認証|プロキシ、ノード|3025|認証サービスへプロキシサービスやノードサービスがアクセスするPort|
|プロキシ|クライアント|3023|プロキシサービスでクライアントからの操作セッションを受け取るPort。ここでの通信をノードサービスへ転送します|
|プロキシ|その他クラスタ(割愛)|3024|プロキシサービスが他のTeleportクラスタとアクセスするPort(今回は使用しません)|
|プロキシ|クライアント|3080|プロキシサービスがClientのWebアクセス待受をするPort|
|ノード|プロキシ|3022|プロキシサービスから転送されたクライアントからのセッションをノードサービスが受け取るPort|

基本設定では上記の通りですが、この後説明する個別サービス設定で変更することも可能です。
その他、構築時の操作用SSHアクセス用セキュリティグループも必要に応じて設定してください。

### 認証サーバ用IAMロール作成
今回の構築では認証サーバで管理するデータをDynamoDBとS3へ保存します。
アクセス権限設定のためにEC2へのIAMロールを付与するため、以下の設定をしたIAMロールを作成してください。

```poricy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllDynamoActionsOnTeleportAuth",
            "Effect": "Allow",
            "Action": "dynamodb:*",
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-2:ACCOUNT:table/STATE_TABLE",
                "arn:aws:dynamodb:ap-northeast-2:ACCOUNT:table/EVENTS_TABLE"
            ]
        },
        {
            "Sid": "AllS3ActionsOnTeleportAuth",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::LOGS_BUCKET",
                "arn:aws:s3:::LOGS_BUCKET/*"
            ]
        }
    ]
}
```
以下4点は適宜置き換えをお願いします

|変数名|説明|
|:----:|:---|
| ACCOUNT | AWSのアカウントID |
| STATE_TABLE | クラスタ状態を保存するDynamoDBのテーブル名 |
| EVENTS_TABLE | ログインやセッション情報などイベントを保存するDynamoDBのテーブル名 |
| LOGS_BUCKET | 操作の各録画データを保存するバケット名 |

https://gravitational.com/teleport/docs/admin-guide/#using-dynamodb

### 認証サーバ構築
ここまでで作成した認証サーバー用のセキュリティグループとIAMロールを付与して、認証サーバ用のEC2インスタンスを起動してください。

起動したら、まずはTeleportのコマンドをインストールします。

```インストール手順.
$ wget https://get.gravitational.com/teleport-v3.0.1-linux-amd64-bin.tar.gz
$ tar xvf teleport-v3.0.1-linux-amd64-bin.tar.gz
$ cd teleport/
$ sudo ./install
```

この手順により、以下の3つのファイルが `/usr/local/bin/` にインストールされます。クライアントと各サーバで利用するコマンドが違いますが、この場合はすべてのコマンドがインストールされます。コマンドをpathに配置するだけなので、必要なコマンドのみを別の場所に配置しても構いません。

| コマンド | 概要 | 対象マシン |
|:-:|:-:|:-:|
| teleport | 各ノードで可動するサービスのコマンド | 認証、プロキシ、ノード |
| tctl | 認証サーバでアカウントやノードを追加するときに用いるコマンド |  認証 |
| tsh  | クライアントからTeleportクラスタへアクセスするコマンド  | クライアント  |

次に設定ファイルを作成します。 Teleportはサービスを起動する際のオプションや設定ファイルに記述した内容に従って、 `認証、プロキシ、ノード`　それぞれの機能を同時に実行することができます。
`teleport configure` を実行することで、１台で全機能を実行するサンプルの設定ファイルを確認できます。デフォルトでは設定ファイルは `/etc/teleport.yaml` に配置します。
認証サービス用の設定ファイルの例は以下になります。

```/etc/teleport.yaml
teleport:
  nodename: auth-1
  # data_dir: /var/lib/teleport # オンプレなど認証サーバに直接保存する場合はこちら。多重化はNFSなどで。
  pid_file: /var/run/teleport.pid
  log:
    output: stderr
    severity: INFO

  storage: # 今回はクラスター情報などをDynamoDBに保存します
    type: dynamodb
    region: REGION
    table_name: STATE_TABLE
    audit_events_uri: dynamodb://EVENTS_TABLE
    audit_sessions_uri: s3://LOGS_BUCKET

auth_service:
  enabled: "yes" # 認証サーバの機能を有効化
  cluster_name: "main" # 任意のクラスタ名
  authentication:
    type: local
    second_factor: otp # ワンタイムパスワードによる２段階認証。有償版ではその他の認証も可能
  listen_addr: 0.0.0.0:3025 # 自分のLocal IPにするとtctlでの127.0.0.1を受け付けなくなる
  session_recording: "node" # ログをnodeから転送する。特殊な場合を除いてproxyからの設定は推奨されません。
  public_addr: [ "SELF_URL:3025" ] # ノードなどからどういう名前でアクセスされるか

ssh_service:
  enabled: "no"

proxy_service:
  enabled: "no"
```
`ssh_service` の設定がノード、　`proxy_service` の設定がプロキシモードのときに必要になる設定箇所です。　`enabled` の項目がデフォルトで `yes` になっているため明示的に無効化します。

https://gravitational.com/teleport/docs/admin-guide/#configuration-file

設定ファイルができたので、サービスとして起動するためにsystemdの設定を行います。

```/lib/systemd/system/teleport.service
[Unit]
Description=Teleport SSH Service
After=network.target

[Service]
Type=simple
Restart=on-failure
ExecStart=/usr/local/bin/teleport start --config=/etc/teleport.yaml
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/var/run/teleport.pid

[Install]
WantedBy=multi-user.target
```

`teleport start --config=/etc/teleport.yaml` で、設定ファイルを指定して起動するよう設定しています。あとはサービスの起動 `sudo systemctl start teleport.service` をするだけで認証サーバの構築は完了です。インスタンス作成時にIAMロールで権限付与しているため、DynamoDBの各テーブルやS3バケットはTeleport認証サービスの初回起動時に自動的に作成されます。
また、今回は認証サーバを複数台で構築するため同じ設定でもう１台認証サーバを作成しました。


### プロキシサーバ構築
次にプロキシサーバを構築します。認証サーバと同様にインスタンスを作成してください。違う点は、IAMロールが不要なのと外部アクセスを受け付けるセキュリティグループの違いです。

インスタンスを作成したらSSL証明書を取得します。public IPにドメインを設定して証明書を取得してください。（証明書取得方法については割愛します。私は [Let's Encrypt](https://letsencrypt.org/) を利用しました。
証明書を取得したら、認証サーバ同様にTeleportのインストールを行い以下の設定ファイルを作成してください。

```/etc/teleport.yaml
teleport:
  nodename: proxy
  data_dir: /var/lib/teleport
  pid_file: /var/run/teleport.pid
  auth_servers: # 認証サーバのIPやURLを設定してください
  - AUTH_SERVER_1:3025
  - AUTH_SERVER_2:3025
  connection_limits:
    max_connections: 1000
    max_users: 250
  log:
    output: stderr
    severity: INFO

auth_service:
  enabled: "no"

ssh_service:
  enabled: "no"

proxy_service:
  enabled: "yes"
  listen_addr: 0.0.0.0:3023 # ターミナルのセッション通信するときにアクセスを受けるアドレスです
  web_listen_addr: 0.0.0.0:3080 # Webの認証ページでアクセスを受けるアドレスです
  # tunnel_listen_addr: 0.0.0.0:3024 # クラスタ連携用のアドレスです。今回は不要なので設定しません
  # 証明書のパスを設定してください。これはLet's Encryptの例です
  https_key_file: /etc/letsencrypt/live/proxy.example.com/privkey.pem
  https_cert_file: /etc/letsencrypt/live/proxy.example.com/fullchain.pem
```
上記のように、認証サーバ同様にプロキシサービスのみ起動するようにして、証明書のパスを指定した設定ファイルを作成します。
https://gravitational.com/blog/letsencrypt-teleport-ssh/

設定ファイルができたら、 **認証サーバ** でプロキシサーバ追加のための招待トークンを発行します。

```認証サーバでプロキシ追加リクエスト.
$ sudo tctl nodes add --roles proxy --ttl=5m
The invite token: XXXXXXX_INVITE_TOKEN_XXXXXXX
This token will expire in 5 minutes
```

トークンをコピーし、プロキシサーバで招待トークンを付与してTeleportサービスを起動します。

```プロキシサーバでクラスタ参加起動.
$ sudo teleport start --token=XXXXXXX_INVITE_TOKEN_XXXXXXX --config=/etc/teleport.yaml
```
接続が確立したら、一度プロセスを終了して構いません。接続のための認証情報をプロキシサーバのローカルに保存するため、次回以降の起動ではトークンは必要ありません。
なので、あらためてTeleportサービスを起動します。認証サーバ同様にsystemdの設定ファイルを作成してサービス起動してください。
これでプロキシサーバの構築は完了です。


### ノードサーバ構築
クライアントからアクセスするノードサーバを構築します。
証明書設定が不要な以外は、基本的にプロキシサーバと同様の手順です。
設定ファイルとクラスタへのノード追加のコマンドは以下のようになります。

```/etc/teleport.yaml
teleport:
  nodename: node-1
  data_dir: /var/lib/teleport
  pid_file: /var/run/teleport.pid
  auth_servers: # 認証サーバのIPやURLを設定してください
  - AUTH_SERVER_1:3025
  - AUTH_SERVER_2:3025
  log:
    output: stderr
    severity: INFO

auth_service:
  enabled: "no"

ssh_service:
  enabled: "yes"
  listen_addr: 0.0.0.0:3022
  public_addr: NODE_1:3022 # プロキシからセッションを作るときにアクセスするアドレス
  commands: # ノードに自由にラベルを付与することができます
  - name: hostname # ラベルキー名
    command: [/bin/hostname] # 値取得コマンド
    period: 1m0s # 更新間隔
  - name: arch
    command: [/bin/uname, -p]
    period: 1h0m0s

proxy_service:
  enabled: "no"
```

管理対象となるノードサーバは多数になることが想定されるため、ラベルを付与することができます。
今回は、ホスト名とアーキテクチャをラベルに設定しました。必要に応じて環境変数などを設定してください。
https://gravitational.com/teleport/docs/admin-guide/#labeling-nodes

```認証サーバでノードサーバ追加リクエスト.
$ sudo tctl nodes add --roles node --ttl=5m
The invite token: XXXXXXX_INVITE_TOKEN_XXXXXXX
This token will expire in 5 minutes
```

```ノードサーバでクラスタ参加起動.
$ sudo teleport start --token=XXXXXXX_INVITE_TOKEN_XXXXXXX --config=/etc/teleport.yaml
```

これらの設定が完了したら、サービス登録してノードサーバの設定は完了です。


## ユーザー登録

クラスタの構築が済んだら、ログインするユーザーを登録します。Teleportを利用するユーザ名と、そのユーザでアクセス可能なOSユーザ名を指定して、ユーザー登録コマンド `tctl users add ユーザ名 OSユーザ名リスト` を実行します。


```ユーザー追加コマンド.
$ sudo tctl users add hoge ubuntu,hoge
Signup token has been created and is valid for 1 hours. Share this URL with the user:
https://proxy.example.com:3080/web/newuser/XXXXXXXXXXXXXXXXXXXXXXXX
```

表示されたアドレス(プロキシアドレス箇所は置き換えが必要になります)へブラウザでアクセスすると次のようなページが表示されます。パスワードの登録とQRコードを読み込んでワンタイムパスワードの設定を行ってください。
 
![ユーザー追加.png](https://qiita-image-store.s3.amazonaws.com/0/32726/e5d34f33-57d1-233d-8336-298079e1c6c4.png)

必要事項を入力して、 `Sign up` でユーザー追加完了です。
https://gravitational.com/teleport/docs/admin-guide/#adding-and-deleting-users

お疲れ様でした。
これで無償版でのTeleportクラスタの構築、及びユーザー設定完了です。
`https://proxy.example.com:3080/web/login` へアクセスしてログインしてみてください。
冒頭の紹介のようにノードを選択して操作したりセッションの記録が確認できます。


## 展望
構築マニュアルによると、ClientとProxyサーバの間にELBを配置してProxyサーバを多重化したり、Proxy, NodeとAuthサーバとの間にELBを配置することも可能なようです。こういったロードバランサを仲介させる構築をすることで、よりインスタンスの入れ替えをしやすい構築ができると思います。
今回の検証中、CLBをプロキシの前に利用することでSSL証明書をACMの証明書にできるのではないかと試行錯誤しましたが、うまくセッションを維持できず稼働させることができませんでした。また今度チャレンジしてみたいと思います。


## おわりに
AWS環境におけるTeleportクラスタの構築方法をご紹介しました。
OpenSSHと比較してのセキュリティ的信頼度など、評価しなければならない事は多々あると思いますがアカウント管理やログ管理など便利そうなツールではないでしょうか？

さらに有償版では、認証にGoogleアカウントなど外部の認証システムを利用することや、ユーザーでログの表示など操作可能な権限や、ログイン可能なサーバーグループなどの制限も可能になるとのことです。
試す機会があればぜひまた記事にしてご紹介していきたいです。
https://gravitational.com/teleport/docs/enterprise/

サーバーへのアクセス方法など、企業としてどのようにやっているかあまり宣伝するようなものではないと思うのですが、ぜひ皆さんのところでも利用してみたお話などコメントいただければと思います。

最後までお読みいただきありがとうございました。