2020-12-23

GoogleのAPIキー(json)を暗号化してリポジトリで管理する(crypto-js)

Gatsby.jsで記事の閲覧数ランキングみたいなページを作ろうとした場合、google analyticsのAPIを叩いて閲覧数が多いページを取得して、そのデータをよしなにいじくりまわしてページを作ると思いますが、ちょっと色々問題があって単純にリポジトリで管理できなかったので暗号化して管理するようにしてみたお話です

前提情報

まずAPIを叩くために必要な秘匿情報ですが、google developer consoleから以下のようなjsonでダウンロードされます

{
  "type": "XXX",
  "project_id": "PID",
  "private_key_id": "KEY_ID",
  "private_key": "-----BEGIN PRIVATE KEY-----HOGEHOGE-----END PRIVATE KEY-----\n",
  "client_email": "CLIENT_EMAIL",
  "client_id": "CID",
  "auth_uri": "PATH",
  "token_uri": "PATH",
  "auth_provider_x509_cert_url": "PATH",
  "client_x509_cert_url": "PATH"
}

そしてこのjsonを使うnode.jsのコードですが、googleのAPIにアクセスするために googleapis パッケージが用意されているのでそれを使います
具体的には以下のような感じです(今回はクライアント作成部分だけ関心があるので、そこだけ掲載)

const { google } = require('googleapis')
const client = await google.auth.getClient({
  keyFile: "./api_key.json",
  scopes: 'https://www.googleapis.com/auth/analytics.readonly'
})

ここでちょっと面倒なのは「秘匿情報はjsonファイルへのパスを指定しなければならない」ということです
jsonの中身を頑張って環境変数で置き換えても、最終的にはjsonファイルとしてどこかに配置して、それを指定する必要があります

かといってこのjsonをリポジトリにそのまんまあげるわけにもいかないし困った

暗号化してリポジトリへ

ということで暗号化です(元に戻す必要があるので当然ハッシュ化ではダメです)
node.jsには crypto-js というパッケージがあるので、それを使って暗号化したうえでリポジトリで管理することにしました

まずは暗号化して適当なファイルで管理したいので、node.jsで暗号化するコードを書きます

encrypt_api.js

const crypto = require("crypto-js")
const key = {
  "type": "XXX",
  "project_id": "PID",
  "private_key_id": "KEY_ID",
  "private_key": "-----BEGIN PRIVATE KEY-----HOGEHOGE-----END PRIVATE KEY-----\n",
  "client_email": "CLIENT_EMAIL",
  "client_id": "CID",
  "auth_uri": "PATH",
  "token_uri": "PATH",
  "auth_provider_x509_cert_url": "PATH",
  "client_x509_cert_url": "PATH"
}

// 暗号化を行う
// 暗号化/復号化で使用するキーはパスワード生成サイトなどであらかじめ用意しておく
const encrypted = crypto.AES.encrypt(JSON.stringify(key), process.env.CRYPT_KEY).toString()
console.log(encrypted)

ファイルを作成し、暗号化した文字列をリポジトリで管理する

node encrypt_api.js >> encrypted_key 

これで暗号化されたのでリポジトリで管理することができるようになった

復号化して使う

復号化してjsonを用意してAPIを叩く
googleのAPIを叩くためクライアントを用意するところまで処理を並べると以下のような感じ

const crypto = require("crypto-js")
const fs = require('fs')
const { google } = require('googleapis')

// 暗号化された文字列をファイルから読み込む
const encryptedKey = fs.readFileSync("./encrypted_key", "utf8")

// jsonを復元する
const decryptedKey = crypto.AES.decrypt(encryptedKey, process.env.CRYPT_KEY).toString(crypto.enc.Utf8)

// ファイルとしてjsonを作成する
fs.writeFileSync(`./decrypted_google_api_key.json`, decryptedKey, () => {})

// 復元した秘匿情報を元にクライアントを作成する
const client = await google.auth.getClient({
  keyFile: "./decrypted_google_api_key.json",
  scopes: 'https://www.googleapis.com/auth/analytics.readonly'
})

// 秘匿情報を削除する
fs.unlinkSync("./decrypted_google_api_key.json")

以上の方法なら .env やGitHubのシークレットで管理するものは、暗号化/復号化に使用するためのキー(ここでは CRYPT_KEY )だけなので楽

ちなみに GOOGLE_APPLICATION_CREDENTIALS という環境変数にjsonへのパスを指定すれば keyFile はいらないっぽいが
結局リポジトリにそのまま置けない問題は解決しないので、やっぱりなんらかの対策は必要と思われる

他にいい感じに楽に管理する方法があれば誰か教えて〜