CLOVER🍀

That was when it all began.

Node.jsでMySQL 8.0のデフォルトの認証方式(caching_sha2_password)に対応するには、mysql2を使う

これは、なにをしたくて書いたもの?

MySQL 8.0になって、デフォルトの認証方式がcaching_sha2_passwordからmysql_native_passwordに変更されました。

For the server, the default value of the default_authentication_plugin system variable changes from mysql_native_password to caching_sha2_password.

MySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.4 (2018-01-23, Release Candidate)

Node.jsでMySQLに接続するにはmysqlが有名だと思うのですが、mysqlではcaching_sha2_passwordが認証方式になっている場合は
接続できません。

mysql2であれば大丈夫なのですが、今回ちゃんと見ておくことにしました。

MySQL 8.0のデフォルトの認証方式とNode.jsのMySQLドライバー

MySQL 8.0.4のリリースのとおり、デフォルトの認証方式がcaching_sha2_passwordからmysql_native_passwordに変更されました。

For the server, the default value of the default_authentication_plugin system variable changes from mysql_native_password to caching_sha2_password.

MySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.4 (2018-01-23, Release Candidate)

これは、認証プラグインの話になりますね。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 6.4.1 認証プラグイン

具体的には、MySQLのサーバーシステム変数default_authentication_pluginのデフォルト値がcaching_sha2_passwordになったという
変更です。

サーバーシステム変数 / default_authentication_plugin

caching_sha2_password自体も、MySQL 8.0で追加されたものですが。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 6.4.1.2 SHA-2 プラガブル認証のキャッシュ

以前はこちら(mysql_native_password)になります。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 6.4.1.1 ネイティブプラガブル認証

このため、caching_sha2_passwordに対応していないクライアントを使用する場合はデフォルトの認証方式をmysql_native_passwordとするか

default_authentication_plugin = mysql_native_password

ユーザー作成時にmysql_native_passwordを指定します。

mysql> create user [username] identified with mysql_native_password by '[password]';

で、Node.jsから接続する際によく使うmysqlはどうなっているかというと、caching_sha2_passwordには対応していないため上記のいずれかの
対応を行い、mysql_native_passwordに切り替える必要があります。

GitHub - mysqljs/mysql: A pure node.js JavaScript Client implementing the MySQL protocol.

issueもオープンのままです。

MySQL 8 incompatibilities · Issue #1959 · mysqljs/mysql · GitHub

以前に、近いことをこちらのエントリー内で書いたことがあります。

Promise-mysqlで、Node.jsからMySQLにアクセスする - CLOVER🍀

mysql2はどうかというと、caching_sha2_passwordに対応しています。

GitHub - sidorares/node-mysql2: fast mysqljs/mysql compatible mysql driver for node.js

ここで入ったみたいですね。

Mysql 8 fixes by sidorares · Pull Request #1021 · sidorares/node-mysql2 · GitHub

そもそもmysql2自体がmysqlの開発チームと共同で開発していることと、mysqlと主要機能な互換性はあるようなので今後はmysql2を使うべき
なのでしょう。

MySQL2 team is working together with mysqljs/mysql team to factor out shared code and move it under mysqljs organisation.

MySQL2 is mostly API compatible with mysqljs and supports majority of features.

では、ちょっと確認してみたいと思います。

環境

今回の環境は、こちら。

$ node --version
v16.16.0


$ npm --version
8.11.0

MySQLはこちら。172.17.0.2で動作しているものとします。

$ mysql --version
mysql  Ver 8.0.30 for Linux on x86_64 (MySQL Community Server - GPL)

MySQLサーバーの認証方式は、デフォルトのcaching_sha2_passwordとします。

mysql> show variables where variable_name = 'default_authentication_plugin';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| default_authentication_plugin | caching_sha2_password |
+-------------------------------+-----------------------+
1 row in set (0.02 sec)

準備とお題

MySQLに対して、以下のようにデータベースと2種類のユーザーを作成します。

-- データベース作成
mysql> create database example;


-- MySQL 8のデフォルトの認証方式(caching_sha2_password)のユーザー
mysql> create user user_sha2_auth@localhost identified by 'password';
mysql> create user user_sha2_auth@'%' identified by 'password';
mysql> grant all privileges on example.* to user_sha2_auth@localhost;
mysql> grant all privileges on example.* to user_sha2_auth@'%';


-- MySQL 8以前の認証方式(mysql_native_password)のユーザー
mysql> create user user_native_auth@localhost identified with mysql_native_password by 'password';
mysql> create user user_native_auth@'%' identified with mysql_native_password by 'password';
mysql> grant all privileges on example.* to user_native_auth@localhost;
mysql> grant all privileges on example.* to user_native_auth@'%';

ユーザーは、ひとつはデフォルトの認証方式(caching_sha2_password)、もうひとつはmysql_native_passwordを使ったものですね。

これらのユーザーに対して、mysqlおよびmysql2から接続してみたいと思います。

確認はテストコードで行い、TypeScriptで書くことにします。

Node.js+TypeScriptのプロジェクトを作成

まずはNode.jsプロジェクトを作成します。

$ npm init -y
$ npm i -D typescript
$ npm i -D prettier
$ npm i -D @types/node@v16
$ npm i -D jest @types/jest
$ npm i -D esbuild esbuild-jest
$ mkdir src test

この時点での依存関係は、こんな感じ。

  "devDependencies": {
    "@types/jest": "^28.1.6",
    "@types/node": "^16.11.48",
    "esbuild": "^0.15.2",
    "esbuild-jest": "^0.5.0",
    "jest": "^28.1.3",
    "prettier": "^2.7.1",
    "typescript": "^4.7.4"
  },

scriptsはこんな感じで用意しました。

  "scripts": {
    "build": "tsc --project .",
    "build:watch": "tsc --project . --watch",
    "typecheck": "tsc --project ./tsconfig.typecheck.json",
    "typecheck:watch": "tsc --project ./tsconfig.typecheck.json --watch",
    "test": "jest",
    "format": "prettier --write src test"
  },

各種設定ファイル。

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "lib": ["esnext"],
    "baseUrl": "./src",
    "outDir": "dist",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "esModuleInterop": true
  },
  "include": [
    "src"
  ]
}

tsconfig.typecheck.json

{
  "extends": "./tsconfig",
  "compilerOptions": {
    "baseUrl": "./",
    "noEmit": true
  },
  "include": [
    "src", "test"
  ]
}

.prettierrc.json

{
  "singleQuote": true
}

jest.config.js

module.exports = {
  testEnvironment: 'node',
  transform: {
    "^.+\\.tsx?$": "esbuild-jest"
  }
};

mysqlをインストールします。async、awaitを使いたかったので、Promise-mysqlもインストールしておきます。

GitHub - CodeFoodPixels/node-promise-mysql: A wrapper for mysqljs/mysql that wraps function calls with Bluebird promises.

$ npm i mysql promise-mysql

mysql2もインストール。

$ npm i mysql2

型定義もインストール。

$ npm i -D @types/mysql @types/mysql2

最終的に、依存関係はこうなりました。

  "devDependencies": {
    "@types/jest": "^28.1.6",
    "@types/mysql": "^2.15.21",
    "@types/node": "^16.11.48",
    "esbuild": "^0.15.2",
    "esbuild-jest": "^0.5.0",
    "jest": "^28.1.3",
    "prettier": "^2.7.1",
    "typescript": "^4.7.4"
  },
  "dependencies": {
    "mysql": "^2.18.1",
    "mysql2": "^2.3.3",
    "promise-mysql": "^5.2.0"
  }

テストコードを書いて確認する

あとは、テストコードを書いて確認するだけですね。

mysql。

test/mysql-auth.test.ts

import mysql from 'promise-mysql';

test('connect, caching_sha2_password authentication user, failure', async () => {
  try {
    await mysql.createConnection({
      host: '172.17.0.2',
      port: 3306,
      database: 'example',
      user: 'user_sha2_auth',
      password: 'password',
    });
  } catch (e) {
    const error = e as Error;
    expect(error.message).toBe(
      'ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client'
    );
  }
});

test('connect, mysql_native_password authentication user', async () => {
  const connection = await mysql.createConnection({
    host: '172.17.0.2',
    port: 3306,
    database: 'example',
    user: 'user_native_auth',
    password: 'password',
  });

  try {
    const [rows, field] = await connection.query('select 1 as result');
    expect(rows).toEqual({ result: 1 });
  } finally {
    await connection.end();
  }
});

mysqlの場合、caching_sha2_passwordを認証方式(というかデフォルト)にしているユーザーには接続できていません。
認証方式をmysql_native_passwordとしているユーザーへは接続できています。

mysql2。

test/mysql2-auth.test.ts

import mysql2 from 'mysql2/promise';

test('connect, user_sha2_auth authentication user', async () => {
  const connection = await mysql2.createConnection({
    host: '172.17.0.2',
    port: 3306,
    database: 'example',
    user: 'user_sha2_auth',
    password: 'password',
  });

  try {
    const [rows, fields] = await connection.execute('select 1 as result');
    expect(rows).toEqual([{ result: 1 }]);
  } finally {
    connection.end();
  }
});

test('connect, mysql_native_password authentication user', async () => {
  const connection = await mysql2.createConnection({
    host: '172.17.0.2',
    port: 3306,
    database: 'example',
    user: 'user_native_auth',
    password: 'password',
  });

  try {
    const [rows, fields] = await connection.execute('select 1 as result');
    expect(rows).toEqual([{ result: 1 }]);
  } finally {
    connection.end();
  }
});

mysql2の場合は、認証方式がcaching_sha2_passwordであってもmysql_native_passwordであっても接続できます。

これで、動作確認できました、と。

まとめ

mysqlとmysql2の2つで、MySQL 8.0のデフォルトの認証方式であるcaching_sha2_passwordに対応しているか見てみました。

対応しているのはmysql2のみで、今後のことを考えるとmysqlよりもmysql2を使っていった方がよさそうですね。

Ubuntu Linux 20.04 LTSにKeycloak 19.0をインストールする

これは、なにをしたくて書いたもの?

Keycloak 17.0.0.から、KeycloakはQuarkusベースになりました。ちょっと気になっていたので、そろそろ触ってみたいと思います。

QuarkusベースになったKeycloak

先に書いた通り、Keycloakは17.0.0からQuarkusベースとなり、これまでのWildFlyベースのディストリビューションは非推奨になりました。

The default Keycloak distribution is now based on Quarkus.

The WildFly distribution of Keycloak is now deprecated, with support ending June 2022.

Keycloak 17.0.0 released - Keycloak

以前からKeycloak.Xとして時々名前が出ていたなぁと思っていましたが、こちらが本流になりました、と。

Introducing Keycloak.X - Keycloak

Introducing Keycloak.X Distribution - Keycloak

Keycloak.X Update - Keycloak

WildFlyベースのディストリビューションのサポートは、2022年6月に終了(もう過ぎていますが…)で、Keycloak 17.0.0のリリースが
2022年2月なのでけっこうなスピードですね。

Webサイトの構成も変わっていて、「Guides」と「Docs」がありますが、基本的にGuidesを見ることになりそうですね。Docsの方に
残っているドキュメントもありますが。

また、Quarkusとは関係ないですが、Keycloak 17.0.0リリースの段階で各種クライアントアダプターも非推奨になったようです。

Deprecation of Keycloak adapters - Keycloak

こちらはメジャー・マイナーリリース終了が2022年9月、マイクロリリース終了が2022年12月となっています。

使用できるデータベースとしては、PostgreSQLやCockroachDB等のPostgreSQL互換のデータベースを残し、それ以外のデータベース
(MySQL、MariaDB、SQL Server、Oracle Database)のサポートは段階的に廃止していくようです。

Supported databases for the new Keycloak store - Keycloak

このあたりはちょっと注意ですね。

とりあえず、今回はあまり深く考えずにKeycloakを触ってみたいと思います。

環境

今回の環境は、こちらです。

Ubuntu Linux 20.04 LTS。

$ uname -srmvpio
Linux 5.4.0-124-generic #140-Ubuntu SMP Thu Aug 4 02:23:37 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.4 LTS
Release:        20.04
Codename:       focal

OpenJDKのバージョンは、17とします。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)

Keycloakをインストールして、管理ユーザーでログインする

Keycloakのインストール方法は、zipアーカイブ、コンテナイメージ、Kubernetes Operatorから選ぶことができます。

downloads - Keycloak

今回は、zipアーカイブを選択します。

ガイドとしては、こちらに沿って進めることになります。

OpenJDK - Keycloak

アーカイブをダウンロード。

$ curl -LO https://github.com/keycloak/keycloak/releases/download/19.0.1/keycloak-19.0.1.zip

展開して

$ unzip keycloak-19.0.1.zip

ディレクトリ内へ移動。

$ cd keycloak-19.0.1

ドキュメントを見ると、bin/kc.shというスクリプトでstart-devというコマンドで起動するみたいです。

OpenJDK / Start Keycloak

とりあえず、起動してみましょう。

bin/kc.sh start-dev

起動時に、なにか設定をしていそうなログが出力されています。

Updating the configuration and installing your custom providers, if any. Please wait.

8080ポートをリッスンして起動するようです。

2022-08-14 19:07:27,082 INFO  [io.quarkus] (main) Keycloak 19.0.1 on JVM (powered by Quarkus 2.7.6.Final) started in 8.905s. Listening on: http://0.0.0.0:8080

Quarkusのバージョンは、2.7.6.Finalですね。また、有効化されているExtensionもリストアップされます。

2022-08-14 19:07:27,090 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, narayana-jta, reactive-routes, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-health, smallrye-metrics, vault, vertx]

http://localhost:8080にアクセスしてみます。

こんなコンソールが表示されました。

最初に管理ユーザーを作成しなさい、ということみたいなので

OpenJDK / Create an admin user

作ってみます。

表示されている「Administration Console」というリンクをクリックして、先ほど作成した管理ユーザーのユーザー名とパスワードを入力して
ログイン。

ログインできました。

Realmとユーザーを作成する

次は、Realmとユーザーを作る手順になっています。

こちらに沿って進めてみましょう。

Realmは、Keycloakのテナントに相当するものだそうです。アプリケーションやユーザーに対する、Keycloak内での管理単位ですね。

A realm in Keycloak is the equivalent of a tenant. It allows creating isolated groups of applications and users.

デフォルトではmasterというRealmがあるようですが、これはKeycloakの管理用Realmであり、Keycloakの利用者が作成するアプリケーション
などで使用するものではないようです。

By default there is a single realm in Keycloak called master. This is dedicated to manage Keycloak and should not be used for your own applications.

実際、この状態で表示されているRealmはmasterとなっています。

Realmが表示されている箇所を選ぶと、「Create Realm」というボタンが表示されるのでこちらを選び

今回はdemoというRealmを作成してみます。

作成すると、demo Realmに移ります。

次に、ユーザーを作成してみましょう。左メニューの「Users」を選択して

適当に情報を入力して、ユーザーを作成。

できました。

パスワードを設定しないとログインできないみたいなので「Credentials」を選択して、「Set password」を押します。

パスワードを設定。「Temporary」をOn(デフォルト)にしておくと初回ログイン時にパスワードの変更を求められますが、今回はOffに
しておきます。

これで、保存。

管理ユーザーは、1度サインアウトします。

ログアウト後のURLはhttp://localhost:8080/realms/master/protocol/openid-connect/auth?client_id=...みたいになっているのですが、
http://localhost:8080/realms/[Realm名]/accountがアカウントコンソールと呼ばれるものらしいので、こちらにアクセスしてみます。

今回の場合は、http://localhost:8080/realms/demo/accountになりますね。アクセスすると、こんな表示になりました。

ヘッダーにある「Sign in」をクリックして、ログイン。

ログインできました。

とりあえず、今回はこのくらいの操作にしておきましょう。

あとは、この時点で少し気になることを調べておきます。

start-devコマンドとは?

Keycloakの起動時にstart-devというコマンドを使いました。明らかに開発モードのようなコマンドですが、こちらはどういう意味に
なるのでしょう?

こちらに説明がありました。Keycloakには2つの起動モードがあるようです。

Configuring Keycloak / Starting Keycloak

やはり、start-devというのはKeycloakを開発モードで起動するように指示するようです。

Configuring Keycloak / Starting Keycloak / Starting Keycloak in development mode

開発モードはKeycloakを初めて試してみる人、すぐに起動したい人を対象にしています。開発者にとって便利なデフォルト設定になっています。

開発モードでは、以下の状態となるそうです。

  • HTTPが有効
  • 厳密なホスト名解決が無効
  • ローカルキャッシュのみの利用(高可用性向けの分散キャッシュは使わない)
  • テーマおよびテンプレートのキャッシュが無効化される

もうひとつのモードは、プロダクションモードです。

Configuring Keycloak / Starting Keycloak / Starting Keycloak in production mode

こちらはstartコマンドで起動しますが、設定していない状態だとエラーになります。

$ bin/kc.sh start
ERROR: Failed to run 'start' command.
ERROR: You can not 'start' the server in development mode. Please re-build the server first, using 'kc.sh build' for the default production mode.

For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.

kc.sh buildでプロダクション環境向けに設定を行う必要があるようです。

そのあたりのガイドは、こちらですね。

Configuring Keycloak for production - Keycloak

今回はこの点はちょっと置いておいて、続きを見ていきます。

プロダクションモードでは、以下がデフォルトになります。

  • HTTPSが必須
  • ホスト名の必要が必要
  • HTTPS/TLSの設定が必要

このあたりを設定するためのガイドが、上記のページだということですね。

管理ユーザーをWebコンソール以外から作成したい

管理ユーザーはWebコンソールから作成しましたが、これをCLIで作成できたらなと思うものです。

こちらに記載がありました。

Configuring Keycloak / Setup of the initial admin user

初回起動時に以下の環境変数を指定することで、管理ユーザーを作成できるようです。

  • KEYCLOAK_ADMIN … 管理ユーザーのユーザー名
  • KEYCLOAK_ADMIN_PASSWORD … 管理ユーザーのパスワード

また、初期の管理者権限を持つユーザーがすでに存在している場合はこれらの環境変数を設定しているとエラーになるようです。

試してみましょう。

Keycloakをクリーンインストールした状態で

$ unzip keycloak-19.0.1.zip
$ cd keycloak-19.0.1

KEYCLOAK_ADMINおよびKEYCLOAK_ADMIN_PASSWORD環境変数を指定して起動。

$ KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=password bin/kc.sh start-dev

起動時のログに、指定したユーザーがmaster Realmに追加されたことが出力されました。

2022-08-14 20:07:14,921 INFO  [org.keycloak.services] (main) KC-SERVICES0009: Added user 'admin' to realm 'master'

この状態で、環境変数で指定した管理ユーザーでログインできます。

以降は、環境変数の指定は不要になりますが

$ bin/kc.sh start-dev

もう1度同じ環境変数を指定して起動すると

$ KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=password bin/kc.sh start-dev

エラーは出力されますが、起動に失敗するようなことはありません。これはドキュメント通りの挙動ですね。

2022-08-14 20:11:27,812 ERROR [org.keycloak.services] (main) KC-SERVICES0010: Failed to add user 'admin' to realm 'master': user with username exists

では、ユーザー名を変えるとどうなるでしょう。

$ KEYCLOAK_ADMIN=admin2 KEYCLOAK_ADMIN_PASSWORD=password2 bin/kc.sh start-dev

これはダメなようです。

2022-08-14 20:13:28,132 ERROR [org.keycloak.services] (main) KC-SERVICES0010: Failed to add user 'admin2' to realm 'master': user with username exists

この環境変数で作成可能なのは、あくまでmaster Realmに対する最初の管理ユーザーのようですね。

管理ユーザーを作成した後は、kcadmというスクリプトで操作できるようですが、それはまた今度。

まとめ

QuarkusベースになったKeycloakを触ってみました。Web UIはそうでもないですけど、構成やスクリプトまわりは相当変わっている気がしますね。

まだOpenID Connectなどは扱っていないので、そのあたりは徐々に進めていきましょう。