Okta WorkflowsのLambdaコネクタを試してみた
データ事業本部のueharaです。
今回は、Okta WorkflowsのLambdaコネクタを試してみたいと思います。
はじめに
Okta Workflowsは、ID管理や外部アプリ連携などの業務プロセスを、ノーコードで自動化できるプラットフォームです。
その中の機能の1つとして、AWS Lambdaを実行することができる Lambda コネクタ があります。
今回はこのLambdaコネクタを利用して、 Okta上でユーザをディアクティベート(非アクティブ化)した際に、無効化されたユーザの情報をLambdaに連携する というのを試してみたいと思います。
ユースケースとしては、退職等に伴いOkta上でユーザを非アクティブ化した際、Lambdaにユーザ情報を渡し特定サービスと連携しながら退職に伴う処理を行うということが考えられます。
全体の流れとしては、以下のような形になります。
Oktaでイベント発生(ユーザのディアクティベート)
↓
Okta Workflows(User Deactivated トリガー)
↓ AWS Lambda コネクタ
AWS Lambda(ユーザ情報の受け取り)
↓
CloudWatch Logs(ログ確認)
今回はサンプルなので、Lambdaでは受け取ったユーザ情報をログに出力するのみとします。
API GatewayやLambda Function URLsも不要で、Lambdaを直接呼び出してもらう形となります。
やってみた
(AWS)SAMアプリケーションの作成
AWSリソースの作成には AWS SAM (Serverless Application Model) を使用します。
今回作成するリソースは以下の通りです。
| リソース | 名前 | 用途 |
|---|---|---|
| Lambda 関数 | okta-retirement-receiver |
ユーザ情報を受け取りログ出力を行う |
| Lambda 実行ロール | okta-retirement-receiver-role |
CloudWatchへのログ出力権限を持つ |
| IAM ポリシー | okta-workflows-invoke-retirement-receiver |
Lambdaの実行権限を持つ |
| IAM ユーザー | okta-workflows-lambda-invoker |
Okta Workflowsの接続用ユーザ (アクセスキーは後に手動で発行) |
SAMアプリケーションのディレクトリ構成は以下の通りです。
sam-app
├── samconfig.toml
├── src
│ └── lambda_function.py
└── template.yaml
まず、 samconfig.toml は以下のように記載しました。
version = 0.1
[default]
region = "ap-northeast-1"
[default.build.parameters]
debug = true
[default.deploy.parameters]
stack_name = "okta-workflows-test"
s3_bucket = "cm-da-uehara"
s3_prefix = "sam-deploy"
capabilities = "CAPABILITY_NAMED_IAM"
confirm_changeset = true
バケット名やスタック名などは任意なので、お手元で確認する際は適宜変更ください。
SAMのテンプレートである template.yaml は以下の通りです。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "SAM application for verifying the Okta Workflows AWS Lambda connector."
# ============================================================
# Globals
# ============================================================
Globals:
Function:
Runtime: python3.12
Architectures:
- x86_64
Timeout: 30
MemorySize: 128
# ============================================================
# Resources
# ============================================================
Resources:
# ----------------------------------------------------------
# Lambda function: retired user information receiver
# ----------------------------------------------------------
OktaRetirementReceiverFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: okta-retirement-receiver
Description: >-
Verification Lambda function that receives retired user information
from the Okta Workflows Lambda connector and logs it to CloudWatch Logs.
CodeUri: src/
Handler: lambda_function.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
# ----------------------------------------------------------
# Lambda execution role
# ----------------------------------------------------------
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: okta-retirement-receiver-role
Description: >-
Execution role for the okta-retirement-receiver Lambda function.
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# ----------------------------------------------------------
# IAM policy for Okta Workflows connectivity
# ----------------------------------------------------------
OktaWorkflowsInvokePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: okta-workflows-invoke-retirement-receiver
Description: >-
Least-privilege policy for the Okta Workflows AWS Lambda connector.
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ListLambdaFunctions
Effect: Allow
Action: lambda:ListFunctions
Resource: '*'
- Sid: InvokeOktaRetirementReceiverFunction
Effect: Allow
Action: lambda:InvokeFunction
Resource: !GetAtt OktaRetirementReceiverFunction.Arn
# ----------------------------------------------------------
# IAM user for Okta Workflows connectivity
# (programmatic access only; access key must be issued manually after deployment)
# ----------------------------------------------------------
OktaWorkflowsLambdaInvokerUser:
Type: AWS::IAM::User
Properties:
UserName: okta-workflows-lambda-invoker
ManagedPolicyArns:
- !Ref OktaWorkflowsInvokePolicy
# ============================================================
# Outputs
# ============================================================
Outputs:
LambdaFunctionName:
Description: Lambda function name
Value: !Ref OktaRetirementReceiverFunction
LambdaFunctionArn:
Description: Lambda function ARN
Value: !GetAtt OktaRetirementReceiverFunction.Arn
IamUserName:
Description: IAM user name for Okta Workflows connectivity
Value: !Ref OktaWorkflowsLambdaInvokerUser
IamPolicyArn:
Description: IAM policy ARN for Okta Workflows connectivity
Value: !Ref OktaWorkflowsInvokePolicy
作成されるリソースについては、先に記載したリソースの表の通りとなります。
Lambda関数のスクリプト src/lambda_function.py については以下の通りです。
from __future__ import annotations
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def _extract_body(event: dict) -> dict:
"""受信イベントからリクエストボディを抽出する
Lambda コネクター経由では event 自体が Payload となるが、
API Gateway / Function URL 経由では event.body に JSON 文字列が包まれる形になる。
どちらの呼び出し形式でも動作するよう両対応。
Args:
event (dict): Lambda が受け取るイベントオブジェクト
Returns:
dict: リクエストボディを表す辞書
"""
if isinstance(event, dict) and isinstance(event.get("body"), str):
try:
return json.loads(event["body"])
except json.JSONDecodeError:
return {}
return event if isinstance(event, dict) else {}
def _response(status_code: int, payload: dict) -> dict:
"""Lambda レスポンス辞書を生成する
Args:
status_code (int): HTTP ステータスコード
payload (dict): レスポンスに含めるデータ
Returns:
dict: statusCode とペイロードをマージしたレスポンス辞書
"""
return {"statusCode": status_code, **payload}
def lambda_handler(event: dict, context) -> dict:
logger.info("受信 event: %s", json.dumps(event, ensure_ascii=False, default=str))
body = _extract_body(event)
email = (body.get("email") or "").strip()
user_id = (body.get("user_id") or "").strip()
display_name = (body.get("display_name") or "").strip()
action = (body.get("action") or "").strip()
logger.info(
"退職者情報を受信しました / action=%s, email=%s, user_id=%s, display_name=%s",
action,
email,
user_id,
display_name,
)
result = {
"message": "退職者情報を正常に受信しました",
"received": {
"action": action,
"email": email,
"user_id": user_id,
"display_name": display_name,
},
}
logger.info("処理完了: %s", json.dumps(result, ensure_ascii=False))
return _response(200, {"result": result})
ここでは、受け取ったペイロードをログに出力して、そのまま返すだけのシンプルな実装にしています。
(AWS)デプロイと確認
ファイルの用意ができたら、以下コマンドでデプロイを行います。
$ sam deploy
デプロイが完了したら、 samconfig.toml で名前を指定したスタックの確認を行います。
以下のようにリソースが作成されていれば完了です。

(AWS)IAM ユーザーのアクセスキー発行
SAMで作成したIAM ユーザ( okta-workflows-lambda-invoker )のアクセスキーを手動で発行します。
『IAMコンソール』 → 『ユーザー』 → 『okta-workflows-lambda-invoker』 → 『アクセスキーを作成』 と進みます。

ユースケースは『サードパーティーサービス』を選択します。
発行された アクセスキー と シークレットアクセスキー を控えておきます。

これでAWS側の準備は完了です。
(Okta)Workflowsのフロー作成
左側のタブから『Workflowsコンソール』へと遷移します。

『New Flow』の『From scratch』からフローを作成します。

トリガー設定
まず、トリガーの設定を行います。
左側のカードの『Add event』を選択します。

『Okta』を選択します。

『User Deactivated』を選択します。

このトリガーはOktaでユーザが非アクティブ化されたときに発火します。
完了するとトリガーが追加されると思います。
追加されたトリガーの『New Connection』を選択します。

Oktaの情報を入力します。

Domainはご自身のOktaのURLの -admin を除いたものを入力して下さい。
例: xxx-yyy-admin.okta.com の場合 xxx-yyy.okta.com となります。
接続が成功すると、以下のようにOutputの欄が表示されます。

Read Userアクションの追加
トリガーから取得できるユーザ情報は限られているため、次に Read User アクションを追加します。
先ほどと同じ要領で、追加したトリガーの右のカードの『Add app action』から『Okta』を選択し、『Read User』アクションを追加します。

追加のプロパティとして、Profile Propertiesの『Primary email』と『Display name』を取得するようにします。

『Save』で保存が完了したら、Inputの『ID or Login』にトリガーの『Okta User』の『ID』をドラッグ&ドロップで接続します。
この時点で、以下のようになっているかと思います。

ペイロードの作成
次に、『Read User』で取得した情報をLambdaに渡すためのペイロードを作成します。
右のカードの『Add function』から、『Object』を選択します。

Objectの中の『Construct』を選択します。

追加したConstructに対し action, email, user_id, display_name のキーを作成し、以下の通り設定します。(マッピングするものについては項目をドラッグ&ドロップして下さい)
| 項目名 | 設定値 | 備考 |
|---|---|---|
| action | notify_retirement | 固定値 |
| Primary email | Read Userからマッピング | |
| user_id | ID | Read Userからマッピング |
| display_name | Display name | Read Userからマッピング |
設定したキー項目は、そのまま『output』として出力するようにします。
ここまで設定すると、以下のようになっているかと思います。

Lambda関数の呼び出し設定
最後に、Lambda関数の呼び出し設定を行います。
作成したConstructの右のカードから、『Add app action』を選択し、Lambdaを検索します。

『Invoke』を選択します。

するとLambdaが追加されると思うので、『New Connection』を選択します。

ここで、AWSリソースの作成時に発行したアクセスキーとシークレットアクセスキーを入力します。リージョンはTokyoにします。

接続が成功すると関数名が入力できるようになるため、作成したLambda関数である『okta-retirement-receiver』を入力します。

Parameters設定で『invocationType』を『RequestResponse』とし、『payload』に先ほど追加したConstructのoutputをドラッグ&ドロップします。

これでLambda関数の設定も完了です。
全体のワークフローは次の通りになっているかと思います。

フローの保存と有効化
最後に、任意でフロー名を入力した後に保存をして、『Flow is OFF』を選択します。

フローの有効化を行います。

これで準備は完了です。
動作確認
AWS・Okta共に準備ができたので、動作確認を行います。
適当なユーザを選択し、非アクティブ化を行います。

Workflowsの履歴を見ると、以下のように無事フローが動いていることが確認できました。


Lambdaのログを確認してみると、こちらも無事情報を取得できていることが分かります。

最後に
今回は、Okta WorkflowsのLambdaコネクタを試してみました。
参考になりましたら幸いです。








