# AWS Lambdaで自動化

## 概説

コマンダーは、さまざまな問題を解決し、Keeper Security環境に関する貴重な情報が得られる強力なツールです。ローカルのデスクトップやサーバーで使用するだけでなく、AWSのようなクラウド環境でコマンダーを実行して、スケジュールされたまたはオンデマンドの要件を実行することも可能です。

この例では、コマンダーをAWS Lambdaと連携させ、ユーザーおよび使用状況のレポートを定期的に実行する方法を解説します。

## 要件

* ワンタイムのデバイスセットアップ用に、ローカルマシンにコマンダーをインストール
  * ダウンロードの詳細については、[インストールとセットアップのページ](/keeperpam/jp/commander-cli/commander-installation-setup.md)をご参照ください
* このLambda専用のKeeperユーザーアカウント (マスターパスワードによるログイン。SSOおよびMFAは、人の操作なしでは利用不可)
* Lambda関数、Lambdaレイヤー、SSM Parameter Storeパラメータの作成、およびAWS CloudShellの利用に必要な権限を持つAWS環境へのアクセス

## ワンタイムのデバイスセットアップ

Lambdaで認証する前に、ローカルマシンで一度デバイス承認を完了する必要があります。Keeperでは、パスワード認証が成功する前にすべてのデバイスを承認する必要があります。Lambdaにはこの手順を完了する対話型シェルがないため、あらかじめ承認済みのデバイスIDを使用する必要があります。

### 手順

1. ローカルマシンで、Lambda専用アカウントを使ってコマンダーにログインします。

```shell-session
$ keeper shell
```

2. デバイス承認プロセスを完了します (エンタープライズの設定に応じて、メールリンク、プッシュ通知、2FA、管理者承認など)
3. ログインに成功したら、構成ファイル内の `device_token` を確認します。

```shell-session
$ cat ~/.keeper/config.json | grep device_token
```

{% hint style="warning" %}
コマンダーのインストール方法によって、構成ファイルの形式は異なる場合があります。CLIでは通常 `device_token` はトップレベルに保存されますが、SDKベースのインストールではネストされた構造 (`devices[].device_token`) を使用する場合があります。`grep` で結果が得られない場合は、ファイルを開いてトークンを手動で確認してください。
{% endhint %}

4. 以下の4つの値をLambda環境変数 (本番環境では後述のSSMパラメータ) として保存します。
   * `KEEPER_DEVICE_TOKEN` — 手順3で取得した、承認済みのデバイスID
   * `KEEPER_USER` — アカウントのメールアドレス
   * `KEEPER_PASSWORD` — マスターパスワード
   * `KEEPER_SERVER` — リージョンのエンドポイント (例: USは `keepersecurity.com`、EUは `keepersecurity.eu`)

{% hint style="info" %}
**保存が不要な項目**

* `private_key` — パスワードログイン時にコマンダーが自動生成します。ウォームコンテナ最適化のために永続ログインを有効にする場合、`private_key` はそのセットアップの一環として `/tmp` に作成・保存されます。
* `clone_code` — パスワードログインのたびに自動再生成されます。永続ログインのセッション再開にのみ使用され、有効にした場合は `/tmp` で自動的に処理されます。
  {% endhint %}

## 手順

### AWS CloudShellを使用してLambdaレイヤーを作成

#### セットアップ

コマンダーは、AWS Lambdaで実行する環境と同じマシンでパッケージ化する必要があります。CloudShellでコマンダーパッケージを作成します。

このワークフローでは、AWS LambdaでPython 3.10または3.11を使用します (Python 3.9以前はAWS Lambdaでは利用できません)。Lambdaレイヤーを構築するCloudShell環境のPythonが、いずれかのバージョンになっているか、インストールされているPythonインタープリタで確認してください。

```shell-session
$ python3 --version
Python 3.11.6
```

CloudShellで利用できるPythonのバージョンが、AWS Lambdaで利用できるランタイムと一致しない場合は、先に進む前にPython 3.10または3.11をインストールしてください。

#### レイヤーコンテンツの構築

以下の段階では、CloudShell環境内で実行できる便利なシェルスクリプトを使用します。このスクリプトは、Lambdaレイヤーに必要な `keepercommander` パッケージを含むZIPファイルを作成します。

このスクリプトは、さまざまなコマンド呼び出しをまとめるだけでなく、`keepercommander` パッケージとその依存関係に特有のビルドプロセスの細かな問題を抽象化することで、レイヤーコンテンツのパッケージングを簡略化し、効率化することを目的としています。**エラーが発生しにくいため、この方法を使用することを強くお勧めします。**

<details>

<summary>スクリプトを見る</summary>

{% code title="package\_layer\_content.sh" fullWidth="true" %}

```bash
#!/usr/bin/env bash

#   AWS Lambda関数用の `keepercommander` 依存関係レイヤーを作成する
#   1. このスクリプトをCloudShell環境内の任意のフォルダにアップロードする
#   2. (オプション) プロジェクトの `requirements.txt` を同じフォルダにアップロードする
#   3. そのフォルダで以下を実行する
#             source ./package_layer_content.sh
#   4. `commander-layer.zip` が作成される。S3バケットにアップロードしてLambdaレイヤーの作成に使用する

MAX_LIB_SIZE=262144000
LAYER_FILENAME='commander-layer.zip'
LAYER_PATH=$(pwd)/$LAYER_FILENAME
LIB_DIR='python'
VENV='commander-venv'
OTHER_DEPS='requirements.txt'

# 過去のアーティファクトをクリーンアップ
test -f $LAYER_FILENAME && rm $LAYER_FILENAME
test -d $LIB_DIR && rm -rf $LIB_DIR
test -d $VENV && rm -rf $VENV

# ZIPファイル用のパッケージフォルダを作成
mkdir $LIB_DIR

# 仮想環境を作成して実行
python3 -m venv $VENV
source ./$VENV/bin/activate

# 依存関係をインストールしてパッケージ化
python3 -m pip install cryptography --platform manylinux2014_x86_64 --only-binary=:all: -t $LIB_DIR
python3 -m pip install keepercommander -t $LIB_DIR

if test -f $OTHER_DEPS; then
  python3 -m pip install -r $OTHER_DEPS -t $LIB_DIR
fi

deactivate

# 解凍後のライブラリサイズを確認
LIB_SIZE=$(du -sb $LIB_DIR | cut -f 1)
LIB_SIZE_MB=$(du -sm $LIB_DIR | cut -f 1)

if [ "$LIB_SIZE" -ge $MAX_LIB_SIZE ]; then
  echo "*****************************************************************************************************************"
  echo 'Operation was aborted'
  echo "The resulting layer has too many dependencies and its size ($LIB_SIZE_MB MB) exceeds the maximum allowed (~262 MB)."
  echo 'Try breaking up your dependencies into smaller groups and package them as separate layers.'
  echo "*****************************************************************************************************************"
else
  zip -r $LAYER_FILENAME $LIB_DIR
  echo "***************************************************************************"
  echo "***************************************************************************"
  echo 'Lambda layer file has been created'
  printf "To download, copy the following file path: %s\n%s\n$LAYER_PATH%s\n%s\n"
  echo 'and click on "Actions" in the upper-right corner of your CloudShell console'
  echo "***************************************************************************"
fi

# クリーンアップ
rm -rf $LIB_DIR
rm -rf $VENV

```

{% endcode %}

</details>

上記のスクリプトを使用するには、以下を実行します。

1. スクリプトをCloudShell環境内の任意のフォルダ（できれば空のフォルダ）にアップロードします。
   * (オプション) プロジェクトに依存関係のリストが含まれる `requirements.txt` がある場合、同じフォルダにアップロードすると、`keepercommander` パッケージに加えて依存関係もレイヤーに含められます。
2. 同じフォルダ内で、ターミナルで以下のコマンドを実行します。

```shell-session
source ./package_layer_content.sh
```

3. 現在のフォルダに `commander-layer.zip` が作成されます。このZIPファイルがLambdaレイヤーのコンテンツです。

{% hint style="info" %}
Lambdaレイヤーのコンテンツにはサイズ制限があります (S3に保存する場合も同様です)。`requirements.txt` で追加の依存関係を含め、コンテンツの総サイズが制限を超えた場合、作成されたZIPファイルは使用できません。この状況を検出した場合、スクリプトはレイヤーコンテンツを出力せず、メッセージを表示します。

比較的簡単な解決策としては、依存関係を小さなグループに分割し、それぞれを個別のレイヤーにパッケージ化します。`requirements.txt`からいくつか (またはすべて) の依存関係を削除し、再度スクリプトを実行することができます。その結果、パッケージに含まれなかった依存関係は、標準のパッケージングプロセスを使用して別のレイヤーとしてパッケージ化できます。
{% endhint %}

#### コンテンツZipファイルからのレイヤーの作成と更新

結果として生成されるZIPファイルは50MB以上 (Lambdaレイヤーに直接アップロードできる最大サイズ) になるため、最初にAWS S3バケットにアップロードし、その後、生成されたS3アイテムをLambdaレイヤーにリンクする必要があります。

残りの手順を完了する方法はいくつかあります。GUIを利用する場合はAWSコンソールから操作できます。すでにCloudShell環境内にある場合は、組み込みのAWS CLIを使う方法が最も簡単なため、ここではCLIでの手順を記載しています。

1. まず、ZIPファイルをAWS S3にアップロードする必要があります。このタスク用にS3バケットをまだ作成していない場合は、CloudShellで以下のコマンドを実行して作成できます。

```shell-session
$ aws s3 mb <bucket-name>
```

ここで、 \<bucket-name>はグローバルで固有の名前である必要があります。

2. 新しくパッケージしたZipファイルをCloudShellからS3バケットへアップロードします。

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ aws s3 cp ./commander-layer.zip 's3://&#x3C;bucket-name>'
</strong></code></pre>

3. アップロードしたコンテンツでLambdaレイヤーを公開します。

```shell-session
$ aws lambda publish-layer-version --layer-name <layer-name> \
--description <layer-description> \
--content "S3Bucket=<bucket-name>,S3Key=commander-layer.zip" \
--compatible-runtimes python3.11

```

### Lambdaの作成

AWS Lambdaで、Lambdaエディタを使用してPython関数を作成します。

`lambda_handler` 関数は、処理される際にLambdaによって呼び出されます。

以下は、コマンダーLambda関数の完全な例となります。

{% code fullWidth="false" %}

```python
#  _  __
# | |/ /___ ___ _ __  ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
#              |_|
#
# Keeper Commander
# Copyright 2024 Keeper Security Inc.
# Contact: ops@keepersecurity.com

import os
import json

# Without mounted volumes, Lambda can only write to /tmp. 
os.environ['HOME'] = '/tmp'
os.environ['TMPDIR'] = '/tmp'
os.environ['TEMP'] = '/tmp'

from keepercommander import api
from keepercommander.__main__ import get_params_from_config

keeper_tmp = '/tmp/.keeper'
os.makedirs(keeper_tmp, exist_ok=True)

# ------------------------------------------------------
# Keeper initialization function
# ------------------------------------------------------
def get_params():
    # Read credentials from environment variables
    user = os.environ.get('KEEPER_USER')
    password = os.environ.get('KEEPER_PASSWORD')
    server = os.environ.get('KEEPER_SERVER', 'keepersecurity.com')
    device_token = os.environ.get('KEEPER_DEVICE_TOKEN')

    # Write config.json with the pre-approved device_token
    config_path = keeper_tmp + '/config.json'
    with open(config_path, 'w') as f:
        json.dump({
            'user': user,
            'server': server,
            'device_token': device_token
        }, f)

    # Load params from the config we just wrote
    params = get_params_from_config(config_path)
    params.password = password
    return params

# ------------------------------------------------------
# Keeper JSON report function
# ------------------------------------------------------
def get_keeper_report(params, kwargs):
    from keepercommander.commands.aram import AuditReportCommand
    from json import loads
    
    report_class = AuditReportCommand()
    report = report_class.execute(params, **kwargs)
    return loads(report)
    
# ------------------------------------------------------
# Keeper CLI function
# ------------------------------------------------------
def run_keeper_cli(params, command):
    from keepercommander import cli
    
    cli.do_command(params, command)
    
# ------------------------------------------------------
# Lambda handler
# ------------------------------------------------------
def lambda_handler(event, context):
    # Initialize Keeper Commander params with pre-approved device token
    params = get_params()

    # Keeper login (uses the pre-approved device_token) and sync
    api.login(params)
    api.sync_down(params)
    # Enterprise sync (for enterprise commands)
    api.query_enterprise(params)

    # Approve any OTHER pending devices in the enterprise
    run_keeper_cli(
        params, 
        'device-approve -a'
    )
    
    run_keeper_cli(
        params, 
        'action-report --target locked --apply-action delete --dry-run'
    )

    return get_keeper_report(
        params,
        {
            'report_type':'raw', 
            'format':'json',
            'limit':100,
            'event_type':['login']
        }
    )
```

{% endcode %}

このプログラムは以下の要素で構成されています。

#### `lambda_handler` 関数

この関数は、lambdaが実行されたときに呼び出されます。他のすべての関数は、この関数から呼び出す必要があります。

#### `get_params()` 関数

この関数は、環境変数から承認済みの `device_token` と認証情報を読み取り、`device_token` を含む `config.json` を書き込み、`api.login()` で使用できる `params` オブジェクトを返します。`device_token` は、ローカルマシンでのワンタイムセットアップ時に承認したデバイスIDです。

{% hint style="info" %}
**`device_token` が重要な理由**

Keeperでは、パスワード認証の前にデバイス承認が必要です。Lambdaにはデバイス承認を完了する対話型シェルがないため、別のマシンであらかじめ承認した `device_token` が必要です。

複数のLambdaコンテナは同じ `device_token` を共有し、パスワードで同時に認証できます。各コンテナは同じ `device_token` をローカルの `/tmp/.keeper/config.json` に書き込み、独立してログインします。`clone_code` はパスワードログインのたびに再生成されますが、永続ログインを使用しないため影響はありません。
{% endhint %}

#### コマンダー関数

ログインして同期が完了すると、コマンダーSDKから関数を実行したりクラスを作成したりできます。上記のプログラムには2つの関数が含まれています。`get_keeper_report()` はJSON形式のレポートを取得します (`audit-report` コマンドと同等)。`run_keeper_cli()` はCLIコマンドを実行します (Python内でデータを返さずに実行のみ行います)。

#### Keeper認証情報の保存

上記の例では、Lambda環境変数から4つの値を読み取ります。

| 環境変数                  | 説明                                          |
| --------------------- | ------------------------------------------- |
| `KEEPER_DEVICE_TOKEN` | ワンタイムセットアップで取得した承認済みのデバイスID                 |
| `KEEPER_USER`         | アカウントのメールアドレス                               |
| `KEEPER_PASSWORD`     | マスターパスワード                                   |
| `KEEPER_SERVER`       | リージョンのエンドポイント (デフォルトは `keepersecurity.com`) |

これは最も簡単な方法で、追加のAWSセットアップは不要です。ただし、環境変数は `lambda:GetFunctionConfiguration` 権限を持つユーザーに表示され、ログに出力された場合はCloudWatchログにも記録される可能性があります。本番環境のデプロイでは、代わりにSSM Parameter Storeを使用してください (以下のセクションをご参照ください)。

#### 本番環境: SSM Parameter Storeの使用

本番環境のデプロイでは、認証情報をAWS SSM Parameter Storeの `SecureString` パラメータとして保存します。Lambdaの構成やCloudWatchログから認証情報を切り離し、保存時はKMSで暗号化し、IAMポリシーで読み取り権限の範囲を限定できます。

SSMに以下のパラメータを作成します (パスは例です。必要に応じてカスタマイズしてください)。

* `/keeper/lambda/device_token`
* `/keeper/lambda/user`
* `/keeper/lambda/password`
* `/keeper/lambda/server`

次に、Lambdaコード内の `get_params()` 関数を、以下のSSMベースの実装に置き換えます。

```python
import os
import json
import boto3

# マウントされたボリュームがない場合、Lambdaは /tmp にのみ書き込み可能です。
os.environ['HOME'] = '/tmp'
os.environ['TMPDIR'] = '/tmp'
os.environ['TEMP'] = '/tmp'

from keepercommander import api
from keepercommander.__main__ import get_params_from_config

keeper_tmp = '/tmp/.keeper'
os.makedirs(keeper_tmp, exist_ok=True)

# モジュールレベルでSSMクライアントを一度だけ作成
ssm_client = boto3.client('ssm')

def get_ssm_parameter(name):
    response = ssm_client.get_parameter(Name=name, WithDecryption=True)
    return response['Parameter']['Value']

def get_params():
    user = get_ssm_parameter('/keeper/lambda/user')
    password = get_ssm_parameter('/keeper/lambda/password')
    server = get_ssm_parameter('/keeper/lambda/server')
    device_token = get_ssm_parameter('/keeper/lambda/device_token')

    # 承認済みの device_token を含む config.json を書き込む
    config_path = keeper_tmp + '/config.json'
    with open(config_path, 'w') as f:
        json.dump({'user': user, 'server': server, 'device_token': device_token}, f)

    # 書き込んだ構成から params を読み込む
    params = get_params_from_config(config_path)
    params.password = password
    return params
```

Lambda実行ロールには必要なIAM権限 (`ssm:GetParameter` をリソースARNでスコープ) のみを付与し、取得した認証情報をログに出力しないでください。

SSMの標準パラメータは無料です。SecureStringはKMSを使用し、KMSには独自の無料枠 (月20,000リクエスト) があります。一般的なLambdaワークロードでは実質的にコストはかかりません。組み込みのローテーション、きめ細かな監査、クロスアカウントアクセスが必要なエンタープライズデプロイでは、代わりに**AWS Secrets Manager**を使用してください (シークレットごとにコストが加算されます)。

#### 永続ログインに関する注意

コマンダーは永続ログインに対応しており、以降のログインでパスワード入力を省略できます。ただし、**マルチコンテナのLambdaデプロイでは永続ログインは利用できません**。

Keeperのドキュメントでは、永続ログインは「動的なマルチサーバー環境向けではない」と記載されています。いずれかのインスタンスが同じ `device_token` でパスワードログインすると `clone_code` が再生成され、同じ `device_token` を共有する他のインスタンス上の永続ログインセッションは無効になります。永続化されたセッションはコンテナの `/tmp` にのみ存在し、AWSがコンテナを入れ替えると失われます。

**単一コンテナのスケジュール実行Lambda** (例: 同じウォームコンテナが毎回の呼び出しを処理する1日1回のcron) では、ウォーム起動時のレイテンシをわずかに短縮できる場合があります。セットアップ手順については、[`this-device` コマンドのドキュメント](/keeperpam/jp/commander-cli/command-reference/misc-commands.md#this-device-command)をご参照ください。

ほとんどのLambdaの用途では、上記の例のように呼び出しのたびにパスワード認証を行う方法が適切です。

### Lambdaの構成

#### タイムアウトの設定

Lambdaの設定にある一般構成セクションでは、タイムアウト値を変更することを推奨します。コマンダーの一部の関数は実行に時間がかかるため、スクリプトの処理時間がこの値を超えると、Lambdaは完了前に自動的に終了してしまいます。

タイムアウト値は任意の値に設定できますが、ほとんどの処理に対しては300秒 (5分) に設定しておけば十分です。

#### レイヤーの選択

Lambdaエディタで、上記で作成したレイヤーを選択して、Lambdaビルドにコマンダーパッケージを追加します。

#### 実行スケジュールの作成

Lambdaを呼び出すEventCloudトリガーを作成し、適切な頻度 (たとえば、1日に1回や30日に1回) で動作するように設定します。

AWSでは、メールやSMSによるトリガーなど、他のいくつかのソースからLambdaを呼び出すように設定することもできます。その他のオプションについては、Lambdaの呼び出しに関するAmazonのドキュメントをご参照ください。

{% embed url="<https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-invocation.html>" %}

## 次の手順

コマンダーの他の機能、Lambdaの呼び出し方法、またSNSなどのAWSサービス (SMSメッセージを含むさまざまなプッシュ通知の手法に活用可能) を組み合わせて、Keeperの処理を自動化してみましょう。

コマンダーSDKコードの使用例については、コマンダーのGitHubリポジトリにあるサンプルスクリプトをご参照ください。

{% embed url="<https://github.com/Keeper-Security/Commander/tree/master/examples>" %}

コマンダーのさまざまなメソッドの詳細については、コマンドリファレンスのページをご参照ください。

{% content-ref url="/pages/-McBE9TLEWh7hS-tYX14" %}
[コマンドリファレンス](/keeperpam/jp/commander-cli/command-reference.md)
{% endcontent-ref %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keeper.io/keeperpam/jp/commander-cli/commander-installation-setup/configuration/using-commander-with-aws-lambda.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
