パスワード生成ポリシーについてのメモ（ＭｙＳＱＬ編）

# はじめに
他のパターンで書くケースがあるかどうかわからないが、「パスワード生成」に関してＴＰＯに応じたルールというかポリシーについて、常々思っていたことをメモしておく。
今回はＭｙＳＱＬということで、想定バージョンは８系以降とする。より古いバージョンについては見なかったこととするとして、もう５系については考慮しなくていいよね。

:::note warn
参考文献として本家ドキュメントのリンクを用意したので、合わせて参照してください。本ドキュメントが怪しくなっても本家ドキュメントなら安心できること請け合いです:-)。
:::

# パスワードに使用できる文字種について
本章ではパスワードに使用できる文字種について述べる。
広くパスワードの話をするなら、絵文字の使用もありうるわけで、ここではＭｙＳＱＬと銘打って、いわゆるアスキー文字のみを前提としてとりまとめる。

## 使用できる文字について
この手のは原則として英数字[^alphabet]および記号類、いわゆる「アスキー印字可能文字」が使用できる。が、この記号類というものがくせ者で、後述の記号については除外する。
もちろん `0` と `O`、`1` と `l` といった字形が似ている文字を除外するケースもあるが、本ユースケースではそこまでの対応は不要であろうと思われる。
この辺りがＴＰＯに応じてポリシーを考える必要がでてくる次第。

[^alphabet]: 大文字・小文字含み、それらは別の文字として区別する。

## 使用できない文字について
ＭｙＳＱＬ自身が設定できないと謳ってる文字もあるかもしれないが、下記の理由で使えない文字について具体的に明記する。

* プログラミング言語（ＳＱＬを含む）ユースで使えない／使いたくない
* シェルユースでの利用で使えない（上記ユースケースに対して追加の制限）
* ヒヤリハットが発生するので使わない方がよい

| 記号     | 記号名           | 使わない方がいい理由                               |
|:-:|:-|:-|
| ` `      | 空白             | 目視的にも文法的にも除外される危険がある           |
| `"`      | ダブルクォート   | 文字列を明示する文法に引っかかる                   |
| `#`      | シャープ         | ｍｙｓｑｌコマンドの入力で問題になる（後述）       |
| `$`      | ダラー           | 変数展開されてしまう可能性が多々ある               |
| `'`      | シングルクォート | 文字列を明示する文法に引っかかる                   |
| `/`      | スラッシュ       | 区切り記号としてパースされてしまうケースがある     |
| `@`      | アット           | 変数展開されてしまう可能性が希にある               |
| `=`      | イコール         | コピペ時に見逃されやすい（特に最初と最後）         |
| `\`      | バックスラッシュ | エスケープ文字として利用されているケースが多々ある |
| `` ` ``  | バッククォート   | 文字列を明示する文法に引っかかる                   |

`#` に関してだが、[バグレポート](https://bugs.mysql.com/bug.php?id=74691)を見ると、一部文字（`#`等[^bugfix]）は使えるようになったとあるが、バージョン依存であるため使用しているＭｙＳＱＬバージョンを確認すること。
詳しくは[リリースノート](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-24.html)（8.0.24の変更点）参照のこと（To enable use of spaces and ... の下り）。

[^bugfix]: リリースノートを読むと有効になった記号類として「spaces」という表現が気になる。` `（空白）だけでなく `\t`（タブ）や `\r`（ＣＲ）、`\n`（ＬＦ）も含んでいるのだろうか。`mysql_config_editor` のマニュアルには記載が無いので詳細は不明。

# パスワード入力方法の簡略化
各種ＭｙＳＱＬクライアント（`mysql` コマンドや `mysqldump` コマンド等）では `--login-path` オプションを指定することで、事前に設定されたパスワード（および接続情報）に基づき、データベースに接続できる。

ここでは設定、参照、利用について解説する。設定一覧・削除・変更については参考文献を参照のこと。

## パスワード設定
mysql_config_editor コマンド[^mysql_config_editor]を使用することで `--login-path` で指定されたキーワードに基づき、ユーザー名とパスワードを設定（保存）できる。

[^mysql_config_editor]: ＭｙＳＱＬクライアントとしてではなく、ＭｙＳＱＬサーバーとしてインストールされるケースがある。一例としてＲＨＥＬ系では `dnf -y install mysql-community-server` すると本コマンドがインストールされる。

下記の通りコマンドを実行すると、パスワード入力を求められるプロンプトが表示されるので、パスワードを入力してリターンキーを押す。

```console
mysql_config_editor set --login-path=admin --user=admin --password
```

この指定では更に `--host`（ホスト名）や `--port`（ポート番号）を指定することで、接続先データベースについても設定できる。

```console
mysql_config_editor set --login-path=admin --host=mysql.example.jp --port=3306 --user=admin --password
```

この時設定される情報は `~/.mylogin.cnf` ファイルに保存される。本ファイルの内容はカジュアルには読めない形式[^obfuscation]となっているが、`my_print_defaults` コマンド等で、パスワード入力を求められたりせず、容易に表示することができるので、そういう向きに期待してはいけない。

[^obfuscation]: 本機能を暗号化（ソースを読むと確かにＡＥＳで暗号化してるが）と称するケースもあるが、同ファイル中に暗号キーを含んでいるので、難読化が正しい。

## パスワード設定状況の確認
パスワードや接続先データベース等の設定状況を確認することができる。

```console
my_print_defaults --login-path=admin --show client
```

この時下記のように情報が出力される。

```console
$ my_print_defaults --login-path=admin --show client
--user=admin
--password=^nV3u-Uj620vP+eC
--host=mysql.example.jp
```

他のメンバーに直接、ＩＤ／パスワードを伝えるくらいなら、このコマンドを実行させることで、ＩＤ／パスワードを取得させた方が、外部に暴露せずに共有できる。
なおパスワードの先頭に `=` があるとコピペしにくい（`--password==...`）のがわかると思う。記号類は基本、コピペしにくいものであるが、よく見定めてコピペすること。

## 実際の利用方法
極シンプルに下記の通りで動く。

```console
mysql --login-path=admin mysql
```

# ＡＷＳのテンプレートでの指定例

```yaml
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  RDSMySQLDataBaseInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: MySQL
      MasterUsername: !Sub '{{resolve:secretsmanager:${RDSMySQLDataBaseInstanceAdminAccount}:SecretString:username}}'
      MasterUserPassword: !Sub '{{resolve:secretsmanager:${RDSMySQLDataBaseInstanceAdminAccount}:SecretString:password}}'
      :

  RDSMySQLDataBaseInstanceAdminAccount:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: RDSMySQLDataBaseAdminAccount
      GenerateSecretString:
        SecretStringTemplate: '{"username":"admin"}'
        GenerateStringKey: password
        PasswordLength: 16
        ExcludeCharacters: !Join [ '', [ ' ', '"', '#', '$', "'", '/', '@', '=', '\\', '`' ] ]

  RDSMySQLDataBaseInstanceAdminAccountAttachment:
    Type: AWS::SecretsManager::SecretTargetAttachment
    Properties:
      TargetType: AWS::RDS::DBInstance
      TargetId: !Ref RDSMySQLDataBaseInstance
      SecretId: !Ref RDSMySQLDataBaseInstanceAdminAccount
```

上記クラウドフォーメーションのテンプレート（ＹＡＭＬ形式）をデプロイすると、ＭｙＳＱＬインスタンスが作成され、自動的にパスワードが設定される。
自動的に設定されたパスワードはシークレットマネージャーの管理下で保存されるため、下記のように[^query]実行[^awscli]してリソース名を取得できる。

[^query]: ＪＭＥＳＰａｔｈは文字列として `'` または `` ` `` でくくることを要求しているので留意すること。
[^awscli]: プロファイル名の指定、リージョンの指定については本例では省略する。

```console: データベース接続情報を収納したリソース（ＡＲＮ情報）取得
$ aws cloudformation list-stack-resources --stack-name 上記テンプレートのスタック名 --output text --query "StackResourceSummaries[?LogicalResourceId == 'RDSMySQLDataBaseInstanceAdminAccount'][].PhysicalResourceId | [0]"
arn:aws:secretsmanager:リージョン名:アカウントＩＤ:secret:RDSMySQLDataBaseInstanceAdminAccount-XXXXXX
```

このリソース名から下記のようにコマンドを実行[^awscli]すると、ＪＳＯＮ形式で設定されたせつぞくじょうほうが引き出せる。

```console: データベース接続情報の取得
$ aws secretsmanager get-secret-value --secret-id arn:aws:secretsmanager:リージョン名:アカウントＩＤ:secret:RDSMySQLDataBaseInstanceAdminAccount-XXXXXX --output text --query 'SecretString'
{"password":"ランダムなパスワード","engine":"mysql","port":3306,"dbInstanceIdentifier":"ＤＢネーム","host":"ＤＢホスト名","username":"admin"}
```

# よくある質問とその答え

## Ｑ．`!` は対象にならないのですか？
Ａ．`!` でおかしくなるケースは確かにあります。シェルとそれ以外のプログラミング言語とは分けて考えた方がいいのですが、一般的なプログラミング言語では `!` にさほど意味はありません。
確かにシェルでは `!` が多大な影響を受けるケースがあります。しかしシェルで使いたいケースでは `--login-path` オプションを付けてコマンドを実行すればいいだけなので、`!` を明示的に拒否するほどの問題を感じません。
もちろん `!` を除外対象にすることは問題はありません。ここでは絶対的なポリシーというよりも、痛い目にあったので、こういうポリシーを提案してみました、という話でしかないためです。

シェルの話で言うと `&` にも捨てがたい（除外したい）魅力はあります。さらには `?` や `*` 等も除外したくなるでしょう。いずれも上記理由より、除外する意味はありません。

## Ｑ．パスワードの長さは？
Ａ．てきと～に決めればいいと思います。長すぎるのは問題ですが、短すぎるのも問題なので、基本的に１２～２４文字程度にしておくのが良いかと。
ユースケース的には何度もパスワード入力することは無いはずなので、極端に安全に倒したところ（１００文字等）で運用負荷はたかが知れてるでしょう。
長すぎた（１メガ文字等）場合のケースについては未検証なので、それについてはノーコメントで。

## Ｑ．パスワードのローテーションは？
Ａ．あー自分、その手の運用は事故しか起きてないので、個人的にオススメしてません。こういうのはより上位からのポリシー強制によるものなので、ここで考える範疇を超えます。
また自動ローテーションする仕組みが提供されているケースもありますが、影響範囲を十分に考慮の上、十分にテストしてから実践することをオススメします。
ていうか、記号を含む十分に長いランダムなパスワード使用してるなら、通常の運用では十分でしょ。パスワードを漏らさないような仕組みや枠組み考える方が先かと。

# 参考文献
- [mysql_config_editor — MySQL Configuration Utility](https://dev.mysql.com/doc/refman/8.0/en/mysql-config-editor.html) ／ [日本語版（機械翻訳）](https://dev.mysql.com/doc/refman/8.0/ja/mysql-config-editor.html)
- [my_print_defaults — Display Options from Option Files](https://dev.mysql.com/doc/refman/8.0/en/my-print-defaults.html) ／ [日本語版（機械翻訳）](https://dev.mysql.com/doc/refman/8.0/ja/my-print-defaults.html)
- [Assigning Account Passwords](https://dev.mysql.com/doc/refman/8.0/en/assigning-passwords.html) ／ [日本語版（機械翻訳）](https://dev.mysql.com/doc/refman/8.0/ja/assigning-passwords.html)
- [End-User Guidelines for Password Security](https://dev.mysql.com/doc/refman/8.0/en/password-security-user.html) ／ [日本語版（機械翻訳）](https://dev.mysql.com/doc/refman/8.0/ja/password-security-user.html)
- [Bug#74691: mysql_config_editor does not escape strings](https://bugs.mysql.com/bug.php?id=74691)
- [Changes in MySQL 8.0.24 (2021-04-20, General Availability)](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-24.html)
- [mysql_config_editor は シャープを含むパスワードをうまく扱えない](https://mita2db.hateblo.jp/entry/mysql_config_editor_は_シャープを含むパスワードをうまく扱えない)
- [mysqlのパスワードにビックリマーク!入れたらいろいろハマった話](https://qiita.com/makito/items/76e937d4f68db96f84f0)