AWS EC2からRDS(MySQL)にIAM認証で接続する

こんには。Webアプリ独学中の週末プログラマです。
EC2からRDS(MySQL)にIAM認証で接続するときのAWSの教科書は（[リンク](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.Java.html)）ですが、これの3番目の「DB インスタンスへの接続」のコードをawssdk2でやってみました。EC2はLinuxでOpenJDKが Corretto-17、RDSはMySQL8.0です。

```java
～(先頭のコード割愛)～
// 教科書より(右の方にsdk2のパッケージがあります)							  　sdk2では
// import com.amazonaws.services.rds.auth.RdsIamAuthTokenGenerator;			→software.amazon.awssdk.services.rds.model
// import com.amazonaws.services.rds.auth.GetIamAuthTokenRequest;			→software.amazon.awssdk.services.rdsの.RdsUtilitiesの中
// import com.amazonaws.auth.BasicAWSCredentials;							→software.amazon.awssdk.auth.credentialsのAwsBasicCredentials
// import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;			→software.amazon.awssdk.auth.credentialsのDefaultCredentialsProvider
// import com.amazonaws.auth.AWSStaticCredentialsProvider;					→software.amazon.awssdk.auth.credentialsのStaticCredentialsProvider

import software.amazon.awssdk.auth.credentials.*;
import software.amazon.awssdk.services.rds.model.*;
import software.amazon.awssdk.services.rds.*;
import software.amazon.awssdk.regions.*;				//リージョンをテキストで入れたら怒られたのでこれを使う。

public class E3daoInit {
	// RDSのトークン取得step0：AWS認証情報プロバイダチェーンの「4.共有 credentials および config ファイル」を設定済み(※1)。
	private static final DefaultCredentialsProvider cCreds = DefaultCredentialsProvider.create();
	private static final String AWS_ACCESS_KEY = cCreds.resolveCredentials().accessKeyId();
	private static final String AWS_SECRET_KEY = cCreds.resolveCredentials().secretAccessKey();

	public static void main(String[] args) {
		Connection cConn = null;
		try {
			// SSLの証明書をjdkのcacertsに入れたので、それをセット(※2)
			System.setProperty("javax.net.ssl.trustStore", "(cacertsへの完全パス)");
			System.setProperty("javax.net.ssl.trustStoreType", "JKS");
			System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

			// RDSのトークン取得step1～最後
			AwsBasicCredentials cTokenStep1 = AwsBasicCredentials.create(AWS_ACCESS_KEY, AWS_SECRET_KEY);
			StaticCredentialsProvider cTokenStep2 = StaticCredentialsProvider.create(cTokenStep1);
			GenerateAuthenticationTokenRequest cTokenStep3 = GenerateAuthenticationTokenRequest.builder()
				.credentialsProvider(cTokenStep2)
				.hostname("(RDSのエンドポイント)")
				.port(3306)
				.region( Region.AP_NORTHEAST_1 )
				.username("(IAMで定義したMySQL接続用ユーザ)")
				.build();
			RdsUtilities cTokenStep4 = RdsUtilities.builder()
				.build();
			String vToken = cTokenStep4.generateAuthenticationToken(cTokenStep3);

			//RDS接続用のプロパティのセット
			Properties cProperties = new Properties();
			cProperties.setProperty("verifyServerCertificate","true");
			cProperties.setProperty("useSSL", "true");
			cProperties.setProperty("user","(IAMで定義したRDS接続用ユーザ)");
			cProperties.setProperty("password",vToken);

			Class.forName("com.mysql.cj.jdbc.Driver");
			cConn = DriverManager.getConnection(
				"jdbc:mysql://(RDSのエンドポイント):3306", cProperties);
        
			～（後続処理割愛）～
```

※1:教科書（[リンク](https://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/credentials-chain.html)）にある認証情報プロバイダチェーンの4番目「共有 credentials および config ファイル」の設定として、IAMで作ったMySQL接続用ユーザのアクセスキーを~/.awsに保存しました。

※2:教科書（[リンク](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.Java.html)）では、SSL証明書を入れる一時的なキーストアを「作って呼び出す処理」が相当な行数で書いてありますが、コードを追っかけて行くうちに気を失いそうになったのでその部分は割愛し、jdk標準のキーストアにkeytoolで入れてしまいました。

＜出会った主なエラー＞
<details><summary>java.sql.SQLException: No suitable driver found for jdbc:～</summary>ぐぐると色んな人が教えてくださいますが、MySQLが8,0なのに、Class.forName("com.mysql.jdbc.Driver")にしてました。</details>
<details><summary>com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure</summary>教科書にSSL中間証明を入れろとありましたが、中間証明の時はこれが出ました。ルート証明だと出ません。(いろいろやっている最中だったので、勘違いの可能性があります)</details>
<details><summary>java.sql.SQLException: Access denied for user (MySQL接続用ユーザ)～</summary>今回最大の山場でした。なんのことはない、MySQL接続用ユーザのアクセスキーがきちんと入っていませんでした。最初は認証情報プロバイダチェーンの6番目(～IAM ロールが提供する認証情報）を適用するつもりで、RDS接続するIAMロールを作ってEC2に付与していましたが、よく考えてみれば、MySQLに接続するユーザを定義しないといけないので、6番目が使えるわけがなかった、というオチでした。</details>

＜未解決＞
<details><summary>java.sql.SQLSyntaxErrorException: SELECT command denied to user '(接続用ユーザ)～</summary>RDS側でshow full processlist;　とすると、接続用ユーザではなく、DBインスタンスのマスターユーザ名が出てきます。ホストもEC2の内部IPを設定したはずなのに%になっています。でも、接続用ユーザに設定した権限以外のことをしようとするとこれが出ます。(そういう仕様なのでしょうか？）</details>

ぐぐっても、ぐぐっても出てこないのは、普通はそんなことをしないのかもしれないと思いつつ、誰かの役に立つかもしれないので投稿しました。(昔一度投稿したもののリンクを変更したりしたものです)

　おわり