golang で AWS Lambda から IAM 認証を利用して RDS に接続するのにめっちゃハマった話

2017年5月ぐらいから IAM 認証で RDS の MySQL or Aurora に接続できるようになっていて、これを利用すれば Lambda から高速にデータベースアクセスができてハッピーになれそうということで試した。
で、aws-sdk-go をつかってアクセスしようとしたけど

Error 1045: Access denied for user 'iam_user'@'ec2-xx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com' (using password: YES)

となって全然繋がらなかった。

コードは以下のような感じ

package main

import (
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/service/rds/rdsutils"

	"fmt"

	"crypto/x509"
	"io/ioutil"

	"crypto/tls"

	"database/sql"

	"github.com/go-sql-driver/mysql"
)

var certFile = "./rds-combined-ca-bundle.pem"
var dbEndpoint = "foo.bar.ap-northeast-1.rds.amazonaws.com"
var dbUser = "iam_user"
var dbName = "iamtest"
var dbPort = "3306"
var awsRegion = "ap-northeast-1"

func RDSConnect() (string, error) {
	awsCredentials := credentials.NewEnvCredentials()
	authToken, err := rdsutils.BuildAuthToken(
		dbEndpoint,
		awsRegion,
		dbUser,
		awsCredentials,
	)
	if err != nil {
		panic(err.Error())
	}
	dsnStr := fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s?tls=true&allowCleartextPasswords=true",
		dbUser, authToken, dbEndpoint, dbPort, dbName,
	)

	db, err := sql.Open("mysql", dsnStr)
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	return "ok", nil
}

func RegisterTLSConfig() {
	rootCertPool := x509.NewCertPool()
	pem, err := ioutil.ReadFile(certFile)
	if err != nil {
		panic(err.Error())
	}
	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
		panic(err.Error())
	}
	err = mysql.RegisterTLSConfig("custom", &tls.Config{
		RootCAs: rootCertPool,
	})
	if err != nil {
		panic(err.Error())
	}
}

func main() {
	RegisterTLSConfig()
	lambda.Start(RDSConnect)
}

結論から言うと、rdsutils.BuildAuthToken の引数の dbEndopoint は hostname:port の形式で書かないとダメ。

	authToken, err := rdsutils.BuildAuthToken(
		fmt.Sprintf("%s:%s", dbEndpoint, dbPort),
		awsRegion,
		dbUser,
		awsCredentials,
	)

とすれば動く。

そしていまドキュメントを再読したら

rdsutils - Amazon Web Services - Go SDK

Endpoint consists of the hostname and port, IE hostname:port, of the RDS database.

とバッチリ書いてあった!!
と言うわけでめちゃくちゃハマった話でした。

その他の注意点

  • RDS への IAM 認証は 1秒間に新規で 20接続までしかできない
  • rds-db-connect という特殊な IAM ポリシーを作って Lambda にアタッチしなければならない
  • データベース上で、AWSAuthenticationPlugin を有効にしたユーザーを作らなければならない
  • TLS 接続が必須で、rds-combined-ca-bundle.pem を指定しなければならない

などなど色々と大変。
接続数20はハードリミットで変更できないらしく、現状はかなり限定的な要件でしか使えないなーという印象です。

詳しくは以下を参照のこと

docs.aws.amazon.com