stnsバックエンドでユーザを一元管理

# はじめに
stnsを使ってユーザ管理を一元化したので、その構築手順や実装例を紹介します。

## STNSとは？
サーバにSSHログインする際に使用する公開鍵をtoml形式のファイル（もしくはstns I/Fを
持ったバックエンド）で提供することで、ユーザの設定状態を一元管理するためのツールです。
各サーバにOSユーザを作成する必要がなく、一元管理でき、利用者の増減（例えば、
入社、部署移動、退職など）を簡単に反映でき、運用の手間を減らせます。
[こちら](https://speakerdeck.com/pyama86/shi-dai-gaqiu-metastnstopu)を読めば開発の動機、背景がわかります。

## STNSバックエンドを自前で立てる理由
そもそもなぜ自前のバックエンドが必要かというと、stnsには標準でtoml形式でユーザ管理するバックエンドが
同梱されていますが、それを使う場合にtomlファイルをgit等で管理すること（`情報の一元管理`）はできますが、
結局各サーバにそのtomlを反映する必要があり、`状態の一元管理`は難しい。
（反映自動化してもいいのですが、サーバ数や環境増減の頻度によってはそれでもツライし。。）
そのため、利用者共通（全サービス／サーバ共通）で使うSTNSバックエンドが必要と考えました。

## やりたいこと
今回実装したSTNSバックエンド要件です。

* 複数サービス／環境を跨ってユーザ一元管理
* 登録ユーザは全ての環境にログインできる
* (QA／本番等の)環境ごとにsudo可能なユーザを設定できる

シンプルにしました。
複数の自社サービス（異なるNW環境で稼働）、全て内製、という弊社の環境としては、上記で十分という感じです。
要件変われば実装変えて対応できますし＾＾；

## やれなくてよいこと

* sudoパスワードは管理しない
* 組織構造はフラット（link_groupsは使用しない）

# 構成

## 環境概要

* インフラ構成
  * AWSを使います
  * STNSクライアント -> **`API Gateway + Lambda + DynamoDB`**

![stnsbackend.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/e1d51d5c-33fa-3abe-9f3c-9c65c1cdc28f.jpeg)


## DynamoDBテーブル構成

stns-osuser、stns-authorityという２テーブルで管理します。
２テーブルともにnameというカラムがプライマリキーとなります。

### stns-osuser

* ユーザごとに下記の情報を１レコードとして登録します

| カラム名 | 設定例 | 説明 | 必須 | 補足 |
| ------ | ----- | --- | ---- | --- |
|name|miyaz|ユーザ名|○|半角英数字の任意ユーザ名|
|id|10002|ユーザID|○||
|directory|/home/miyaz|ホームディレクトリ||指定しない場合は/home/{name}となります|
|keys|ssh-rsa　〜〜,ssh-rsa 〜〜〜|公開鍵||公開鍵をカンマ区切りで指定できます|
|link_users|deploy|公開鍵参照ユーザ||指定したユーザの公開鍵をkeysに加えます|
|setup_commands|["/bin/sh base_setup.sh","/bin/sh custom_setup.sh"]|stns-setup実行コマンド||stns-setupで実行したいコマンドをJSON(配列)形式で指定可能。詳細後述|
|setup_variables|{\"MAILADDRESS\":\"miyaz@example.com\"}|stns-setup実行環境変数||stns-setup実行時に設定するシェル変数をJSON(ハッシュ)形式で指定可能。詳細後述|

**※「値を指定しない」状態とは、レコードにそのカラムが存在しない、もしくはnullという文字列が指定された状態を指します**

### stns-authority

* 環境ごとにsudo可能なユーザを定義します
* 環境を識別する文字列をstnsクライアント設定(libnss_stns.conf)のBasic認証ユーザ名として指定します
* 一致するレコードがない場合はname=defaultというレコードを使用します(そのため、defaultレコードは必須)

| カラム名 | 設定例 | 説明 | 必須 | 補足 |
| ------ | ----- | --- | ---- | --- |
|name|ec-prod|環境を識別する文字列|○||
|superusers|miyaz|sudo可能ユーザ||カンマ区切りで指定されたユーザをadmin(10001)セカンダリグループとして追加します|

**※対象サーバのsudoersに、adminグループのNOPASSWORD sudo権限が付与されている前提となります(ansibleで設定してください)**
**※全ユーザはデフォルトでoperator(10000)グループに属します**

### stns-setupについて

stnsを使用してsshログインしたユーザはstns-setupというコマンドが使えるようになります。
このコマンド実行時に実際に何を実行するかをstnsバックエンドからのレスポンスで指定できます。
[参考事例](https://ten-snapon.com/archives/1544)

stns-osuserテーブルのsetup_commandsの指定有無に関わらず、デフォルトで　`~/.stns-setup.sh`を実行します。
実行時にコマンドに渡すシェル変数を、setup_varibalesで指定することができます。
例えば、setup_varibalesに`{"MAILADDRESS":"miyaz@example.com"}`を指定している場合に
stns-setupコマンドで実行されるコマンドラインは下記のようになります。

`env MAILADDRESS=miyaz@example.com /bin/sh /home/miyaz/.stns-setup.sh`

この場合、.stns-setup.sh内でMAILADDRESS変数を使って必要な処理を行うことができます。
その他にもSlackユーザ名やGitHubユーザ名などを登録しておくといろいろ使えるかもしれません。

例えば下記のようなことに使えます。

* 開発環境のアラートメール送信先定義を書き換えてアラートメールを自分宛てにする
* Slack通知スクリプトの通知先を自分宛てのSlackユーザ名に書き換える
* GitHubユーザ名を .gitconfigに設定して、gitでごにょごにょする

# セットアップ手順

*以降東京リージョン指定して作業すること*

## DynamoDB作成

* stns-osuserテーブル作成
  * プライマリキー[name](文字列)
  * その他デフォルト設定を使用
     * Auto Scaling 無効と表示されている場合はデフォルト設定を外して再作成
* stns-authorityテーブル作成
  * プライマリキー[name](文字列)
  * その他デフォルト設定を使用
* 上述のテーブル構成を参照し、必要なユーザ情報を登録しておくこと

## Lambda作成

* 関数を作成
  * 名前[stnsBackend]を指定
  * ランタイム[Node.js 8.10]を指定
  * カスタムロールとしてロール名[lambda_stnsbackend_execution]を入力
     * Actionには ["dynamodb:*"] を指定
     * Resourceには作成した２テーブルのARNを指定

* コードをzipアップロード
  * 事前に~/.aws/credentials設定とnodebrew等でnodejs(v8.10.0推奨)をインストールしておくこと
  * コードをgit clone
     * `git clone https://github.com/miyaz/stns-backend.git; cd stns-backend`
         * 上記[リポジトリへのリンク](https://github.com/miyaz/stns-backend) （メインのコードはstnsBackend.jsです）
  * 必要なnodeモジュール収集
     * `npm install`
  * デプロイを実行
     * `npm run gulp deploy`

## API Gateway作成

* 新しいAPIを作成
  * API名[StnsBackend]
  * 説明[stnsバックエンド用lambdaにproxyします]
  * エンドポイントタイプ[地域]

* リソースの作成
  * プロキシリソースとして設定のチェックを入れて作成
  * 統合タイプ[Lambda関数プロキシ]
  * Lambdaリージョン[ap-northeast-1]
  * Lambda関数[stnsBackend]

* APIのデプロイ
  * デプロイされるステージ[新しいステージ]
  * ステージ名[prod]

* APIキー作成
  * 名前[stns_backend_key]
  * APIキー[自動生成]
     * このキーはstnsクライアント(libnss_stns.conf)に指定するので控えておくこと

* 使用量プラン作成（任意）
  * 名前[stns_backend_plan]
  * スロットリング レート[100] バースト[200]
  * クォータは指定なし
  * APIステージの追加
     * API[StnsBackend] ステージ[prod]

* カスタムドメイン用ACM発行
  * ドメイン名[stns.example.com]（任意）
  * 検証方法[Eメールの検証]を選択
  * 証明書リクエスト実施後届いたメールのリンクをクリックして承認する

* カスタムドメイン名作成
  * ドメイン名[stns.example.com]
  * エンドポイントの設定[Regional]
  * ACM証明書で作成済み証明書を選択
  * ベースパスマッピング追加
     * パス[/]
     * 送信先[StnsBackend]
     * ステージ[prod]

## Route53設定

* example.comのホステッドゾーンを編集
  * stns.example.comのCNAMEにAPI Gatewayのドメインを指定

## サーバ設定

* sudoersに`%admin ALL=(ALL) NOPASSWD:ALL`を追記
* stnsを導入し、libnss_stns.confを下記のように設定

```config:/etc/stns/libnss_stns.conf
api_end_point = ["https://stns.example.com"]

# 環境を識別する名称を指定(stns-authorityテーブルname列で権限チェック)
user = "hoge-service"
password = "dummy"

ssl_verify = true
request_timeout = 3

[request_header]
x-api-key = "API GatewayのAPI認証に使用するキー"
```

## 動作確認

* stns-osuserテーブルに登録されたユーザでsshログインできること
* stns-authorityテーブルにsuperusers列に指定されたユーザでログインし、sudo lsが実行できること

# おわりに

実際に運用してみて、ユーザ追加、権限昇格、削除などが一瞬で反映できるので便利です！！
一元管理することでユーザ／権限の消し忘れなども減るためセキュリティ的にも向上します。
あとは、ユーザ追加、削除などをgitで管理して、pushされたらDynamoDBに反映って
とこまで作ればもっと楽できますね。（今後やる予定）