Security

How Cloud GUI accesses your AWS account

The short version: you create a read-only role in your own account, we borrow it for fifteen minutes at a time, and you can delete it whenever you like. Editing is a second, optional role that you add only if you want it.

How connecting works

  1. You tell us your 12-digit AWS account number. We generate a random external ID that belongs to your connection alone.
  2. We send you to AWS CloudFormation with our template filled in. You review it there, in AWS, and click Create stack. It creates one IAM role.
  3. That role trusts Cloud GUI's AWS account (487275459989), and only for requests that carry your external ID and name the Cloud GUI user they're for. Another Cloud GUI customer can't point us at your account: they'd need your ID, and we never let anyone choose their own.
  4. When you open a page, we ask AWS STS for a session on that role. It lasts 15 minutes, lives only in our server's memory, and is never written to a database, file or log.

Exactly what the read-only role allows

Template v3. Every permission is a read. Nothing in it can create, change or delete anything in your account. It has four parts: a policy AWS wrote, a few reads that policy leaves out, a short list of reads it allows that we refuse, and log lines, which you can switch off.

See the names and settings of what's in your account

This is AWS's own view-only job-function policy, attached as it is. AWS wrote it for someone who should see what exists in an account without seeing inside it: it lists resources and reads their settings across about 120 services (S3, Lambda, EC2, RDS, DynamoDB, CloudFront, Route 53, IAM and many more). It's built to leave contents out (file bodies, database items, log lines, secret values), and the few reads in it that can expose secrets or details about people are denied outright. AWS maintains it and publishes every action in it.

arn:aws:iam::aws:policy/job-function/ViewOnlyAccess

Plus these reads, which ViewOnlyAccess leaves out and Cloud GUI's pages need (some for pages on the way: servers, websites and costs):

Find which region each S3 bucket is in

So a bucket's files are listed from the right place. Not what is inside the files: there is no s3:GetObject.

s3:GetBucketLocation
View one Lambda function's settings and web address

Runtime, memory, timeout and its function URL, one function at a time. Not your code: there is no lambda:GetFunction. AWS includes environment variable values in these responses; Cloud GUI keeps only the names and discards the values before anything is displayed.

lambda:GetFunctionConfigurationlambda:GetFunctionUrlConfig
View Aurora DSQL clusters

Cluster names, status and settings. Not the data in them: connecting to a cluster needs dsql:DbConnect, which this role doesn't have.

dsql:ListClustersdsql:GetCluster
View a CloudFront distribution's full settings

Its origins, domain names, caching rules and certificate, for the Websites page (ViewOnlyAccess only lists distributions). These settings can include custom headers sent to your origin, which sometimes hold secrets: Cloud GUI keeps only the header names and discards the values, as it does for Lambda environment variables.

cloudfront:GetDistribution
See when certificates expire

Each certificate's domain names, status, expiry date and renewal, so expiring ones can be flagged. Not the private key: there is no acm:ExportCertificate.

acm:DescribeCertificate
See which servers Systems Manager can reach

Whether each server's SSM agent is online, with its platform and agent version. Not your parameters or secrets (no ssm:GetParameter), and it can't run anything on a server.

ssm:DescribeInstanceInformation
Check whether S3 buckets are public

Whether a bucket's policy makes it public, and whether S3 Block Public Access is on for the bucket and for the account. Not the policy text, and not your files.

s3:GetBucketPolicyStatuss3:GetBucketPublicAccessBlocks3:GetAccountPublicAccessBlock
See what your account costs

Spend by service and by day from Cost Explorer, and AWS's forecast for the month. AWS charges about $0.01 for each Cost Explorer request, billed to your account; Cloud GUI caches cost figures to keep that to cents.

ce:GetCostAndUsagece:GetCostForecast

Logs (on unless you switch them off)

Log lines are content: whatever your code prints, which can include customer data. So reading them is a separate policy in the template, controlled by a setting called AllowLogs on the CloudFormation page. It's true unless you change it, so a function's page can show its recent lines and errors and you can search a log group. Set it to false and that policy isn't created: Cloud GUI can still see which log groups exist and how big they are, but not what's in them. You can change it later by updating the stack.

Read log lines (only while AllowLogs is true)

The lines your functions and services write to CloudWatch Logs, so a function's page can show its recent logs and errors and you can search a log group. They pass through Cloud GUI to reach your screen and are never stored, cached or logged. Set AllowLogs to false on the stack and this permission is removed.

logs:FilterLogEventslogs:GetLogEventslogs:StartQuerylogs:GetQueryResultslogs:StopQuery

What that lets us see

One policy that covers all of AWS means a single stack update gives every new Cloud GUI page what it needs. It also means the role can see more than today's pages show, and we'd rather tell you than have you find out:

Every call is in your CloudTrail either way.

What we refuse, even though AWS's policy allows it

Some reads in ViewOnlyAccess count as settings to AWS but can hold secrets or details about people. We went through every read it allows, checking AWS's own API definitions for responses that carry startup scripts, environment variables or job arguments, and added the ones that expose people. The template denies all of them in a statement of its own (except the three above), and an explicit deny beats any allow in IAM, so Cloud GUI can't read these even by mistake. No Cloud GUI page needs them, and our tests check that the code never calls them.

DeniedStartup scripts

EC2 user data, on instances, launch templates, Spot requests and Auto Scaling launch configurations, and SageMaker notebook and Studio lifecycle scripts. People put passwords and keys in these.

ec2:DescribeInstanceAttributeec2:DescribeLaunchTemplateVersionsec2:DescribeSpotInstanceRequestsec2:DescribeSpotFleetRequestsautoscaling:DescribeLaunchConfigurationssagemaker:DescribeNotebookInstanceLifecycleConfigsagemaker:DescribeStudioLifecycleConfig
DeniedEnvironment variables in containers, ML jobs and test runs

ECS task definitions and running tasks, SageMaker models, jobs and endpoints, Device Farm projects and runs, and Lambda MicroVM images. Environment variables often carry connection strings and keys.

ecs:DescribeTaskDefinitionecs:DescribeTasksecs:DescribeDaemonTaskDefinitionecs:DescribeExpressGatewayServicesagemaker:DescribeAIRecommendationJobsagemaker:DescribeAlgorithmsagemaker:DescribeAutoMLJobsagemaker:DescribeAutoMLJobV2sagemaker:DescribeDataQualityJobDefinitionsagemaker:DescribeHyperParameterTuningJobsagemaker:DescribeInferenceComponentsagemaker:DescribeModelsagemaker:DescribeModelBiasJobDefinitionsagemaker:DescribeModelExplainabilityJobDefinitionsagemaker:DescribeModelPackagesagemaker:DescribeModelQualityJobDefinitionsagemaker:DescribeMonitoringSchedulesagemaker:DescribeOptimizationJobsagemaker:DescribePartnerAppsagemaker:DescribeProcessingJobsagemaker:DescribeTrainingJobsagemaker:DescribeTransformJobsagemaker:ListCandidatesForAutoMLJobdevicefarm:ListProjectsdevicefarm:ListRunslambda:ListMicrovmImageVersions
DeniedJob arguments and stack outputs

Arguments passed to Glue jobs and sessions, and CloudFormation stack parameters, outputs and exports, which often carry connection strings and keys.

glue:GetJobRunsglue:ListSessionscloudformation:DescribeStackscloudformation:ListExports
DeniedDetails about people

Your app's users in Cognito (with attributes like email addresses), who is subscribed to your SNS topics, SNS phone-number lists, and SES contact and suppression lists.

cognito-idp:ListUserscognito-idp:ListUsersInGroupsns:ListSubscriptionssns:ListSubscriptionsByTopicsns:ListPhoneNumbersOptedOutsns:ListSMSSandboxPhoneNumbersses:ListContactsses:ListSuppressedDestinations
DeniedActivity and conversations

CloudTrail's event history (who did what in your account) and what people said to Lex chatbots.

cloudtrail:LookupEventslex:GetUtterancesView

What the read-only role can't do

Connected before version 3?

Your account keeps the version you deployed: a published template never changes. The console shows an Update banner that opens your stack's update page in AWS with the new template filled in. You review it there and submit, and nothing changes until you do. Until then, Cloud GUI can do exactly what your current version allows.

Editing (optional)

Off unless you turn it on, account by account. It's a second role, cloudgui-editor-…, in its own CloudFormation stack, and Cloud GUI uses it only for the action someone clicks. Viewing never uses it. When you set it up you choose which buckets and functions it covers, by the start of their names. Template v1 allows these four things and nothing else:

Download files

When you click Download, Cloud GUI signs a link that expires in five minutes and your browser fetches the file straight from S3. The file never passes through Cloud GUI.

s3:GetObject
Upload files

Your browser sends the file straight to S3 using a signed form. The file never passes through Cloud GUI, and an upload can replace a file with the same name.

s3:PutObject
Delete files

One file at a time, after you type its name to confirm.

s3:DeleteObject
Run functions

Runs a function once with the input you type, then shows its result and the last few lines of its log. That result passes through Cloud GUI to reach your screen; it is never stored or logged.

lambda:InvokeFunction

Delete the cloudgui-editor-… stack to turn editing off. Viewing keeps working. The template is at /cloudgui-editor.yaml.

It's all in your CloudTrail

Each session carries the email of the Cloud GUI user who opened it as its source identity, and the session name cloudgui-…. Your role enforces this: it refuses any session that doesn't name a user. In CloudTrail, filter by user name cloudgui-viewer-… or look for sourceIdentity to see what we did and who on your team did it. One caveat: listing the files inside a bucket is an S3 data event, which CloudTrail records only if you've turned on data events for S3. Everything else Cloud GUI calls, reading log lines included, is a management event, recorded by default.

What we store

Your email address, and for each connected account its number, the role name, your external ID and, if you turned on editing, the name prefixes you chose. That's all. We never store anything we read from your account. To keep pages quick, a list (of buckets, say) may be held in our server's memory for a few minutes and is then dropped. Log lines are never held at all: they pass through our server on their way to your screen, and aren't stored, cached or written to our own logs.

Things we want you to know

How to revoke access

In the AWS console, open CloudFormation → Stacks (region us-east-1), select the stack named cloudgui-viewer-… (and cloudgui-editor-…, if you turned on editing) and click Delete. Access ends as soon as the roles are gone. Disconnecting inside Cloud GUI forgets the account on our side, and the role stays until you delete the stack.

The template

This is the exact read-only file CloudFormation runs (the editing one is here), also at cloudgui-app.s3.us-east-2.amazonaws.com/templates/cloudgui-viewer-v3.yaml. Published versions are never changed. A new permission of ours means a new version that you choose to apply (ViewOnlyAccess is updated by AWS, as above).

# Cloud GUI read-only access, template v3.
#
# Creates ONE IAM role in your account that Cloud GUI (AWS account 487275459989)
# can use, for at most an hour at a time, to view what's in this account. Every
# permission is a read. Nothing here can create, change or delete anything.
#
# What the role can read:
# - AWS's own ViewOnlyAccess policy: the names and settings of resources across
#   AWS services (buckets, functions, servers, databases, IAM users and roles, and
#   so on), not what's inside them. AWS maintains it and lists every action:
#   https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html#jf_view-only-user
# - A few reads that policy leaves out, listed under cloudgui-viewer below.
# - Minus the reads it would allow that can hold secrets or details about people
#   (startup scripts, environment variables, job arguments, your app's users,
#   subscribers): explicitly denied in the NeverReadSecretsOrPeople statement.
# - Log lines from CloudWatch Logs, only while AllowLogs is 'true'. Log lines are
#   whatever your code prints, so they get their own switch: 'false' removes that
#   permission and everything else keeps working.
#
# How Cloud GUI uses it:
# - Cloud GUI never receives access keys. It asks AWS STS for a short-lived session on
#   this role, and only when the request carries your connection's ExternalId.
# - Each session is stamped with the Cloud GUI user's email (sts:SetSourceIdentity),
#   so every call shows up in your CloudTrail under the person who made it.
# - To revoke access, delete this stack. It takes effect immediately.
#
# Plain-English version: https://cloudgui.com/security

AWSTemplateFormatVersion: '2010-09-09'
Description: >-
  Cloud GUI read-only access (v3). One IAM role that Cloud GUI can use to view
  this account: AWS's ViewOnlyAccess policy, a few extra reads, and log lines while
  AllowLogs is true. Delete this stack to revoke access.

Parameters:
  ConnectionId:
    Type: String
    Description: Identifies this connection in Cloud GUI. Filled in for you.
    AllowedPattern: '[0-9a-f]{8}'
  ExternalId:
    Type: String
    Description: Cloud GUI must present this value to use the role. Filled in for you.
    AllowedPattern: '[0-9a-f]{32}'
  AllowLogs:
    Type: String
    Default: 'true'
    AllowedValues: ['true', 'false']
    Description: Let Cloud GUI show your CloudWatch log lines. They can contain anything your code prints. 'false' keeps them out, and everything else keeps working.

Conditions:
  LogsAllowed: !Equals [!Ref AllowLogs, 'true']

Resources:
  ViewerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'cloudgui-viewer-${ConnectionId}'
      Description: Read-only access for Cloud GUI (cloudgui.com). Delete the CloudFormation stack to revoke.
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          # Cloud GUI may use this role only with your ExternalId, and only when the
          # session names the Cloud GUI user it's for (so CloudTrail always can).
          - Sid: CloudGUIWithExternalId
            Effect: Allow
            Principal:
              AWS: 'arn:aws:iam::487275459989:root'
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref ExternalId
              'Null':
                sts:SourceIdentity: 'false'
          # Lets that session carry the user's email as its source identity. On its
          # own it grants nothing: it only applies within an AssumeRole allowed above.
          - Sid: NameTheUser
            Effect: Allow
            Principal:
              AWS: 'arn:aws:iam::487275459989:root'
            Action: sts:SetSourceIdentity
      # AWS's view-only job-function policy: names and settings, not contents.
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/job-function/ViewOnlyAccess
      Policies:
        - PolicyName: cloudgui-viewer
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              # Find which region each S3 bucket is in
              - Sid: FindBucketRegions
                Effect: Allow
                Action:
                  - s3:GetBucketLocation
                Resource: '*'
              # View one Lambda function's settings and web address
              - Sid: ViewFunctionSettings
                Effect: Allow
                Action:
                  - lambda:GetFunctionConfiguration
                  - lambda:GetFunctionUrlConfig
                Resource: '*'
              # View Aurora DSQL clusters
              - Sid: ViewDsqlClusters
                Effect: Allow
                Action:
                  - dsql:ListClusters
                  - dsql:GetCluster
                Resource: '*'
              # View a CloudFront distribution's full settings
              - Sid: ViewWebsites
                Effect: Allow
                Action:
                  - cloudfront:GetDistribution
                Resource: '*'
              # See when certificates expire
              - Sid: ViewCertificates
                Effect: Allow
                Action:
                  - acm:DescribeCertificate
                Resource: '*'
              # See which servers Systems Manager can reach
              - Sid: ViewServerAgents
                Effect: Allow
                Action:
                  - ssm:DescribeInstanceInformation
                Resource: '*'
              # Check whether S3 buckets are public
              - Sid: CheckPublicBuckets
                Effect: Allow
                Action:
                  - s3:GetBucketPolicyStatus
                  - s3:GetBucketPublicAccessBlock
                  - s3:GetAccountPublicAccessBlock
                Resource: '*'
              # See what your account costs
              - Sid: ReadCosts
                Effect: Allow
                Action:
                  - ce:GetCostAndUsage
                  - ce:GetCostForecast
                Resource: '*'
              # NEVER, even though ViewOnlyAccess allows them: settings and lists that can
              # hold secrets or details about people. An explicit Deny overrides any Allow.
              - Sid: NeverReadSecretsOrPeople
                Effect: Deny
                Action:
                  # Startup scripts
                  - ec2:DescribeInstanceAttribute
                  - ec2:DescribeLaunchTemplateVersions
                  - ec2:DescribeSpotInstanceRequests
                  - ec2:DescribeSpotFleetRequests
                  - autoscaling:DescribeLaunchConfigurations
                  - sagemaker:DescribeNotebookInstanceLifecycleConfig
                  - sagemaker:DescribeStudioLifecycleConfig
                  # Environment variables in containers, ML jobs and test runs
                  - ecs:DescribeTaskDefinition
                  - ecs:DescribeTasks
                  - ecs:DescribeDaemonTaskDefinition
                  - ecs:DescribeExpressGatewayService
                  - sagemaker:DescribeAIRecommendationJob
                  - sagemaker:DescribeAlgorithm
                  - sagemaker:DescribeAutoMLJob
                  - sagemaker:DescribeAutoMLJobV2
                  - sagemaker:DescribeDataQualityJobDefinition
                  - sagemaker:DescribeHyperParameterTuningJob
                  - sagemaker:DescribeInferenceComponent
                  - sagemaker:DescribeModel
                  - sagemaker:DescribeModelBiasJobDefinition
                  - sagemaker:DescribeModelExplainabilityJobDefinition
                  - sagemaker:DescribeModelPackage
                  - sagemaker:DescribeModelQualityJobDefinition
                  - sagemaker:DescribeMonitoringSchedule
                  - sagemaker:DescribeOptimizationJob
                  - sagemaker:DescribePartnerApp
                  - sagemaker:DescribeProcessingJob
                  - sagemaker:DescribeTrainingJob
                  - sagemaker:DescribeTransformJob
                  - sagemaker:ListCandidatesForAutoMLJob
                  - devicefarm:ListProjects
                  - devicefarm:ListRuns
                  - lambda:ListMicrovmImageVersions
                  # Job arguments and stack outputs
                  - glue:GetJobRuns
                  - glue:ListSessions
                  - cloudformation:DescribeStacks
                  - cloudformation:ListExports
                  # Details about people
                  - cognito-idp:ListUsers
                  - cognito-idp:ListUsersInGroup
                  - sns:ListSubscriptions
                  - sns:ListSubscriptionsByTopic
                  - sns:ListPhoneNumbersOptedOut
                  - sns:ListSMSSandboxPhoneNumbers
                  - ses:ListContacts
                  - ses:ListSuppressedDestinations
                  # Activity and conversations
                  - cloudtrail:LookupEvents
                  - lex:GetUtterancesView
                Resource: '*'
      Tags:
        - Key: cloudgui:connection
          Value: !Ref ConnectionId
        - Key: cloudgui:template-version
          Value: '3'

  # Reading log lines. This policy exists only while AllowLogs is 'true'.
  LogsPolicy:
    Type: AWS::IAM::RolePolicy
    Condition: LogsAllowed
    Properties:
      RoleName: !Ref ViewerRole
      PolicyName: cloudgui-viewer-logs
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          # Read log lines (only while AllowLogs is true)
          - Sid: ReadLogEvents
            Effect: Allow
            Action:
              - logs:FilterLogEvents
              - logs:GetLogEvents
              - logs:StartQuery
              - logs:GetQueryResults
              - logs:StopQuery
            Resource: '*'

Outputs:
  RoleArn:
    Description: The role Cloud GUI uses. Cloud GUI finds it on its own; nothing to copy.
    Value: !GetAtt ViewerRole.Arn