💡

Momento API Key を AWS Secrets Manager に保存する方法

2024/01/23に公開

Momento では呼び出しにクレデンシャルが必要となります。Momento の Documentでは環境変数を使っていますが、AWS上で動かすプログラムの場合例えばSecrets Manager等安全なクレデンシャルストアに保存することをお勧めしています。

https://docs.momentohq.com/ja/cache/develop/integrations/aws-secrets-manager
ただ残念ながらこちらのドキュメントではJavaのサンプルのみが存在しているため、他のSDKを使っている場合少し情報の調査などが必要です。

この記事ではJavaScript(Node)で AWS Secrets Manager を使う方法を紹介します。

まず初めに

環境のセットアップですがまず最初にこちらの手順を終わらせておきます。
https://zenn.dev/momentobigfun/articles/aa24ff7817e06c

WindowsでもLinuxでもソースコードは動作します。

やってみる

1. Secrets Managerの設定

上記の手順が終わった後 AWS Secrets Manager にMomento API Keyを保存します。
Store a new secretを押します。

Other type of secretを選びます。

keyにはプログラムからクレデンシャルを呼び出す名前をセットします。valueはMomentoコンソールから取得可能なApi keyをセットします。

2. AWS configureの設定

この記事を読んでいる方は、プログラムの実行環境がAWSサービスを呼び出すためのAWSクレデンシャルをIAMで設定した後、aws configureコマンドでセットする方法は知っている方が対象ですので、この手順は割愛します。以下のサンプルソースコードはWindowsでもLinuxでも動作します。

test.jsの置換と実行

const {
  GetSecretValueCommand,
  SecretsManagerClient,
} = require('@aws-sdk/client-secrets-manager');

// Declare the Momento SDK library
const {
  CacheGet, CacheSet, Configurations, ListCaches, CreateCache,
  CacheClient, CredentialProvider
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Defines name of cache to use.
const CACHE_NAME = 'demo-cache';
var secretstring;

// Run the config function to bring in the .env file
dotenv.config();

async function secrets(secretstring) {
  const secret_name = "momento";

  const client = new SecretsManagerClient({
    region: "ap-northeast-1",
  });

  let response;

  try {
    response = await client.send(
      new GetSecretValueCommand({
        SecretId: secret_name,
        VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified
      })
    );
  } catch (error) {
    // For a list of exceptions thrown, see
    // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
    throw error;
  }

  const secret = response.SecretString;

  console.log(response);
  console.log(secret);

  const obj1 = JSON.parse(secret);
  secretstring = obj1.momento;
  return secretstring;
}

// Creates the Momento cache client object
async function createCacheClient() {
  return await CacheClient.create({
    configuration: Configurations.Laptop.v1(),
    credentialProvider: CredentialProvider.fromString({
      apiKey: secretstring,
    }),
    defaultTtlSeconds: 600,
  });
}

// Create a new cache
async function createCache(client) {
  const createCacheResponse = await client.createCache(CACHE_NAME);
  if (createCacheResponse instanceof CreateCache.Success) {
    console.log('Cache created.');
  } else if (createCacheResponse instanceof CreateCache.AlreadyExists) {
    console.log('Cache already exists');
  } else if (createCacheResponse instanceof CreateCache.Error) {
    throw createCacheResponse.innerException();
  }
}

// List all caches in Momento for this account.
async function listCaches(client) {
  const listResponse = await client.listCaches(client);
  if (listResponse instanceof ListCaches.Error) {
    console.log('Error listing caches: ', listResponse.message());
  } else if (listResponse instanceof ListCaches.Success) {
    console.log('Found caches:');
    listResponse.getCaches().forEach(cacheInfo => {
      console.log(' -', cacheInfo.getName());
    });
  } else {
    throw new Error('Unrecognized response: ', listResponse.toString());
  }
}

// A function to write to the cache
async function writeToCache(client, cacheName, key, data) {
  const setResponse = await client.set(cacheName, key, data);
  if (setResponse instanceof CacheSet.Success) {
    console.log('Key stored successfully!');
  } else if (setResponse instanceof CacheSet.Error) {
    console.log('Error setting key: ', setResponse.toString());
  } else {
    console.log('Some other error: ', setResponse.toString());
  }
}

// A function to read scalar items from the cache
async function readFromCache(client, cacheName, key) {
  const readResponse = await client.get(cacheName, key);
  if (readResponse instanceof CacheGet.Hit) {
    console.log('Cache hit: ', readResponse.valueString());
  } else if (readResponse instanceof CacheGet.Miss) {
    console.log('Cache miss');
  } else if (readResponse instanceof CacheGet.Error) {
    console.log('Error: ', readResponse.message());
  }
}

// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
  secretstring = await secrets();

  const cacheClient = await createCacheClient();

  await createCache(cacheClient);

  await listCaches(cacheClient);

  await writeToCache(cacheClient, CACHE_NAME, "code", "12345");
  await readFromCache(cacheClient, CACHE_NAME, "code");
}

run();

先の手順で用いたものをSecrets Manager用に書き換えたものです。
少し部分部分で解説を入れます。

const {
  GetSecretValueCommand,
  SecretsManagerClient,
} = require('@aws-sdk/client-secrets-manager');

Secrets Manager を呼び出す部分の定義です。

async function secrets(secretstring) {
  const secret_name = "momento";

  const client = new SecretsManagerClient({
    region: "ap-northeast-1",
  });

  let response;

  try {
    response = await client.send(
      new GetSecretValueCommand({
        SecretId: secret_name,
        VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified
      })
    );
  } catch (error) {
    // For a list of exceptions thrown, see
    // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
    throw error;
  }

  const secret = response.SecretString;

  console.log(response);
  console.log(secret);

  const obj1 = JSON.parse(secret);
  secretstring = obj1.momento;
  return secretstring;
}

Secrets Managerからmomentoという名前のシークレットを取り出します。値はJSONで出てきますので、パースしてsecretstringに文字列としてセットします。

async function createCacheClient() {
  return await CacheClient.create({
    configuration: Configurations.Laptop.v1(),
    credentialProvider: CredentialProvider.fromString({
      apiKey: secretstring,
    }),
    defaultTtlSeconds: 600,
  });
}

Cache Client作成時に従来は環境変数から値を呼び出していましたが、文字列でクレデンシャルをセットするように変更しています。

あとはメイン関数の中でCacheClientの作成前にSecrets Managerから値を取り出す関数を呼び出すだけです。

async function run() {
  secretstring = await secrets();

  const cacheClient = await createCacheClient();

Secrets Managerの呼び出しはパブリック通信です。(クライアントがAWS EC2であれば、AWSが管理しているネットワークだけで完結しますので、完全パブリックというわけではないですが)
Private通信に切り替える場合、VPC Endpointを作ってあげれば自動で切り替わります。

VPC Endpointの作り方

AWSマネージメントコンソール左ペインから、VPC→EndPointsをクリックします。

Create endpointを押します。
適当な名前を入力しSecrets Managerのエンドポイントを選択します。

クライアントが存在しているVPCを選びます。

VPC Endpointを作成するサブネットを選びます。

Create endpointを押します。数分待つと自動で通信が切り替わります。(名前解決のDNS名に紐付くIPアドレスが切り替わるため)

Discussion