### Documentation: SSM Automation Runbook with S3 Script Execution This could solve the problem you are looking for, but I don't think its the native solution. Alas, the native solution is so unfindable I had to build something to fit the level of abstraction I wanted. #### Overview This SSM Automation runbook (`Template_Test.ssm.yml`) demonstrates a method to execute a Python script stored in an S3 bucket (`s3://your-s3-bucket/SSM/test.py`) by downloading and running it dynamically within an inline script. The runbook takes a `Message` parameter, passes it to the script, and outputs the processed result. It relies on an IAM role (`SSM_Role`) to provide the necessary permissions for S3 access. #### Runbook Structure ```yaml description: A simple SSM runbook that calls a script from S3 to print and output a message. schemaVersion: '0.3' parameters: Message: type: String description: The message to print and output. default: Hello from the runbook! assumeRole: arn:aws-us-gov(Your partition):iam::000000000(Account Number):role/SSM_Role mainSteps: - name: ExecuteS3Script action: aws:executeScript isEnd: true inputs: Runtime: python3.10 Handler: main InputPayload: Message: '{{ Message }}' Script: | import boto3 import traceback def main(events, context): try: s3 = boto3.client('s3') bucket = 'your-s3-bucket' key = 'SSM/test.py' response = s3.get_object(Bucket=bucket, Key=key) script_content = response['Body'].read().decode('utf-8') namespace = {} exec(script_content, namespace) if 'process_message' not in namespace: raise AttributeError("process_message function not found in the script") result = namespace['process_message'](events) return result except Exception as e: print(f"Error: {str(e)}") print(traceback.format_exc()) raise outputs: - Name: OutputMessage Selector: $.Payload.OutputMessage Type: String ``` ##### **Key Components** 1. **Schema and Parameters**: - `schemaVersion: '0.3'`: Specifies the SSM document schema version, supporting advanced features like `aws:executeScript`. - `parameters.Message`: Accepts a string input, defaulting to `"Hello from the runbook!"`, which is passed to the script. 2. **assumeRole**: - `assumeRole: arn:aws-us-gov:iam::000000000000:role/SSM_Role`: Defines the IAM role that SSM assumes during execution. This role provides the credentials needed for AWS API calls (e.g., S3 access) within the script. 3. **Main Step: ExecuteS3Script**: - `action: aws:executeScript`: Executes a Python script inline within SSM. - `Runtime: python3.10`: Specifies the Python 3.10 runtime environment. - `Handler: main`: Indicates the `main` function as the entry point in the inline script. - `InputPayload.Message: '{{ Message }}'`: Passes the `Message` parameter to the script as part of the `events` dictionary. - `Script`: The inline Python code that: - Uses `boto3` to download `test.py` from S3. - Executes the downloaded script in a separate namespace. - Calls `process_message` from the downloaded script and returns its result. - `outputs`: Captures the `OutputMessage` from the script’s return value. #### **How It Works** 1. **Execution Initiation**: - You run `aws ssm start-automation-execution --document-name "Template_Test" --parameters Message="Test Message"`. - SSM launches an automation execution in an AWS-managed environment. 2. **Role Assumption**: - SSM assumes the `SSM_Role` role specified in `assumeRole`. - The trust policy (below) allows `ssm.amazonaws.com` to assume this role, providing temporary credentials for the execution. 3. **Inline Script Execution**: - The `main(events, context)` function runs: - `events` contains `{"Message": "Test Message"}`. - `context` provides runtime metadata (unused here). - `boto3.client('s3')` uses the role’s credentials to download `test.py` from `s3://your-s3-bucket/SSM/test.py`. - `exec(script_content, namespace)` executes the downloaded script, making its functions (e.g., `process_message`) available in `namespace`. - `namespace['process_message'](events)` processes the input and returns a result (e.g., `{"OutputMessage": "Test Message"}`). 4. **Output Handling**: - The return value from `main` is captured by SSM. - `Selector: $.Payload.OutputMessage` extracts `"Test Message"` as `OutputMessage`. #### **Trust Policy** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ssm.amazonaws.com" ], "AWS": [ "arn:aws-us-gov(Your partition):iam::000000000(Account Number):role/SSM_Role" ] }, "Action": "sts:AssumeRole" } ] } ``` ##### **Why the Trust Policy Is Needed** - **Purpose**: Defines who or what can assume the `SSM_Role` role via `sts:AssumeRole`. - **Principals**: - **Service Principals**: - `"ssm.amazonaws.com"`: Critical here—allows SSM Automation to assume the role during execution, providing credentials for S3 access. - **AWS Principals**: Specific roles that can also assume `SSM_Role` (e.g., for cross-account or delegated access). - **Action**: `sts:AssumeRole` enables these principals to obtain temporary credentials. - **SSM Context**: Without `ssm.amazonaws.com`, SSM couldn’t assume the role, leading to the `NoCredentialsError` you saw earlier. The `assumeRole` field links this policy to the execution. #### **S3 Script (`test.py`)** The script in S3 must define `process_message` to handle the `events` payload: ```python #!/usr/bin/env python3 """Simple templated script for SSM that prints and outputs a message.""" import json def process_message(payload: dict) -> dict: """Process the input message and return it.""" message = payload.get('Message', 'No message provided') print(f"Message received: {message}") # Printed to SSM logs return {'OutputMessage': message} def main(events, context): """Main function for SSM execution.""" # SSM passes InputPayload as 'events' payload = events result = process_message(payload) return result # SSM captures this as output if __name__ == "__main__": # For local testing, simulate SSM input import sys if not sys.stdin.isatty(): payload = json.load(sys.stdin) else: payload = {'Message': 'Hello, world!'} result = process_message(payload) print(json.dumps(result)) ``` - Note: This is the script the Runbook at the top was tested with. #### **Permissions** The `SSM_Role` role must have an attached policy allowing S3 access: ```json { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-s3-bucket/SSM/test.py" } ``` --- ### How It All Ties Together 1. **Runbook Execution**: - SSM starts the automation, assuming the `SSM_Role` role via the trust policy. - The inline script (`main`) executes with the role’s credentials. 2. **S3 Access**: - `boto3.client('s3')` uses the role’s permissions to download `test.py`. - The trust policy ensures these credentials are available. 3. **Dynamic Execution**: - `exec` runs the downloaded script, making `process_message` available. - The inline script calls it and returns the result, which SSM captures as output. --- ### Troubleshooting Tips - **Credentials Failure**: If `NoCredentialsError` reappears, verify the role ARN (`arn:aws-us-gov:iam::000000000000:role/SSM_Role`) and trust policy are correct. - **S3 Access Denied**: Ensure the role’s policy includes the `s3:GetObject` permission for the exact bucket and key. - **Function Not Found**: If `process_message` isn’t found, double-check `test.py` in S3 matches the expected content. --- ### Supporting AWS Documentation - **[SSM Automation Overview](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-automation.html)**: - Explains how SSM Automation executes workflows and uses IAM roles. - **[aws:executeScript Action](https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeScript.html)**: - Details the `Handler`, `Script`, and `InputPayload` fields. Confirms Python handlers must accept `events` and `context`. - **[assumeRole Field](https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-document-fields.html#assumeRole)**: - Describes how `assumeRole` specifies the IAM role for execution, assumed by SSM via `sts:AssumeRole`. - **[IAM Trust Relationships](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console)**: - Explains how to configure a trust policy for service principals like `ssm.amazonaws.com`. - **[Boto3 Credentials](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#guide-credentials)**: - Notes that `boto3` uses the execution role’s credentials in AWS-managed environments like SSM Automation. ---