Skip to content

Permissions your credentials need

When you connect a cloud provider, the access you grant determines what Sencai can do on that account. This page describes what each operation actually needs, so you can scope a credential narrowly instead of granting an account-wide admin key by default.

Read-only discovery - used to validate a credential and to run an inventory scan. Sencai only lists and reads existing resources at this level: instances/servers, and - depending on the provider - networks, volumes, load balancers, and a handful of other resource kinds. Nothing is created, changed, or deleted.

Full provisioning - used when you create, resize, start, stop, or terminate an instance through Sencai. This needs write access on top of the read-only set above, scoped to whatever resources that operation touches (creating an instance, for example, also needs enough access to create the disk it boots from).

Exactly which resource kinds a credential can see and manage depends on the provider - coverage isn’t identical across all of them (see below).

Every provider’s own console lets you create some form of scoped credential - an IAM policy, a role, or a token with limited scopes. Where that’s available, prefer it over an account-wide administrator credential:

  • Grant only the operations you actually intend to use through Sencai - read-only if you only want inventory visibility, or the narrower of “compute” vs. “full account” if your provider separates those.
  • Use a separate credential for production versus non-production accounts or projects, rather than one broad key that reaches everything.
  • Revisit the credential’s scope if you later turn on features that need more - for example, attaching instances to an existing network needs the credential to also see that provider’s networking resources.

Where the exact permission names for your provider aren’t listed below, grant the narrowest role or policy that still permits the operations described - not a broader one “to be safe.”

What Sencai actually does, per provider family

Section titled “What Sencai actually does, per provider family”
ProviderRead-only discovery coversFull provisioning also needs
AWSA broad inventory sweep - EC2 instances, EBS volumes, VPCs/subnets/security groups/route tables, load balancers, RDS, EKS/ECS clusters, S3 buckets, Lambda functions, IAM users/roles/groups/policies (read-only, for the identity inventory), and several other resource kinds.Creating, starting, stopping, and terminating EC2 instances.
Google CloudCompute Engine instances, Cloud Storage buckets, Cloud SQL instances, BigQuery datasets, Cloud Functions, and IAM service accounts.Creating, starting, stopping, and deleting Compute Engine instances.
Microsoft AzureA general resource inventory across your subscription (Sencai reads via Azure’s own resource graph and falls back to listing resources directly).Creating, starting, stopping, and deleting virtual machines.
HetznerServers, volumes, networks, load balancers, and floating IPs.Creating, starting, stopping, and deleting servers.
DigitalOceanDroplets, volumes, VPCs, load balancers, and reserved IPs.Creating, starting, stopping, and deleting droplets. DigitalOcean isn’t offered on the Add Cloud Account screen yet; this row describes coverage that exists in the connector but has no credential form behind it.
ScalewayInstances, volumes, and load balancers.Creating and deleting instances. Start/stop through Sencai isn’t available for Scaleway today.
OVHcloudInstances, volumes, private networks, and load balancers.Creating and deleting instances. Start/stop through Sencai isn’t available for OVHcloud today.
UpCloudServers, storage, networks, and load balancers.Creating and deleting servers. Start/stop through Sencai isn’t available for UpCloud today.
Oracle Cloud (OCI)Compute instances only.Creating, starting, stopping, and deleting compute instances. Requires an existing subnet in your account - Sencai doesn’t create OCI networking for you (see Provision & manage instances).
VultrInstances only.Creating, starting, stopping, and deleting instances.
Linode (Akamai)Instances only.Creating, starting, stopping, and deleting instances.
IBM Cloud VPCInstances only.Creating, starting, stopping, and deleting instances. Requires an existing VPC, subnet, and image in your account, since none of those have a safe cross-account default. IBM Cloud isn’t offered on the Add Cloud Account screen yet; this row describes planned coverage.

Providers marked “instances only” above don’t yet have broader inventory coverage (networks, storage, and so on) the way AWS, Google Cloud, Azure, Hetzner, DigitalOcean, Scaleway, OVHcloud, and UpCloud do - a credential for one of them only needs compute access, because that’s all Sencai currently reads or manages there.

Connecting an AWS account is four steps: create an IAM user, attach a policy to it, create an access key for that user, and paste the key into Sencai. The policy you attach is the only thing that decides what Sencai can do, so choose it before you start clicking.

  1. Sign in to the AWS account you want to connect and open the IAM console.
  2. Go to Users and start creating a user. Give it a name that says what it is, such as sencai-inventory or sencai-provisioning.
  3. Leave console access turned off. This user never signs in to the AWS console; it only ever authenticates with an access key.
  4. On the permissions step, choose to attach a policy directly, create a new policy, and switch the policy editor to its JSON view. Replace whatever is in the editor with one of the two policies below, and name the policy something recognizable such as SencaiReadOnlyInventory.
  5. Finish creating the user.
  1. Open the user you just created and go to its Security credentials tab.
  2. Create an access key. AWS asks what the key will be used for - choose the option describing an application running outside AWS. AWS also shows a panel suggesting alternatives to long-lived keys; a third-party service that holds your credential is the case where an access key is the mechanism available.
  3. Copy both the Access key ID and the Secret access key before leaving the page. AWS displays the secret exactly once and cannot show it again.
  4. In Sencai, go to Settings → Cloud Credentials (/gravity/settings/cloud-credentials), click Add Cloud Account, choose AWS, and paste the two values plus a region. See Connect a cloud provider for the rest of the form.

This is the complete set of permissions Sencai uses to validate an AWS credential and to run an inventory scan, and nothing else. It is 1,087 characters excluding whitespace, which fits both the 2,048-character limit for a policy embedded directly in the user and the 6,144-character limit for a standalone customer-managed policy - either placement works.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SencaiValidateAndInventoryReadOnly",
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity",
"ec2:DescribeRegions",
"ec2:DescribeInstances",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVolumes",
"ec2:DescribeAddresses",
"ec2:DescribeInternetGateways",
"ec2:DescribeNatGateways",
"ec2:DescribeRouteTables",
"rds:DescribeDBInstances",
"lambda:ListFunctions",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"iam:ListUsers",
"iam:ListRoles",
"iam:ListPolicies",
"iam:ListGroups",
"route53:ListHostedZones",
"cloudfront:ListDistributions",
"eks:ListClusters",
"eks:DescribeCluster",
"ecs:ListClusters",
"ecs:DescribeClusters",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTags",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"elasticache:DescribeCacheClusters",
"elasticfilesystem:DescribeFileSystems",
"ecr:DescribeRepositories",
"sqs:ListQueues",
"sns:ListTopics",
"acm:ListCertificates",
"kms:ListKeys",
"kms:ListAliases",
"autoscaling:DescribeAutoScalingGroups",
"secretsmanager:ListSecrets"
],
"Resource": "*"
}
]
}

Why "Resource": "*" is not a loophole here. A discovery scan exists to find out which resources you have, so at the moment you write the policy there are no resource ARNs to name - that is genuinely circular, not laziness. On top of that, most of these actions do not support resource-level permissions in IAM at all: every ec2:Describe* call, sts:GetCallerIdentity, s3:ListAllMyBuckets, elasticloadbalancing:DescribeLoadBalancers and the List* calls accept only *, and AWS rejects or ignores an ARN there. The scoping in this policy is the action list itself - 39 named metadata reads, zero mutations, zero data-plane reads. If you want to narrow it further, add an aws:RequestedRegion condition rather than trying to name resources.

Worth reading before you hand it to anyone, and worth repeating to whoever owns the account:

  • It cannot read the contents of any S3 bucket. There is no s3:GetObject. It sees bucket names and their regions, nothing inside them.
  • It cannot read secret values. secretsmanager:ListSecrets returns names, ARNs and rotation settings; secretsmanager:GetSecretValue is absent, and Sencai never calls it anywhere.
  • It cannot read table rows. dynamodb:ListTables and DescribeTable give table names and shape; there is no Scan, Query, or GetItem.
  • It cannot read key material or key policies. Only kms:ListKeys and kms:ListAliases - no DescribeKey, no Decrypt.
  • It cannot read IAM policy documents. It lists users, roles, groups and customer-managed policies by name; it does not fetch what those policies say.
  • It cannot read messages or queue configuration. No sqs:ReceiveMessage, no sqs:GetQueueAttributes, no sns:GetTopicAttributes.
  • It cannot change anything at all. Every action is a read.

One consequence to plan for: adopting a discovered resource into your inventory writes a tag onto it at the provider, and this credential cannot. Adoption needs ec2:CreateTags, rds:AddTagsToResource, s3:GetBucketTagging and s3:PutBucketTagging, all of which live only in the full policy below. Scanning and viewing are unaffected - it is the tag-injection step alone that fails, and it fails loudly rather than silently.

A green Validate does not prove the scan will work. sts:GetCallerIdentity cannot be restricted by an IAM policy - AWS lets any valid credential call it. Validation therefore succeeds on a key with a completely empty policy: it proves the key is live and tells Sencai the account ID, and it separately checks ec2:DescribeRegions but only warns if that is denied. After connecting, judge the credential by the resource counts the first scan returns, not by the validation badge.

An under-privileged policy produces a short inventory, not an error. Each service is scanned independently and a denial on one of them is recorded and skipped, so the scan still reports success with fewer resources in it. If you trim the action list above, compare the resource counts against a run with the full list before concluding that the trimmed policy was enough.

Attaching the AWS-managed ReadOnlyAccess policy instead of the one above does work, and it keeps the guarantee that matters most: every action in it is a read, so the credential still cannot create, modify, or delete anything. It is also a superset of all 39 actions Sencai needs, so the scan completes.

What it grants beyond that is very large, and the difference is not a few extra Describe calls - it is the difference between reading metadata and reading data:

  • s3:Get* makes every object in every bucket readable. Sencai’s list has no s3:GetObject at all. On an account hosting live customer workloads this is the single biggest gap between the two policies.
  • dynamodb:GetItem, Query, and Scan expose actual table rows.
  • lambda:GetFunction returns a link to download a function’s deployment package - that is your source code.
  • cloudformation:GetTemplate returns full stack templates, which routinely embed connection strings and credentials.
  • Secrets Manager: ReadOnlyAccess grants the Describe and List calls, so the complete inventory of which secrets exist, their rotation settings and their tags becomes readable. AWS deliberately excludes GetSecretValue from it, so secret values stay out of reach either way.
  • Effectively every AWS service, including hundreds Sencai never calls. Sencai’s list touches 21 services.
  • It is a moving grant. AWS amends ReadOnlyAccess when new services ship, so the permissions attached to that credential grow over time without anyone approving the change. The explicit policy only changes when you edit it.

The convenience argument is thin: the read-only policy is 1,087 characters and pastes in as fast as picking a policy from a list. Use the explicit one on any account you care about.

Attach this only when you want Sencai to create and manage AWS resources for you. It is the read-only set, plus 17 further non-mutating reads that provisioning, backup, cost, and certificate features need, plus 53 write actions - 109 in total.

It is 3,425 characters excluding whitespace. That fits the 6,144-character limit for a customer-managed policy but exceeds the 2,048-character limit for a policy embedded directly in a user, so create it as its own customer-managed policy and attach it. Pasting it into the user’s inline policy editor fails with a size error; that is the limit you hit, not a syntax problem.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SencaiValidateAndInventoryReadOnly",
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity",
"ec2:DescribeRegions",
"ec2:DescribeInstances",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVolumes",
"ec2:DescribeAddresses",
"ec2:DescribeInternetGateways",
"ec2:DescribeNatGateways",
"ec2:DescribeRouteTables",
"rds:DescribeDBInstances",
"lambda:ListFunctions",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"iam:ListUsers",
"iam:ListRoles",
"iam:ListPolicies",
"iam:ListGroups",
"route53:ListHostedZones",
"cloudfront:ListDistributions",
"eks:ListClusters",
"eks:DescribeCluster",
"ecs:ListClusters",
"ecs:DescribeClusters",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTags",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"elasticache:DescribeCacheClusters",
"elasticfilesystem:DescribeFileSystems",
"ecr:DescribeRepositories",
"sqs:ListQueues",
"sns:ListTopics",
"acm:ListCertificates",
"kms:ListKeys",
"kms:ListAliases",
"autoscaling:DescribeAutoScalingGroups",
"secretsmanager:ListSecrets"
],
"Resource": "*"
},
{
"Sid": "SencaiOperationalReads",
"Effect": "Allow",
"Action": [
"ec2:DescribeImages",
"ec2:DescribeVpnGateways",
"ec2:DescribeVpnConnections",
"rds:DescribeDBSnapshots",
"rds:DescribeDBInstanceAutomatedBackups",
"s3:GetBucketTagging",
"ce:GetCostAndUsage",
"cloudwatch:GetMetricStatistics",
"route53:ListResourceRecordSets",
"cloudfront:GetDistribution",
"acm:DescribeCertificate",
"wafv2:ListWebACLs",
"wafv2:GetWebACL",
"shield:ListProtections",
"shield:ListAttacks",
"shield:DescribeSubscription",
"iam:GetAccountAuthorizationDetails"
],
"Resource": "*"
},
{
"Sid": "SencaiComputeStorageWrite",
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:TerminateInstances",
"ec2:ModifyInstanceAttribute",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:AttachVolume",
"rds:CreateDBInstance",
"rds:DeleteDBInstance",
"rds:AddTagsToResource",
"rds:RestoreDBInstanceFromDBSnapshot",
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:PutBucketVersioning",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketTagging"
],
"Resource": "*"
},
{
"Sid": "SencaiNetworkWrite",
"Effect": "Allow",
"Action": [
"ec2:CreateVpc",
"ec2:DeleteVpc",
"ec2:ModifyVpcAttribute",
"ec2:CreateSubnet",
"ec2:DeleteSubnet",
"ec2:ModifySubnetAttribute",
"ec2:CreateSecurityGroup",
"ec2:DeleteSecurityGroup",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AllocateAddress",
"ec2:ReleaseAddress",
"ec2:CreateNatGateway",
"ec2:DeleteNatGateway",
"ec2:CreateVpnGateway",
"ec2:AttachVpnGateway",
"ec2:DetachVpnGateway",
"ec2:DeleteVpnGateway",
"ec2:CreateCustomerGateway",
"ec2:CreateVpnConnection",
"ec2:DeleteVpnConnection",
"ec2:DetachInternetGateway",
"ec2:DeleteInternetGateway"
],
"Resource": "*"
},
{
"Sid": "SencaiEdgeDnsWafWrite",
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"cloudfront:CreateDistribution",
"cloudfront:UpdateDistribution",
"cloudfront:DeleteDistribution",
"cloudfront:CreateInvalidation",
"wafv2:CreateWebACL",
"wafv2:UpdateWebACL",
"wafv2:DeleteWebACL"
],
"Resource": "*"
},
{
"Sid": "SencaiBackupAndKeyRotation",
"Effect": "Allow",
"Action": [
"dlm:CreateLifecyclePolicy",
"dlm:DeleteLifecyclePolicy",
"iam:CreateAccessKey",
"iam:DeleteAccessKey"
],
"Resource": "*"
},
{
"Sid": "SencaiPassRoleToDlmOnly",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/AWSDataLifecycleManagerDefaultRole",
"Condition": {
"StringEquals": {
"iam:PassedToService": "dlm.amazonaws.com"
}
}
}
]
}

Four things in that policy deserve a decision rather than a paste, plus one that can fail whatever you grant:

  • iam:GetAccountAuthorizationDetails is a read, but it is the most revealing one in this policy: a single call returns every user, role, group and customer-managed policy in the account together with the policy documents and role trust policies themselves. It is what the cloud IAM scanner uses, and it is precisely the thing the read-only policy above deliberately withholds. If you want provisioning without handing over a full map of the account’s privilege graph, drop this one action - only the IAM scanner stops working.
  • iam:CreateAccessKey and iam:DeleteAccessKey exist for automatic key rotation, and at runtime Sencai only ever acts on the calling user. The IAM action with "Resource": "*", however, permits it against any user in the account, including administrators - that is a privilege-escalation primitive. On a production account either scope it to the one user ("Resource": "arn:aws:iam::<ACCOUNT_ID>:user/<sencai-user>") or drop both actions and rotate the key by hand.
  • iam:PassRole is needed only because snapshot lifecycle policies pass the AWSDataLifecycleManagerDefaultRole role to AWS Data Lifecycle Manager. That is why it is a separate statement with a specific ARN and a iam:PassedToService condition - never merge it into a "Resource": "*" block. The role must already exist in the account; Sencai does not create it.
  • Two actions can fail no matter what you grant. ce:GetCostAndUsage needs Cost Explorer to have been enabled on the account first (a one-time action that takes about a day to populate), and in an AWS Organization a member account only sees its own cost data if the management account allows it. The shield:* actions need an active Shield Advanced subscription. Both live only in the full policy, so neither can affect a read-only connection.

Making “read-only” survive a policy edit

Section titled “Making “read-only” survive a policy edit”

An identity policy can be replaced by anyone who can edit IAM. If you need the guarantee to hold beyond the current grant, attach a permissions boundary to the Sencai IAM user that explicitly denies ec2:Terminate*, ec2:Delete*, ec2:Stop*, rds:Delete*, s3:Delete*, cloudfront:Delete*, wafv2:Delete*, and route53:ChangeResourceRecordSets. Effective permissions are the intersection of the identity policy, the boundary, and any service control policy on the account, so the boundary keeps holding even if someone later swaps the policy for a broader one.

The same intersection explains a confusing failure: if the account sits in an organizational unit with a restrictive service control policy, you can attach the full policy exactly as written and still get access-denied errors. That is the organization’s policy winning, not a mistake in this one - and it surfaces as the silently short inventory described above.

You create exactly one thing: a Hetzner Cloud API token. There is no separate DNS credential, no sub-user, and no second token of any kind.

  1. Open the Hetzner Cloud console and switch to the project you want Sencai to see. Tokens are scoped to a single project, so a token made in one project cannot see any other - connect one credential per project.
  2. Open that project’s security settings and create an API token.
  3. Hetzner asks for a permission level: Read, or Read & Write. Pick Read for inventory only.
  4. Copy the token - Hetzner shows it once - and paste it into Sencai’s API Token field. That single field is the whole form; there is no region or location field for Hetzner.

With a Read token, validation works (it lists servers and locations), inventory scans work, and the Networks, Firewalls, and DNS screens display your real resources. Nothing can be created or destroyed - by Sencai, or by anyone who steals the token.

A Read token cannot create, resize, power on or off, or delete servers; cannot create or change networks, firewalls, or DNS zones and records; and cannot apply Sencai’s tags when you adopt a discovered resource into your inventory. Adopting with tagging is the only read-path feature that needs write access.

Same principle everywhere: connect for inventory first, widen later. Sencai’s validation and scan are read calls on every provider, so a read-scoped credential is enough to get an account connected and inventoried wherever the provider offers one.

ProviderWhat you createMinimum for inventory
Microsoft AzureAn application registration in your directory, a client secret on it, and a role assignment for its service principal on the target subscription. All three - an app registration with no role assignment authenticates fine and then fails every call.Reader on the subscription.
Google CloudA service account in the project, a role binding for it, and a JSON key. Paste the entire JSON file into the form.roles/viewer on the project. The key itself carries a broad scope - the role binding is what actually limits Sencai, so this is the one provider where getting the role right is the whole job.
ScalewayAn IAM API key, which gives you an access key and a secret key. The form also needs your organization ID and project ID.A read-only IAM policy covering instances, block storage, and load balancers.
OVHcloudAn application in OVH’s API credential system, which returns an application key, an application secret, and a consumer key together.Grant the consumer key GET only, on the cloud project, instance, region, volume, private network, and load balancer routes. This is the cleanest read-only story of any provider here, because the grant is per-route: with GET only, provisioning is impossible by construction.
Oracle Cloud (OCI)An API signing key pair for an IAM user: upload the public half to the user, then collect the tenancy OCID, user OCID, fingerprint, private key, and compartment OCID.An IAM policy allowing inspect or read on instance-family in the target compartment.
Linode (Akamai)A personal access token.Linodes: Read Only and Account: Read Only. Both are required - see the caution below.
UpCloudAn API user; the credential is a username and password, not a token.UpCloud offers no read-only token here - the credential is a full login. Restricting it is a matter of UpCloud’s own sub-account permissions.
VultrA personal access token from the Vultr console.Vultr API keys are account-wide with no read-only scope. A key that can list instances can also create and delete them.

Two further limits worth knowing before you promise an account will connect:

  • OVHcloud works against OVH’s European API endpoint only. US and Canadian OVH accounts cannot be connected through this form today.
  • Oracle Cloud credentials cover one compartment and one region each. A tenancy spanning several compartments or regions needs one credential per combination.

Three facts to weigh deliberately, none of which are specific to any one provider:

  • A read-only credential cannot provision. That is the point, but it means any attempt to create, start, stop, or delete an instance with it fails with a permission error from the provider. Widen the credential first, or connect a second, narrower one for the account you actually provision into.
  • Automatic scanning is unattended periodic reading. Turning it on makes Sencai re-read the account roughly every six hours, indefinitely, without anyone triggering it. It is off by default, manual scans work whether or not it is on, and on a customer’s production account it is worth being a decision rather than a default. See Connect a cloud provider.
  • Sencai does not enforce read-only. Nothing in the platform inspects a credential’s scope and refuses to use it for writes. If you hand over a full-access key, the provisioning screens will use it. The read-only guarantee comes entirely from the policy or token scope you set at the provider - which is exactly why this page starts there.

A credential’s secret value cannot be edited in place, and deleting the credential first would leave a gap where scans and provisioning fail. Rotate by overlapping instead, in this order:

  1. Create the replacement at the provider, alongside the existing one. AWS allows two access keys per IAM user, which is exactly what this is for; most token-based providers let you hold several tokens at once. Give the new one the same policy or scope as the old one.
  2. Add a new cloud account in Sencai with the new value, using the same descriptive name plus a marker so you can tell them apart.
  3. Validate the new credential and confirm it works - run a scan and check the resource counts match what the old one returned.
  4. Remove the old credential from Sencai. Removing it does not touch anything running at the provider; it only stops Sencai using that value.
  5. Delete or revoke the old key at the provider last, once nothing depends on it. On AWS you can deactivate the old access key before deleting it, which is reversible if you find something still using it.

Rotation reminders and their schedule live on Secrets → Rotation Policies (/gravity/secrets/rotation-policies) - they track when a rotation is due; the replacement itself is the sequence above.

If you use Sencai’s Live cloud features to manage real networks, firewalls, or DNS zones at the provider directly (see Networks, Firewalls, and DNS), the credential used for those actions needs the matching read/write access at the provider for whichever of those resource types you manage that way - separate from, and in addition to, plain instance provisioning access.

Checking what a credential can currently do

Section titled “Checking what a credential can currently do”

The credential’s row on Settings → Cloud Credentials (/gravity/settings/cloud-credentials) shows its validation status. Validate confirms the credential authenticates; it doesn’t itself report a full breakdown of every permission the provider granted it. If an operation later fails with a permission error, that’s the most reliable signal of what to widen - the error message from the provider is shown so you know exactly what to add.