Closed41

CloudSQL に Vercel から接続する

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

このスクラップについて

このスクラップでは CloudSQL の MySQL インスタンスに Vercel から接続できるようにするまでに過程を記録していく。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

スクラップ作成の経緯

それまで PlanetScale を使っていたが無料プランがなくなってしまったので移行が必要になってしまった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

MySQL クライアントのインストール

コマンド
# インストール実行
brew install mysql-client
# インストール確認
/opt/homebrew/opt/mysql-client/bin/mysql --version
# PATH に追加したい場合は下記を実行
echo 'export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH"' >> ~/.zshrc
# ただし、将来 MySQL サーバーをインストールすることを想定して今はしない。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ローカルから接続確認

コマンド
/opt/homebrew/opt/mysql-client/bin/mysql \
  -u {username} \
  -p \
  -h {IP address} \
  --ssl-mode REQUIRED

無事に成功した。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

データベース作成

root でログインして下記を実行する。

SQL
CREATE DATABASE {dbname} CHARSET utf8mb4;
CREATE USER {username}@'%' IDENTIFIED BY '{password}';
GRANT ALL PRIVILEGES ON {dbname}.* TO {username}@'%';

作成したら mysql コマンドで接続できるかどうかを試すと良いかも。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Prisma から接続

.env.local
DATABASE_URL='mysql://{username}:{password}@{IP address}/{dbname}'
コマンド
dotenv -e .env.local -- npx prisma db push
dotenv -e .env.local -- npx prisma db seed

どうも接続できない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

初心にかえる

https://www.prisma.io/docs/getting-started/quickstart

https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-mysql

コマンド
mkdir hello-prisma
cd hello-prisma
npm init -y
npm install typescript ts-node @types/node --save-dev
npx tsc --init
npm install prisma --save-dev
npx prisma init --datasource-provider mysql
prisma/schema.prisma(追記)
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}

.env を変更してから下記を実行してみたがダメだった。

コマンド
npx prisma db push
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

データベース名などを短くしてみる

SQL
CREATE DATABASE testdb CHARSET utf8mb4;
CREATE USER testuser@'%' IDENTIFIED BY 'testpass';
GRANT ALL PRIVILEGES ON testdb.* TO testuser@'%';
コマンド
/opt/homebrew/opt/mysql-client/bin/mysql -u testuser -p -h xxx.xxx.xxx.xxx
.env
DATABASE_URL='mysql://testuser:testpass@xxx.xxx.xxx.xxx:3306/testdb'

ダメだった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

いったん SSL を OFF にしてみる

いけた!!!

こんなことに小一時間くらい費やしてしまった。

でも SSL は ON にしたいから引き続き調べよう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

何やら気になるボタンがある


証明書をダウンロードしてみる。

そして気になる記述もある。

sslcert=<PATH>: Path to the server certificate. This is the root certificate used by the database server to sign the client certificate. You need to provide this if the certificate doesn't exist in the trusted certificate store of your system. For Google Cloud this likely is server-ca.pem. Certificate paths are resolved relative to the ./prisma folder

https://www.prisma.io/docs/orm/overview/databases/mysql#configuring-an-ssl-connection

もしかして server-ca.pem をダウンロードして prisma ディレクトリに入れて sslcert を URL に追加すればいける?

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Next.js の pre-render

Next.js ではビルド時にもデータベースからソースコードを取得しようとしてくるらしい。

Next.js 14 であれば route.ts に下記を設定することで無効化できるようだ。

export const dynamic = 'auto';
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

server-ca.pem をバージョン管理する

あまり良いやり方ではないが prisma ディレクトリに入れてバージョン管理する必要がある。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

なんと動く方法を見つけた

諦めかけてたが適当にやったら動いた。

https://stackoverflow.com/questions/77083613/how-to-use-ssl-with-prisma-on-a-vercel-deployment

https://davidparks.dev/blog/planetscale-deployment-with-prisma/

Vercel で DATABASE_URL の環境変数を下記のように設定すれば良い。

mysql://testuser:testpass@xxx.xxx.xxx.xxx:3306/testdb?sslcert=/etc/pki/tls/certs/ca-bundle.crt

もしかすると証明書ファイルは何でも良くて、間違えていても設定されていれば SSL を使うようになっているのかも知れない。

試しに server-ca.pem の内容を削除して prisma db push しても大丈夫だった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

セキュリティ上の懸念

本来であれば正しい鍵を確認できなければ接続できない方が望ましい。

sslaccept=strict を使えば良いのかな?

また接続できる IP アドレスを制限した方が良い。

今回はまずは接続できたので良しとしよう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

まとめ

下記を設定すればとりあえずは接続できるようだ。

  • CloudSQL の設定で 0.0.0.0/0 からアクセスできるようにする。
  • DATABASE_URL の末尾に &sslcert=/etc/pki/tls/certs/ca-bundle.crt を追加する。

こんな簡単なことを調べるのに 3 時間も使ってしまったが誰かの役に立てば幸いです。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

恨み言

Prisma の接続エラーメッセージはわかりにくすぎる。

  • Error: P1000: Authentication failed against database server → sslcert を指定していない。
  • Error: P1001: Can't reach database server → sslcert のファイルが存在しない。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

サーバー証明書の中身

コマンド
openssl x509 -text -in prisma/server-ca.pem

Issuer と Subject がそれぞれ下記のようになっている。

dnQualifier=00000000-0000-0000-0000-000000000000, CN=Google Cloud SQL Server CA, O=Google, Inc, C=US

server-ca.pem の名前の通り認証局の証明書のようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Prisma のドキュメントをよく読む

sslcert=<PATH>: Path to the server certificate. This is the root certificate used by the database server to sign the client certificate. You need to provide this if the certificate doesn't exist in the trusted certificate store of your system. For Google Cloud this likely is server-ca.pem. Certificate paths are resolved relative to the ./prisma folder

これはクライアント証明書に証明するためにデータベースサーバーによって使用されるルート証明書です、とある。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

クライアント証明書の作成


「クライアント証明書を作成」ボタンを押す。

名前は「Vercel」とした。

  • client-key.pem
  • client-cert.pem
  • server-ca.pem

上記 3 点の内容が表示されるのでそれぞれコピー&ペースとして prisma ディレクトリ内に保存する。

client-key.pem だけは間違ってもバージョン管理しないように気をつけよう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

クライアント証明書の中身を見る

コマンド
openssl x509 -text -in prisma/client-cert.pem

Issuer がルート証明書の Issuer / Subject であり、Subject が CN=Vercel, O=Google, Inc, C=US となっている。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

接続チャレンジ

.env.local(例)
DATABASE_URL='mysql://testuser:testpass@xxx.xxx.xxx.xxx/testdb?sslcert=server-ca.pem&sslidentity=client-identity.p12&sslaccept=strict'
コマンド
dotenv -e .env.local -- npx prisma db push

下記のエラーメッセージが表示された。

Error: P1011: Error opening a TLS connection: MAC verification failed during PKCS12 import (wrong password?)

もう一度アイデンティティファイルを作り直してみよう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

OpenSSL 1.1 を使ってみる

コマンド
/opt/homebrew/opt/openssl@1.1/bin/openssl pkcs12 -export \
  -out prisma/client-identity.p12 \
  -inkey prisma/client-key.pem \
  -in prisma/client-cert.pem \
  -passout pass:

ダメだと思ったが一度ファイルを削除して再作成したら大丈夫だった。

もしかして上書きされない?

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

どうやらパスワードが必須らしい

pass とかでも良いので設定しておく必要がある。

OpenSSL 1.1 は必須ではない様子だったが -legacy パラメーターは必要なようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

エラーメッセージが変化した

Error: P1011: Error opening a TLS connection: The certificate was not trusted.

Prisma ドキュメントには気になる記述がある。

strict: Any missing value in the certificate will lead to an error. For Google Cloud, especially if the database doesn't have a domain name, the certificate might miss the domain/IP address, causing an error when connecting

GCP の場合、特にもしデータベースにドメイン名がない場合、証明書はドメインや IP アドレスの情報を含んでいないかもしれず、接続時にエラーが発生する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

mysql コマンドで試す

コマンド
mysql -u root -h xxx.xxx.xxx.xxx --ssl-ca=prisma/server-ca.pem --ssl-cert=prisma/client-cert.pem --ssl-key=prisma/client-key.pem

下記のエラーメッセージが表示されたら成功している。

ERROR 1045 (28000): Access denied for user 'root'@'xxx.xxx.xxx.xxx' (using password: NO)

client-cert.pem などの内容を削除して接続しようとすると失敗するのでしっかり動作しているようだ。

一方、Prisma の方は内容を削除しても普通に接続できてしまうのでちゃんと設定されているかどうか不安だ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

CloudSQL でクライアント証明書を必須にする

この状態で mysql コマンドで接続できるかどうかを試す。

コマンド
mysql -u root -p -h xxx.xxx.xxx.xxx

正しいパスワードを入力しても接続できなくなっている。

コマンド
/opt/homebrew/opt/mysql-client/bin/mysql -u root -p -h xxx.xxx.xxx.xxx --ssl-ca=prisma/server-ca.pem --ssl-cert=prisma/client-cert.pem --ssl-key=prisma/client-key.pem

クライアント証明書を指定すると接続できるようになった。

Prisma の方はどうだろう?

sslcert を指定するだけではアクセスできなくなった。

相変わらず server-ca.pem の中身はどうでも良いようだ。

この状態で CloudSQL で証明書を削除してアクセスしてみる。

そうすると下記のエラーが表示されるようになった。

Error: P1011: Error opening a TLS connection: unknown Cert Authority

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Vercel でクライアント証明書を使う方法

環境変数で証明書と秘密鍵の PEM 本文を設定し、起動時に tmp ディレクトリに client-identity.p12 を出力する必要がありそうだ。

このスクラップは21日前にクローズされました