Installation guide
This guide is for the platform team installing Cloud Certainty StateCraft in their AWS account. The install is one CloudFormation stack (the hub). Every other account whose state machines StateCraft should reach gets a small role stack (the spoke). With IAM Identity Center or another SAML IdP, the hub takes two steps: deploy the stack, configure the SAML application, then update the stack. For the short version, see Getting started.
Before you start
- Region. Any Region with AWS Step Functions, AWS Lambda, Amazon API Gateway (HTTP APIs), Amazon
DynamoDB, Amplify Hosting, Amazon Cognito, AWS License Manager and Amazon ECS on Fargate. Voice
also needs Amazon Transcribe streaming in the stack's Region, and assist needs Amazon Bedrock with
the Claude model in
AssistRegion. The templates are always loaded from the vendor's public bucket in us-east-1 (CloudFormation accepts a template URL from any Region), and all product code comes from the container image. The hub's Region doesn't limit which Regions you can work in: list them inLocalRegionsand in each connection. - Account. Deploy the hub in the account that subscribed on AWS Marketplace: the licence is delivered to that account through AWS License Manager. To run the hub in another account of your organisation, distribute the licence to it with a License Manager grant first.
- Permissions. You need permission to create the resources in the stack: IAM roles (some have fixed names), Lambda, Step Functions, ECS, EC2 security groups, Cognito, API Gateway, Amplify, DynamoDB, S3, SNS, EventBridge, KMS and CloudWatch Logs.
- Network. A VPC with at least one subnet with outbound HTTPS access, for the one-off installer task (see Networking). Nothing else runs in your VPC.
- Identity. Decide how users will sign in: IAM Identity Center, another SAML 2.0 IdP (Entra ID, Okta and others), an OIDC IdP, or Cognito-managed users. SAML IdPs other than IAM Identity Center, and OIDC, need the Team plan or above.
- Your state machines. Decide the access level per account (
Read,OperateorFull), note the execution roles your state machines use (StateCraft passes only the ones you list to Step Functions, see What StateCraft may do), and the customer managed KMS keys of any encrypted state machines.
1. Subscribe
- Open StateCraft in AWS Marketplace and choose Continue to Subscribe. Choose the plan and the contract length (Team is the base plan; Business and Enterprise are add-ons to it) and accept the offer. AWS License Manager now holds your licence.
- Choose Continue to Configuration, select the fulfilment option and the Region, then open the CloudFormation launch link from the usage instructions.
2. Launch the hub stack
In the CloudFormation console, fill in the parameters (see the reference). Acknowledge "AWS CloudFormation might create IAM resources with custom names" and create the stack. Creation takes about 10 minutes.
The quick-create link, with the release version filled in:
https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate?templateURL=https://cloudcertainty-statecraft-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/main.yaml&stackName=statecraftFrom the AWS CLI (--region is the Region you deploy to):
aws cloudformation create-stack --region <your-region> --stack-name statecraft \
--template-url https://cloudcertainty-statecraft-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/main.yaml \
--parameters file://params.json \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAMparams.json lists the parameters as [{"ParameterKey": "VpcId", "ParameterValue": "vpc-..."}, ...].
What happens during creation
The product's code (the Lambda functions and the web app) ships inside the subscription-protected container image, not as public downloads. Early in stack creation:
- CloudFormation creates a private S3 bucket in your account (output
ArtifactsBucketName; all public access blocked, encrypted, TLS only). - The
ArtifactInstallcustom resource starts one Fargate task from the product image in yourInstallerSubnetIds. The task checks your AWS Marketplace licence in AWS License Manager, verifies every file of the code bundle against the SHA-256 checksums in the image, and copies it into the bucket under the release's version. It runs for about a minute at 0.25 vCPU and 512 MB, with a read-only root filesystem and a security group without inbound rules. - The Lambda functions, StateCraft's own bulk jobs workflow (a Step Functions Standard state
machine named
<NamePrefix>-bulk, whose items run as Express child executions of a Distributed Map) and the web app are then created from that bucket.
If the installer task fails, stack creation fails and rolls back. The error message gives the task's
exit code and its log stream (log group /<NamePrefix>/installer); see
Troubleshooting.
3. Connect your identity provider
IdentityProvider | What to do |
|---|---|
IAMIdentityCenter | Follow Identity providers. Create the app with the SamlAcsUrl and SamlAudienceUri outputs, then update the stack with SamlMetadataUrl. |
SAML | Team and above. Do the same in your IdP (Entra ID and Okta are covered in Identity providers). |
OIDC | Team and above. Put the client secret in Secrets Manager, then set OidcIssuer, OidcClientId and OidcClientSecretArn when you create the stack. Register <CognitoDomain>/oauth2/idpresponse as the redirect URI in your IdP. |
CognitoOnly | Nothing more to do. BootstrapAdminEmail receives a temporary password. Every user must register an authenticator app (TOTP MFA is required). |
Until SamlMetadataUrl is set, the portal falls back to Cognito sign-in, which has no users. Nobody
can sign in at that point, so the stack is safe in that state.
4. First sign-in
Open the PortalUrl output and sign in as BootstrapAdminEmail. The first account that signs in
with that (verified) email becomes the bootstrap administrator and stays bound to it (see the
admin guide).
The local connection to the hub's own account exists from the start. It uses the stack's local
role, which may do what LocalAccessLevel allows, in the Regions of LocalRegions. Administrators
see every state machine there. Other users see nothing until an administrator grants them access
(admin guide).
What StateCraft may do
The local role and every spoke role are the upper limit of what StateCraft can do in an account, for everyone, administrators included. StateCraft then narrows each request to the person's grants with a session policy.
| What | Parameter (spoke) | Default | Notes |
|---|---|---|---|
| Access level | LocalAccessLevel (AccessLevel) | Operate | Read: list state machines and read their definitions, versions, aliases, tags, CloudWatch metrics, executions, event histories, Map Runs and the CloudWatch Logs of Express workflows. Operate: also start, stop and redrive executions, run TestState, change a running Map Run's concurrency and failure tolerance, and bulk jobs. Full: also create, update and delete state machines, publish and delete versions, create, change and delete aliases, and change tags, logging and tracing |
| Roles passed to Step Functions | PassRoleArns (PassRoleArns) | * | With Operate (TestState with a real call) and Full (deploys), StateCraft passes an execution role to Step Functions, and only to Step Functions (iam:PassedToService = states.amazonaws.com). List the role ARNs or patterns your state machines use, for example arn:aws:iam::123456789012:role/sfn-*. * means every role in the account; StateCraft's own roles under /statecraft/ are never passable. Narrow it for production accounts |
| Encrypted state machines | StateMachineKmsKeyArns (StateMachineKmsKeyArns) | empty | StateCraft uses customer managed KMS keys (kms:Decrypt to show data, kms:GenerateDataKey to start executions) only through Step Functions (kms:ViaService). Empty allows any key your key policies allow; list the keys of your state machines to limit it to them. Not needed for state machines encrypted with AWS owned keys |
| Express executions | (automatic) | Reads of CloudWatch Logs log groups in the account (logs:FilterLogEvents, logs:GetLogEvents), used only for the log groups of Express state machines a person may view |
With Read, nobody can change anything through StateCraft, whatever their grants. People without
the data permission see executions without their inputs, outputs and error causes: StateCraft asks
Step Functions for metadata only, and for state machines encrypted with a customer managed key, their
AWS session can't decrypt the data either.
The roles have no IAM permissions other than iam:PassRole to Step Functions, and no
CloudFormation, DynamoDB or StateCraft bucket permissions, so StateCraft can't change its own stack.
Voice
With Voice = Enabled (the default), the stack creates a role used only to sign Amazon Transcribe
streaming sessions in the stack's Region (transcribe:StartStreamTranscriptionWebSocket, nothing
else). When someone presses the microphone, the API signs a WebSocket URL valid for 5 minutes for
that person, and the browser streams the audio straight to Amazon Transcribe in your account.
StateCraft never stores audio or transcripts. Administrators choose the speech engine and language
under Administration › Settings (admin guide).
What your environment needs for voice:
- Amazon Transcribe streaming in the stack's Region, and no SCP that denies it.
- Microphone permission. Browsers ask each person once per site. Managed browsers may block the
microphone by policy (in Chrome and Edge,
AudioCaptureAllowedandAudioCaptureAllowedUrls); allow it for thePortalUrl. - Content Security Policy. The portal's CSP allows the browser to connect only to your sign-in,
your API and
wss://transcribestreaming.<stack region>.amazonaws.com:8443, and its Permissions-Policy allows the microphone for the portal itself only. If you put a proxy or your own security headers in front of the portal, keep both, or voice stops working (typed commands still work). - Network. Users' networks must allow outbound WebSocket connections on port 8443 to that endpoint.
- Data use. Amazon Transcribe may use content it processes to improve AWS services unless your organisation has an AWS Organizations AI services opt-out policy. Set one if your policy requires it.
With Voice = Disabled, no voice role is created and the Amazon Transcribe engine is unavailable;
typed commands still work, and an administrator can still choose the browser's own speech service
(which sends audio to the browser vendor, outside your AWS account).
Assist
Assist (Business plan and above) turns a description into a workflow, or a change to one, with Claude on Amazon Bedrock in your account. It is off until an administrator turns it on under Administration › Settings. To use it:
- Pick the model and Region.
AssistModelIdis the Bedrock model id, by default Claude Opus 5.5 (anthropic.claude-opus-5-5); empty turns assist off and grants no Bedrock permission.AssistRegionis the Region of the Bedrock endpoint, by default the stack's Region. StateCraft calls the Amazon Bedrock Mantle endpoint (bedrock-mantle.<region>.api.aws), and the stack's API role may call only that model there (bedrock-mantle:CreateInference). - Make the model available to the account. In the Amazon Bedrock console of
AssistRegion, check that the model is available to your account; the first use of an Anthropic model may ask you to submit your use case details once. Make sure no SCP denies Amazon Bedrock (bedrock-mantle:*) in that Region. - Turn assist on under Administration › Settings.
Each request sends only the person's description and, for a change, the current definition; the result comes back as a diff that the person accepts or rejects. Bedrock bills the tokens to your account. If the model isn't available, assist shows the AWS error and nothing else is affected.
5. Deploy the spoke role in other accounts
spoke-role.yaml creates one role, /statecraft/<RoleName>, that only the hub's API role and its
bulk job worker role (stack output SpokeTrustedPrincipal) can assume, only with your external ID,
and only with a user's email as the source identity. IAM is global, so deploy it once per account; the connection then covers
every Region you list. Its template URL is the stack output SpokeTemplateUrl:
https://cloudcertainty-statecraft-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/spoke-role.yaml| Parameter | Default | Meaning |
|---|---|---|
TrustedPrincipal | The hub's SpokeTrustedPrincipal output: two role ARNs (the API and the bulk job worker), comma-separated. Copy the whole value. Required | |
ExternalId | A secret of 16 to 128 characters (letters, digits and +=,.@:/-). Enter the same value when you add the connection in StateCraft. Required | |
RoleName | statecraft-spoke | Role name; the role is created under the IAM path /statecraft/, the only path the hub may assume |
AccessLevel | Operate | Read, Operate or Full, as LocalAccessLevel |
PassRoleArns, StateMachineKmsKeyArns | *, empty | As on the hub (What StateCraft may do) |
The role is the upper limit of what StateCraft can do in that account; grants narrow it per user or
group but never widen it. The stack's outputs are RoleArn (enter it in the connection) and
AccessLevel (choose the same level for the connection).
A single account
cat > spoke-params.json <<'EOF'
[
{"ParameterKey": "TrustedPrincipal", "ParameterValue": "<SpokeTrustedPrincipal output>"},
{"ParameterKey": "ExternalId", "ParameterValue": "<a secret of at least 16 characters>"},
{"ParameterKey": "AccessLevel", "ParameterValue": "Operate"},
{"ParameterKey": "PassRoleArns", "ParameterValue": "arn:aws:iam::<account>:role/sfn-*"}
]
EOF
aws cloudformation create-stack --region eu-west-1 --stack-name statecraft-spoke \
--template-url https://cloudcertainty-statecraft-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/spoke-role.yaml \
--parameters file://spoke-params.json --capabilities CAPABILITY_NAMED_IAMEvery account of an AWS Organization (StackSets)
With service-managed StackSets, new accounts in the target OUs get the role automatically. Use one
external ID for the whole StackSet, and enter that same external ID for every connection you create
for those accounts. PassRoleArns patterns without an account ID (for example
arn:aws:iam::*:role/sfn-*) suit a StackSet.
# From the management account or a delegated StackSets administrator.
aws cloudformation create-stack-set --region us-east-1 --stack-set-name statecraft-spoke \
--template-url https://cloudcertainty-statecraft-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/spoke-role.yaml \
--parameters file://spoke-params.json \
--capabilities CAPABILITY_NAMED_IAM \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# One Region is enough: IAM is global.
aws cloudformation create-stack-instances --region us-east-1 --stack-set-name statecraft-spoke \
--deployment-targets OrganizationalUnitIds=ou-abcd-11111111 --regions eu-west-1Service-managed StackSets don't deploy to the management account; use a normal stack there if
StateCraft should reach it. To change AccessLevel later, update the StackSet, then the access level
of the connections.
Add the connection
In Administration › Connections › Add connection, enter an id (used in links, it can't be
changed later), a name, the account ID, the access level, the spoke stack's RoleArn, the external
ID and the Regions. Choose Test: StateCraft assumes the role and counts the state machines in
each Region. See the admin guide.
Parameter reference
| Parameter | Default | Description |
|---|---|---|
NamePrefix | statecraft | 2 to 20 lowercase letters, digits and hyphens, starting with a letter. It prefixes resource and role names, so it must be unique in the account and Region. Don't use aws, amazon or cognito: it forms part of the Cognito domain. |
TagOwner | platform-team | Value of the Owner tag on every taggable resource. |
TagCostCenter | none | Value of the CostCenter tag on every taggable resource. |
TagEnvironment | prod | Value of the Environment tag on every taggable resource. |
IdentityProvider | IAMIdentityCenter | IAMIdentityCenter, SAML, OIDC or CognitoOnly. SAML (other than IAM Identity Center) and OIDC need the Team plan or above. See Identity providers. |
SamlMetadataUrl | empty | The metadata URL of your SAML application (IAMIdentityCenter or SAML only). Leave it empty on the first deployment. |
OidcIssuer | empty | OIDC only, required: the issuer URL. |
OidcClientId | empty | OIDC only, required: the client ID. |
OidcClientSecretArn | empty | OIDC only, required: the ARN of a Secrets Manager secret whose value is the client secret. CloudFormation resolves it at deploy time. |
OidcEmailVerifiedClaim | email_verified | OIDC only: the IdP claim that says the email is verified. Use xms_edov for Microsoft Entra ID. |
BootstrapAdminEmail | The first administrator. With CognitoOnly the user is created and receives a temporary password by email. | |
LocalAccessLevel | Operate | The most StateCraft may do in this account, for every user: Read, Operate or Full (What StateCraft may do). |
LocalRegions | empty | Comma-separated Regions whose state machines to show for this account, for example eu-west-1,us-east-1. Empty means the stack's Region only. |
PassRoleArns | * | Execution roles StateCraft may pass to Step Functions in this account (TestState with Operate, deploys with Full); wildcards allowed. * means every role in the account except StateCraft's own. |
StateMachineKmsKeyArns | empty | Customer managed KMS keys of your state machines, to limit which keys StateCraft may use (always only through Step Functions). Empty allows any key your key policies allow. |
Voice | Enabled | Create the role that signs Amazon Transcribe streaming sessions for voice commands (Voice). Disabled creates none. |
AssistModelId | anthropic.claude-opus-5-5 | The Claude model on Amazon Bedrock for assist (Business and above); empty turns assist off and grants no Bedrock permission (Assist). |
AssistRegion | empty | The Region of the Amazon Bedrock endpoint for assist. Empty means the stack's Region. |
VpcId | VPC for the one-off installer task. | |
InstallerSubnetIds | Subnets for the installer task, in VpcId (a template rule checks it), with outbound HTTPS (see Networking). | |
InstallerSubnetsArePublic | false | true only for subnets that route to an internet gateway; the task then gets a public IP for outbound access. |
LogRetentionDays | 90 | Retention of StateCraft's own CloudWatch log groups, including the audit events written there and the bulk jobs workflow's logs. |
KmsKeyArn | empty | Optional customer managed KMS key (see Encryption). Empty uses AWS owned keys and SSE-S3. |
UpdateChecks | Enabled | Once a day, read the public release manifest from the vendor's bucket and announce a newer release to administrators and on the update SNS topic. Nothing about you, your users or your account is sent. |
UpdateNotificationEmail | empty | Optional email address subscribed to the update topic. |
ArtifactVersionOverride, ImageUriOverride | empty | Advanced (support use only). Leave empty unless Cloud Certainty support asks you to set them. |
Networking
Only the installer task runs in your VPC, for about a minute at install and at each upgrade. It needs outbound HTTPS (443) to:
- Amazon ECR in us-east-1, to pull the image from the AWS Marketplace registry (cross-Region, so this needs internet egress: a NAT gateway or public subnets);
- AWS License Manager in your Region, for the licence check;
- Amazon S3 in your Region, to write the code into the artifacts bucket (a gateway endpoint works);
- CloudWatch Logs in your Region, for its log stream.
Its security group has no inbound rules and one outbound rule, TCP 443. The Lambda functions don't run in your VPC; they reach Step Functions, CloudWatch, CloudWatch Logs, STS and, for assist, Amazon Bedrock in your accounts over AWS's public endpoints. Voice audio goes from users' browsers straight to Amazon Transcribe in the stack's Region.
Encryption and KMS
Without KmsKeyArn, the DynamoDB tables, the log groups, the update SNS topic, the artifacts bucket
and the bulk jobs workflow use AWS owned keys and SSE-S3.
With KmsKeyArn, your key encrypts all of them. The key must be a symmetric customer managed key in
the stack's Region (give its ARN, not an alias), and its key policy must:
- let the account's IAM policies grant its use (the default key policy statement for the account root does this). The stack's roles use it only through DynamoDB, CloudWatch Logs, SNS, S3 and Step Functions;
- allow
logs.<region>.amazonaws.comto use it for the stack's log groups, anddelivery.logs.amazonaws.comfor the bulk jobs workflow's log delivery; - allow
sns.amazonaws.comto use it for the update topic.
The identity that creates or updates the stack also needs kms:Decrypt on the key, because Lambda
reads the function code from the encrypted artifacts bucket with the deploying identity's
permissions.
The key protects StateCraft's own data. Your state machines keep their own encryption; list their
keys in StateMachineKmsKeyArns.
Stack outputs
| Output | Use |
|---|---|
PortalUrl | The URL users and admins open. Also the application start URL in your IdP. |
ApiUrl | HTTP API base URL (informational). |
CognitoDomain | Cognito managed login domain. The OIDC redirect URI is <CognitoDomain>/oauth2/idpresponse. |
UserPoolId, UserPoolClientId | Cognito identifiers. |
SamlAcsUrl, SamlAudienceUri | Values for your SAML application. |
SpokeTrustedPrincipal | The hub role ARN for the spoke template's TrustedPrincipal. |
SpokeTemplateUrl | URL of this release's spoke-role.yaml. |
LocalAccessRoleArn | The role of the local connection. |
VoiceRoleArn | The role whose sessions sign Amazon Transcribe streaming URLs (with Voice = Enabled). |
BulkStateMachineArn | StateCraft's own bulk jobs workflow. |
UpdateTopicArn | SNS topic that announces new releases. Subscribe your own endpoints to it. |
TableName | DynamoDB table (retained when the stack is deleted). |
ArtifactsBucketName | Private bucket with the stack's code. Emptied and deleted with the stack; store nothing else in it. |
ProductVersion | The StateCraft release the stack runs. |
Updating
Each release is a new template; there is no version parameter. To upgrade, update the stack with the new release's template (Update stack › Replace current template) and keep every other parameter value. See the upgrading guide.
To change a setting such as LocalAccessLevel, LocalRegions, PassRoleArns, Voice or
SamlMetadataUrl, update the stack with the current template and change only that parameter.
Deleting
Delete the hub stack. The DynamoDB table (
<NamePrefix>-table) is retained on purpose, with its settings, grants, drafts, saved inputs, execution records, bulk jobs and audit log. Everything else is deleted: the cache table (with the usage counters), the Cognito user pool (Cognito-only users are lost), the artifacts bucket, the bulk jobs workflow and the log groups. A new stack with the sameNamePrefixcan't be created while the retained table exists.To delete the table when you no longer need it, turn off its deletion protection, then delete it. Your own state machines are never touched by deleting StateCraft.
Delete the spoke stacks or the StackSets in the other accounts.
Delete the SAML or OIDC application in your identity provider.
Troubleshooting
| Symptom | Check |
|---|---|
Stack creation or update fails at ArtifactInstall | The status reason gives the installer's exit code and log stream (log group /<NamePrefix>/installer). 2: the image has no valid code bundle; contact support. 3: the account has no StateCraft licence: subscribe on AWS Marketplace, or deploy in the subscribing account (see Before you start). 4: the upload to the artifacts bucket failed; check SCPs, the S3 endpoint policy and, with KmsKeyArn, the key policy. 5: AWS License Manager stayed unavailable for the whole retry budget; retry the stack operation later. No exit code, with CannotPullContainerError or ResourceInitializationError: the subnets can't reach ECR in us-east-1, S3 or CloudWatch Logs. "The installer task did not finish in time": the same network checks. |
| Every page says a licence is required | Administration › Licence and about shows what License Manager returned. The subscription may have expired, or the stack runs in an account without the licence. Choose Check licence now after renewing. |
A connection test fails at AssumeRole | The spoke's TrustedPrincipal must be the hub's whole SpokeTrustedPrincipal value (both role ARNs), the external IDs must match, and the role must be under /statecraft/. An SCP can also deny sts:AssumeRole, sts:SetSourceIdentity or sts:TagSession. |
| Start, stop or redrive buttons are disabled for everyone | The connection's access level is Read. Update the stack (or the spoke stack) to Operate, then the connection. |
A deploy fails with AccessDenied on iam:PassRole | The state machine's execution role isn't in PassRoleArns of that account's stack, or the connection is not Full. |
| An encrypted state machine shows no inputs or outputs, even for administrators | StateMachineKmsKeyArns lists keys but not this one, or the key policy doesn't let the account's IAM policies grant its use. |
| "Turn on logging at level ALL" on an Express state machine | StateCraft reads Express executions from the machine's CloudWatch Logs; set its logging level to ALL (with execution data if people should see inputs and outputs). |
| The microphone button is missing or fails | Administration › Settings may have voice off; Voice may be Disabled; the browser or a managed browser policy may block the microphone; a proxy may have replaced the portal's CSP; or the network blocks port 8443 to Amazon Transcribe (see Voice). Typed commands always work. |
| Assist says the model isn't available | The model in AssistModelId isn't available to the account in AssistRegion, or an SCP denies bedrock-mantle actions there (see Assist). |
| A user gets "too many grants" | The user's grants make a session policy larger than AWS allows. Use group grants and wildcard patterns (for example orders-*) instead of many single names. |
ip_not_allowed | Administration › Settings, network access. The API sees the client's public IP. |
| A user is refused because their email isn't trusted | See Identity providers: the email must be asserted by SAML, verified by OIDC, or marked verified in Cognito. |
| SAML error after sign-in | The ACS URL and audience match the outputs; the email attribute is sent; the user is assigned to the app. |
Something unclear or missing? Email [email protected].