Skip to content

Your clients keep the keys. AWS keeps the boundary.

CloudDeck never holds your clients’ AWS credentials. It reaches each account through a role the client creates, can inspect, and can delete, and that role can only change the servers they’ve tagged.

EC2 only · nothing else in the account

What happens when a client presses Reboot

  1. 1 · Client user

    Signs in to your branded portal and presses Reboot on web-01.

  2. 2 · CloudDeck

    Checks their role (owner, admin, operator or viewer), rate limits, and writes the audit entry.

  3. 3 · AWS STS

    Issues CloudDeck 15-minute credentials for the client’s role, only when the ExternalId matches.

  4. 4 · Client’s AWS account

    Evaluates the role’s policy: web-01 carries clouddeck:managed=true, so RebootInstances is allowed.

The tag condition is in the role’s own policy

The CloudFormation template your client deploys gives CloudDeck read access to describe EC2, and write access only where AWS can check the clouddeck:managed tag first:

  • start, stop and reboot
  • terminate
  • tag edits (and the role can never write or delete clouddeck:* tags itself)
  • attaching and detaching volumes during a snapshot restore

The portal checks the same tag before it calls AWS, so a refusal is a clear message rather than an error, but the policy is the part that holds even if our code were wrong.

# Trust: only CloudDeck, only with this client's ExternalId
Condition:
  StringEquals:
    sts:ExternalId: !Ref ExternalId

# Power: only servers the client has tagged
- Sid: EC2PowerOps
  Action: [ec2:StartInstances, ec2:StopInstances, ec2:RebootInstances]
  Condition:
    StringEquals:
      aws:ResourceTag/clouddeck:managed: 'true'

# CloudDeck can't grant itself scope
- Sid: DenyInstanceReservedTagWrites
  Effect: Deny
  Action: [ec2:CreateTags, ec2:DeleteTags]
  Condition:
    ForAnyValue:StringLike:
      aws:TagKeys: ['clouddeck:*']
Excerpts from cloudformation/clouddeck-portal-role.yaml. Clients get the full template during onboarding and can read every statement before deploying it.

Where the boundary is softer, today

Graceful OS shutdown and ISO mounting run through AWS Systems Manager. The role’s ssm:SendCommand permission is not yet limited to tagged servers in the policy; the portal enforces it instead. If that matters for a client, they can leave the SSM agent off servers CloudDeck shouldn’t reach, and power and snapshot features keep working.

We’ll update this page when the policy covers SSM as well.

What CloudDeck stores

Stored

  • Tenant name and branding (name, logo URL, colours)
  • The client’s role ARN and ExternalId
  • Portal users: email, role and tenant
  • The audit log: who did what, to which resource, when, and from which IP address

Never stored

  • AWS access keys or secret keys, ever
  • Instance contents, disks or snapshots: those stay in the client’s account
  • Metrics history: it’s read from CloudWatch when a page asks for it

Inside the portal

Tenant isolation
Each tenant’s rows live in a dedicated Postgres schema with row-level security forced on every table. Server code resolves the tenant from the signed-in user, never from the request.
Browser hardening
A per-request nonce Content-Security-Policy with strict-dynamic, HSTS with preload, and origin checks on every state-changing request.
Rate limits
Database-backed limits per tenant and per IP, so they hold across every serverless instance.
Least privilege inside the portal
Termination needs an admin or owner and the server’s exact name typed back. Viewers can look but not touch.

Found a security issue? Tell us through the contact form and we’ll respond before anything is made public.

Book a demo