diff --git a/oneclick-stepfunction-human-approval/example-pattern.json b/oneclick-stepfunction-human-approval/example-pattern.json new file mode 100644 index 000000000..2008ce024 --- /dev/null +++ b/oneclick-stepfunction-human-approval/example-pattern.json @@ -0,0 +1,80 @@ +{ + "title": "AWS Step Functions Manual Approval Workflow with SNS", + "description": "Create a serverless manual approval process within Step Functions workflows using SNS notifications and API Gateway.", + "language": "Python", + "level": "200", + "framework": "AWS SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern creates a Step Functions workflow that pauses for human approval, using SNS for email notifications and API Gateway for handling responses.", + "The workflow sends approval requests via SNS email notifications containing approve/reject links.", + "Recipients can approve or reject the workflow by clicking links that trigger an API Gateway endpoint.", + "The pattern demonstrates how to incorporate human decision points within automated Step Functions workflows.", + "Please consider AWS Step Functions state transitions quotas and API Gateway limits when implementing this pattern." + ] + }, + "gitHub": { + "template": { + "repoURL": "", + "templateURL": "serverless-patterns/stepfunctions-manual-approval", + "projectFolder": "stepfunctions-manual-approval", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "AWS Step Functions Task Tokens", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token" + }, + { + "text": "Step Functions Service Integration Patterns", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html" + }, + { + "text": "AWS Step Functions Developer Guide", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html" + }, + { + "text": "AWS SNS Developer Guide", + "link": "https://docs.aws.amazon.com/sns/latest/dg/welcome.html" + }, + { + "text": "AWS Step Functions Quotas", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/limits.html" + } + ] + }, + "deploy": { + "text": [ + "sam build --use-container", + "sam deploy --guided" + ] + }, + "testing": { + "text": [ + "1. Start a workflow execution using the AWS CLI:", + "export STATE_MACHINE_ARN=$(aws cloudformation describe-stacks --stack-name approval-workflow --query \"Stacks[0].Outputs[?OutputKey=='StateMachineArn'].OutputValue\" --output text)", + "aws stepfunctions start-execution --state-machine-arn $STATE_MACHINE_ARN --input \"{\\\"key\\\": \\\"value\\\"}\"", + "2. Check your email for the approval request", + "3. Click the approve or reject link in the email", + "4. Verify the workflow execution status in Step Functions console" + ] + }, + "cleanup": { + "text": [ + "1. Delete the stack: sam delete", + "2. Confirm stack deletion when prompted", + "3. Check SNS topic and unsubscribe from any remaining email subscriptions" + ] + }, + "authors": [ + { + "name": "Yogesh Nain", + "image": "link-to-your-photo.jpg", + "bio": "Yogesh is Cloud Support Engineer and SME of Lambda, API Gateway at Amazon Web Services.", + "linkedin": "https://www.linkedin.com/in/yogesh-nain-a54133170/" + } + ] +} diff --git a/oneclick-stepfunction-human-approval/img/digram.png b/oneclick-stepfunction-human-approval/img/digram.png new file mode 100644 index 000000000..08419688a Binary files /dev/null and b/oneclick-stepfunction-human-approval/img/digram.png differ diff --git a/oneclick-stepfunction-human-approval/readme.md b/oneclick-stepfunction-human-approval/readme.md new file mode 100644 index 000000000..87aa791a8 --- /dev/null +++ b/oneclick-stepfunction-human-approval/readme.md @@ -0,0 +1,133 @@ +# One Click Manual Approval Workflow in Stepfunction + +This pattern creates a serverless approval workflow using AWS Step Functions, Lambda, SNS, and API Gateway. The workflow sends approval requests via email and handles approvals through a REST API endpoint. + +## Requirements + +* An AWS account with appropriate IAM permissions +* AWS CLI installed and configured +* Git installed +* AWS Serverless Application Model (AWS SAM) CLI installed +* Python 3.13 or later +* A verified email address to receive approval requests + +## Deployment Instructions + +1. Clone the repository: +```bash + git clone + cd serverless-approval-workflow +``` + +2. Deploy the application: +```bash + sam build --use-container + sam deploy --guided +``` + +During the prompts: + - Enter a stack name (e.g., approval-workflow) + - Select your desired AWS Region + - Enter the email address for receiving approval requests + - Allow SAM CLI to create IAM roles with required permissions + - After the initial deployment, subsequent deployments can use sam deploy with the saved configuration (samconfig.toml). + +## How it works +1. The workflow starts when a Step Functions state machine execution is initiated +2. A Lambda function sends an approval request via SNS to the specified email address +3. The email contains approve/reject links that point to an API Gateway endpoint +4. When the recipient clicks either link, the API Gateway triggers a Lambda function +5. The Lambda function sends the approval/rejection response back to Step Functions +6. The workflow completes based on the approval decision +![](img/digram.png) + +## Usage Example and Consideration + +This pattern is particularly valuable when you need to incorporate human decision-making into your AWS Step Functions workflows. Here are key use cases and benefits in the context of Step Functions: +- Change management processes +- Release approvals +- Resource provisioning requests +- Access grant workflows +- Content moderation workflows + +### Use Cases: +1. **Multi-stage Deployment Pipelines** + - Pause an automated deployment for manual verification and approval before proceeding to production. + +2. **Data Processing Workflows** + - Allow human verification of processed data before triggering downstream systems. + +3. **Resource Provisioning** + - Require manual approval for provisioning high-cost or sensitive resources within an automated workflow. + +4. **Compliance and Governance** + - Enforce mandatory human sign-off for actions that require oversight due to regulatory or policy requirements eg: when updating IAM policy through Step Function. + +5. **Error Handling and Remediation** + - Incorporate human intervention in complex error scenarios where automated resolution is not possible. + + +This pattern demonstrates how to enhance your Step Functions workflows with manual approvals, striking a balance between automation and human oversight in critical processes. + +Please review Step Functions, Lambda, and API Gateway service quotas before implementation. + +## Testing +1. Start a workflow execution: +```bash + # Get the State Machine ARN + export STATE_MACHINE_ARN=$(aws cloudformation describe-stacks \ + --stack-name approval-workflow \ + --query "Stacks[0].Outputs[?OutputKey=='StateMachineArn'].OutputValue" \ + --output text) +``` +```bash + # Start execution + aws stepfunctions start-execution \ + --state-machine-arn $STATE_MACHINE_ARN \ + --input "{\"key\": \"value\"}" +``` + + +2. Check your email for the approval request + +3. Click the approve or reject link in the email + +4. Check the execution status: +```bash + aws stepfunctions describe-execution --execution-arn +``` + + +## Expected Output + +Clicking the API Gateway Url from the approval mail will have below output based on approval status: + +API response when approved: +```bash +{ + "message": "Workflow approved successfully" +} +``` + +API response when rejected +```bash +{ + "message": "Workflow rejected" +} +``` + +## Cleanup +Delete the stack using SAM: + +```bash +sam delete +``` +Note: + +- Confirm when prompted to delete the stack and its resources +- You may need to manually delete any SNS subscriptions +- Use sam delete --no-prompts to skip confirmation steps + +Important: This application uses various AWS services that may incur costs beyond the AWS Free Tier. Please review the AWS Pricing page for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +For additional information about AWS Step Functions service integrations, see the [AWS Step Functions Developer Guide](https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html). \ No newline at end of file diff --git a/oneclick-stepfunction-human-approval/src/handle_approval.py b/oneclick-stepfunction-human-approval/src/handle_approval.py new file mode 100644 index 000000000..893c89a2c --- /dev/null +++ b/oneclick-stepfunction-human-approval/src/handle_approval.py @@ -0,0 +1,42 @@ +import boto3 +import json +from urllib.parse import unquote + +def lambda_handler(event, context): + query_params = event.get('queryStringParameters', {}) + # URL decode the task token + task_token = unquote(query_params.get('taskToken', '')) + decision = query_params.get('decision') + + sfn = boto3.client('stepfunctions') + + try: + if decision == 'approve': + sfn.send_task_success( + taskToken=task_token, + output=json.dumps({'approved': True}) + ) + message = "Workflow approved successfully" + else: + sfn.send_task_failure( + taskToken=task_token, + error='Rejected', + cause='User rejected the workflow' + ) + message = "Workflow rejected" + + return { + 'statusCode': 200, + 'body': json.dumps({'message': message}), + 'headers': { + 'Content-Type': 'application/json' + } + } + except Exception as e: + return { + 'statusCode': 500, + 'body': json.dumps({'error': str(e)}), + 'headers': { + 'Content-Type': 'application/json' + } + } diff --git a/oneclick-stepfunction-human-approval/src/requirements.txt b/oneclick-stepfunction-human-approval/src/requirements.txt new file mode 100644 index 000000000..1db657b6b --- /dev/null +++ b/oneclick-stepfunction-human-approval/src/requirements.txt @@ -0,0 +1 @@ +boto3 \ No newline at end of file diff --git a/oneclick-stepfunction-human-approval/src/send_approval_email.py b/oneclick-stepfunction-human-approval/src/send_approval_email.py new file mode 100644 index 000000000..f0108ef28 --- /dev/null +++ b/oneclick-stepfunction-human-approval/src/send_approval_email.py @@ -0,0 +1,42 @@ +import boto3 +import json +import os +from urllib.parse import quote + +def lambda_handler(event, context): + task_token = quote(event['taskToken']) # URL encode the task token + execution_id = event['execution'] + + api_endpoint = os.environ['API_ENDPOINT'] + approve_url = f"{api_endpoint}?taskToken={task_token}&decision=approve" + reject_url = f"{api_endpoint}?taskToken={task_token}&decision=reject" + + # Create message in SNS message format + message_data = { + 'default': 'Workflow Approval Required', + 'email': f""" +Workflow Approval Required - {execution_id} + +A new approval is required for workflow execution {execution_id} + +To approve: {approve_url} + +To reject: {reject_url} + """ + } + + sns = boto3.client('sns') + try: + # Publish message with MessageStructure as 'json' + response = sns.publish( + TopicArn=os.environ['SNS_TOPIC_ARN'], + Message=json.dumps(message_data), + MessageStructure='json' + ) + return { + 'statusCode': 200, + 'body': 'Approval notification sent successfully' + } + except Exception as e: + print(str(e)) + raise diff --git a/oneclick-stepfunction-human-approval/statemachine/approval_workflow.asl.json b/oneclick-stepfunction-human-approval/statemachine/approval_workflow.asl.json new file mode 100644 index 000000000..78d3614a4 --- /dev/null +++ b/oneclick-stepfunction-human-approval/statemachine/approval_workflow.asl.json @@ -0,0 +1,43 @@ +{ + "Comment": "Manual approval workflow with pass/fail states", + "StartAt": "Manual Approval", + "States": { + "Manual Approval": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken", + "Parameters": { + "FunctionName": "${SendApprovalEmailFunctionArn}", + "Payload": { + "execution.$": "$$.Execution.Id", + "taskToken.$": "$$.Task.Token", + "input.$": "$" + } + }, + "Next": "Process Approval", + "TimeoutSeconds": 3600 + }, + "Process Approval": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.approved", + "BooleanEquals": true, + "Next": "Success Pass State" + } + ], + "Default": "Fail" + }, + "Success Pass State": { + "Type": "Pass", + "Result": { + "status": "success", + "message": "Workflow approved and completed successfully" + }, + "End": true + }, + "Fail": { + "Type": "Fail", + "Cause": "Workflow rejected by user" + } + } +} diff --git a/oneclick-stepfunction-human-approval/template.yaml b/oneclick-stepfunction-human-approval/template.yaml new file mode 100644 index 000000000..2880a3b7d --- /dev/null +++ b/oneclick-stepfunction-human-approval/template.yaml @@ -0,0 +1,174 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Approval Workflow with Step Functions and Lambda + +Parameters: + ApproverEmail: + Type: String + Description: Email address of the approver + +Resources: + # SNS Topic + ApprovalTopic: + Type: AWS::SNS::Topic + Properties: + Subscription: + - Protocol: email + Endpoint: !Ref ApproverEmail + + # IAM Roles + SendApprovalEmailFunctionRole: + Type: AWS::IAM::Role + Properties: + 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 + Policies: + - PolicyName: SNSPublish + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: sns:Publish + Resource: !Ref ApprovalTopic + + ApprovalHandlerFunctionRole: + Type: AWS::IAM::Role + Properties: + 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 + Policies: + - PolicyName: StepFunctionsAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - states:SendTaskSuccess + - states:SendTaskFailure + Resource: '*' + + ApprovalWorkflowRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: states.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: LambdaInvoke + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: !GetAtt SendApprovalEmailFunction.Arn + + # Lambda Functions + SendApprovalEmailFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: src/ + Handler: send_approval_email.lambda_handler + Runtime: python3.13 + Role: !GetAtt SendApprovalEmailFunctionRole.Arn + Environment: + Variables: + SNS_TOPIC_ARN: !Ref ApprovalTopic + API_ENDPOINT: !Sub "https://${ApprovalApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/approve" + + ApprovalHandlerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: src/ + Handler: handle_approval.lambda_handler + Runtime: python3.13 + Role: !GetAtt ApprovalHandlerFunctionRole.Arn + + # Step Functions State Machine + ApprovalWorkflow: + Type: AWS::Serverless::StateMachine + Properties: + DefinitionUri: statemachine/approval_workflow.asl.json + DefinitionSubstitutions: + SendApprovalEmailFunctionArn: !GetAtt SendApprovalEmailFunction.Arn + Role: !GetAtt ApprovalWorkflowRole.Arn + Type: STANDARD + + # API Gateway + ApprovalApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: ApprovalApi + + ApprovalApiResource: + Type: AWS::ApiGateway::Resource + Properties: + ParentId: !GetAtt ApprovalApi.RootResourceId + PathPart: 'approve' + RestApiId: !Ref ApprovalApi + + ApprovalApiMethod: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: GET + ResourceId: !Ref ApprovalApiResource + RestApiId: !Ref ApprovalApi + AuthorizationType: NONE + Integration: + Type: AWS_PROXY + IntegrationHttpMethod: POST + Uri: !Sub + - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations + - LambdaArn: !GetAtt ApprovalHandlerFunction.Arn + + ApprovalApiDeployment: + Type: AWS::ApiGateway::Deployment + DependsOn: ApprovalApiMethod + Properties: + RestApiId: !Ref ApprovalApi + + ApprovalApiStage: + Type: AWS::ApiGateway::Stage + Properties: + DeploymentId: !Ref ApprovalApiDeployment + RestApiId: !Ref ApprovalApi + StageName: Prod + + ApiGatewayPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: !Ref ApprovalHandlerFunction + Principal: apigateway.amazonaws.com + SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApprovalApi}/*/GET/approve + +Outputs: + StateMachineArn: + Description: "ARN of the Step Functions State Machine" + Value: !GetAtt ApprovalWorkflow.Arn + + ApiEndpoint: + Description: "API Gateway endpoint URL for Prod stage" + Value: !Sub "https://${ApprovalApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/approve" + + SNSTopicArn: + Description: "ARN of the SNS Topic" + Value: !Ref ApprovalTopic