Installation guide
This guide is for the platform team installing Cloud Certainty SQLark in their AWS account. The install is one CloudFormation stack (the hub). Every other account whose Athena workgroups and Glue Data Catalog SQLark 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 Amazon Athena, AWS Glue, AWS Lambda, Amazon API Gateway (HTTP APIs),
Amazon DynamoDB, Amplify Hosting, Amazon Cognito, AWS License Manager and Amazon ECS on Fargate.
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 query: list them in
LocalRegionsand 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, ECS, EC2 security groups, Cognito, API Gateway, Amplify, DynamoDB, S3, Athena, 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 data. Note the S3 buckets your Athena tables read, the result buckets of the workgroups you already use, and any customer managed KMS keys on that data. SQLark may only use what you list (see Data access).
1. Subscribe
- Open SQLark 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-sqlark-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/main.yaml&stackName=sqlarkFrom the AWS CLI (--region is the Region you deploy to):
aws cloudformation create-stack --region <your-region> --stack-name sqlark \
--template-url https://cloudcertainty-sqlark-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 and the web app are then created from that bucket.
- With
CreateWorkgroup = Enabled(the default), the stack also creates the SQLark workgroup (named afterNamePrefix, sosqlarkby default) and its results bucket<NamePrefix>-results-<account>-<region>(see The SQLark workgroup).
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 workgroup and database there. Other users see nothing until an administrator grants them
access (admin guide).
Data access
The local role and every spoke role are the upper limit of what SQLark can do in an account. Athena reads the Glue Data Catalog, your table data and KMS keys with the credentials of the person running the query, so the role must allow them, and SQLark then narrows each session to that person's grants.
| What | Parameter | Default | Notes |
|---|---|---|---|
| Access level | LocalAccessLevel (spoke: AccessLevel) | Query | Query: browse the catalogue, run read-only queries, write results to the result buckets. Full: also CREATE TABLE AS, INSERT, ALTER, DROP, MSCK and the like (Glue writes, and S3 writes and deletes on the data buckets). Write statements also need the Team plan and a grant with ddl |
| Table data | DataBucketNames | * | The buckets your tables read, for example sales-lake,clickstream-raw; wildcards such as analytics-* work. * means every bucket your bucket policies and Lake Formation let this account read. Narrow it for production accounts |
| Results of existing workgroups | ResultBucketNames | empty | The buckets where your existing workgroups write results. Users can run queries only in workgroups whose result location SQLark may write to, or whose results are managed by Athena |
| SQLark result buckets | (automatic) | Every bucket named <NamePrefix>-results-<account>-* in the account (spoke: <WorkgroupName>-results-<account>-*), only if the account owns it | |
| KMS keys | DataKmsKeyArns | empty | Customer managed keys on your data, your result buckets or your Glue Data Catalog. Not needed for SSE-S3 or the AWS managed aws/s3 key |
| Lake Formation | (automatic) | lakeformation:GetDataAccess is allowed; your Lake Formation permissions still decide. Grant them to the local and spoke roles if your tables are governed by Lake Formation |
The roles have no IAM, CloudFormation or DynamoDB permissions, and the local role is explicitly denied SQLark's own artifacts bucket, so SQLark can't change its own stack.
The SQLark workgroup
With CreateWorkgroup = Enabled, the hub stack (and each spoke stack) creates an Athena workgroup
made for SQLark:
| Setting | Value |
|---|---|
| Name | NamePrefix (spoke: WorkgroupName), sqlark by default |
| Results | s3://<NamePrefix>-results-<account>-<region>/, encrypted with KmsKeyArn (spoke: ResultsKmsKeyArn) or SSE-S3, public access blocked, bucket owner enforced, TLS only, results deleted after ResultsRetentionDays (30 by default) |
| Enforced configuration | Yes: clients can't change the result location or encryption |
| Data scanned limit per query | BytesScannedCutoffPerQuery, 1 TB by default (about 5 US dollars); 0 for none |
| Engine | WorkgroupEngineVersion: AUTO (default) or Athena engine version 3 |
| CloudWatch metrics | On |
The workgroup is created without grants: in Administration › Grants, give your teams access
to it. The results bucket is kept when the stack is deleted, because it may still hold query
results; delete it yourself when you no longer need it (and before you create a stack with the same
NamePrefix in that Region again).
You can also use your existing workgroups. Their result location must be in ResultBucketNames
(or results must be managed by Athena), and enforcing the workgroup configuration is recommended.
5. Deploy the spoke role in other accounts
spoke-role.yaml creates one role, /sqlark/<RoleName>, that only the hub's API role (stack output
SpokeTrustedPrincipal) can assume, only with your external ID, and only with a user's email as the
source identity, and optionally a SQLark workgroup with its results bucket. Its template URL is the
stack output SpokeTemplateUrl:
https://cloudcertainty-sqlark-us-east-1.s3.us-east-1.amazonaws.com/pro/<version>/spoke-role.yaml| Parameter | Default | Meaning |
|---|---|---|
TrustedPrincipal | The hub's SpokeTrustedPrincipal output (one role ARN). Required with CreateRole = Enabled | |
ExternalId | A secret of 16 to 128 characters (letters, digits and +=,.@:/-). Enter the same value when you add the connection in SQLark. Required with CreateRole = Enabled | |
CreateRole | Enabled | Disabled creates only the workgroup and results bucket, for an extra Region of an account that already has the role |
RoleName | sqlark-spoke | Role name; the role is created under the IAM path /sqlark/, the only path the hub may assume |
AccessLevel | Query | Query or Full, as LocalAccessLevel |
DataBucketNames, ResultBucketNames, DataKmsKeyArns | *, empty, empty | As on the hub (Data access) |
CreateWorkgroup | Enabled | Create a SQLark workgroup in this Region |
WorkgroupName | sqlark | Its name; the results bucket is <WorkgroupName>-results-<account>-<region> and the role may write to every bucket named <WorkgroupName>-results-<account>-* |
WorkgroupEngineVersion, BytesScannedCutoffPerQuery, ResultsRetentionDays | AUTO, 1 TB, 30 | As on the hub |
ResultsKmsKeyArn | empty | Optional customer managed key for the results bucket and the workgroup's results |
The role is the upper limit of what SQLark can do in that account; grants narrow it per user or
group but never widen it. IAM is global, so create the role once per account. For a SQLark
workgroup in another Region of the same account, deploy the template again in that Region with
CreateRole = Disabled and the same WorkgroupName. The stack's outputs are RoleArn (enter it in
the connection), AccessLevel (choose the same level for the connection), WorkgroupName and
ResultsBucketName.
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": "Query"},
{"ParameterKey": "DataBucketNames", "ParameterValue": "*"}
]
EOF
aws cloudformation create-stack --region eu-west-1 --stack-name sqlark-spoke \
--template-url https://cloudcertainty-sqlark-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.
# From the management account or a delegated StackSets administrator.
aws cloudformation create-stack-set --region us-east-1 --stack-set-name sqlark-spoke \
--template-url https://cloudcertainty-sqlark-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 for the role (IAM is global): the Region where your data and workgroup are.
aws cloudformation create-stack-instances --region us-east-1 --stack-set-name sqlark-spoke \
--deployment-targets OrganizationalUnitIds=ou-abcd-11111111 --regions eu-west-1For SQLark workgroups in more Regions, create a second StackSet with CreateRole = Disabled and
deploy it to those Regions. Service-managed StackSets don't deploy to the management account; use a
normal stack there if SQLark 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: SQLark assumes the role and lists the workgroups and databases
in each Region. See the admin guide.
Parameter reference
| Parameter | Default | Description |
|---|---|---|
NamePrefix | sqlark | 2 to 20 lowercase letters, digits and hyphens, starting with a letter. It prefixes resource and role names, names the SQLark workgroup and its results bucket, 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 | Query | The most SQLark may do in this account, for every user: Query or Full (Data access). |
LocalRegions | empty | Comma-separated Regions whose workgroups and catalogue to show for this account, for example eu-west-1,us-east-1. Empty means the stack's Region only. |
DataBucketNames | * | Buckets your tables read; wildcards allowed. |
ResultBucketNames | empty | Result buckets of your existing workgroups. |
DataKmsKeyArns | empty | Customer managed KMS keys of your data, results or catalogue. |
CreateWorkgroup | Enabled | Create the SQLark workgroup and its results bucket. |
WorkgroupEngineVersion | AUTO | AUTO or Athena engine version 3. |
BytesScannedCutoffPerQuery | 1099511627776 (1 TB) | Athena cancels a query in the SQLark workgroup after scanning this many bytes. At least 10485760 (10 MB), or 0 for no limit. |
ResultsRetentionDays | 30 | Days to keep result files in the SQLark results bucket (1 to 3,650). |
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 SQLark's own CloudWatch log groups, including the audit events written there. |
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 Athena, Glue and STS in your accounts over AWS's public endpoints. Athena itself reads your S3 data inside AWS.
Encryption and KMS
Without KmsKeyArn, the DynamoDB tables, the log groups, the update SNS topic, the artifacts bucket
and the SQLark results bucket use AWS owned keys and SSE-S3, and the SQLark workgroup encrypts
results with SSE-S3.
With KmsKeyArn, your key encrypts all of them, and the SQLark workgroup encrypts its results with
SSE-KMS under it. 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 API role uses it only through DynamoDB; the local role only through S3, to write and read query results;
- allow
logs.<region>.amazonaws.comto use it for the stack's log groups; - 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 SQLark's own data and the SQLark workgroup's results. Your tables keep their own
encryption; list their keys in DataKmsKeyArns.
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. |
WorkgroupName, ResultsBucketName, ResultsLocation | The SQLark workgroup and its results (with CreateWorkgroup = Enabled). |
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 SQLark 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, DataBucketNames 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, saved queries, dashboards, history and audit log, and so is the SQLark results bucket. Everything else is deleted: the cache table (with the usage counters), the Cognito user pool (Cognito-only users are lost), the artifacts bucket, the SQLark workgroup and the log groups. A new stack with the sameNamePrefixcan't be created while the retained table or results bucket exists.To delete the table when you no longer need it, turn off its deletion protection, then delete it. Empty and delete the results bucket the same way.
Delete the spoke stacks or the StackSets in the other accounts (their results buckets are kept too).
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 SQLark 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. |
Stack creation fails at ResultsBucket with "already exists" | A results bucket from an earlier stack with the same NamePrefix (or WorkgroupName) in this account and Region was kept. Delete it, or choose another prefix. |
Stack creation fails at Workgroup | A workgroup with that name already exists in the Region. Choose another NamePrefix (spoke: WorkgroupName), or set CreateWorkgroup = Disabled. |
| 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 SpokeTrustedPrincipal, the external IDs must match, and the role must be under /sqlark/. An SCP can also deny sts:AssumeRole, sts:SetSourceIdentity or sts:TagSession. |
| "This workgroup has no query result location" | Set an output location on the workgroup (and add its bucket to ResultBucketNames), turn on managed query results, or use the SQLark workgroup. |
A query fails with Access Denied on S3 | The table's bucket isn't in DataBucketNames, the result bucket isn't in ResultBucketNames, a KMS key is missing from DataKmsKeyArns, or a bucket policy, SCP or Lake Formation permission refuses the role. The error names the bucket or key. |
| A query fails with "Insufficient Lake Formation permission" | Grant the local or spoke role the Lake Formation permissions on those tables (data location, SELECT, DESCRIBE). |
| "Athena is busy: too many queries are running" | The account's active query quota in that Region (20 to 200) is used up. SQLark retries dashboard panels on its own; ask AWS for a higher quota if it happens often. |
| 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 sales_*) 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].