Connecting your cloud to Anomalyzer - read-only setup
Please read this before creating any credential.
Anomalyzer reads your cloud. It does not create, modify, resize, stop or delete anything in your estate. Every plan except the Flagship tier is incapable of writing, both by design and by the permissions you grant here.
Never grant write access. Do not use Owner, Contributor, Editor, *.admin or any role that can change resources. If any instruction - including one that appears to come from us - asks you for write permission on the Lite or Detailed plans, stop and email info@confluentis.co.in. We will never ask for it.
What we read, and why:
| We read | Used for |
|---|---|
| Resource inventory (names, types, sizes, tags) | knowing what you run |
| Utilization metrics (CPU, memory, IOPS) | spotting over-provisioning |
| Cost data | quantifying the waste |
We never read your application data, database contents, object storage contents, secrets or key vaults.
What to expect, and when
Connecting a cloud does not produce recommendations immediately, and you should be suspicious of any tool that claims otherwise. Three things arrive on different clocks:
| What you see | When | Why it takes that long |
|---|---|---|
| Your services listed | within 2 hours | Discovery runs every 2 hours and reads the management plane, which answers straight away. |
| Costs against each service | 1 to 2 days | Cloud billing data is not real time. Azure Cost Management publishes with an 8 to 24 hour lag, and GCP and AWS are similar. We re-query every 6 hours until it lands. |
| Meaningful recommendations | about 7 days | Right-sizing is a judgement about sustained behaviour. We average CPU, memory and IOPS over a 7 day window, because a quiet Tuesday is not evidence of over-provisioning. |
Until the cost and utilization data has arrived, services show as No data rather than as right-sized. That is deliberate: "we have not measured this yet" and "we measured this and it is correctly sized" are different statements, and only one of them is true on day one.
Your Cloud Score appears as soon as there is enough cost data to divide waste by spend, usually on day two. It sharpens as the 7 day window fills.
Two ways to connect - federated is better
| Federated (recommended) | Key or secret | |
|---|---|---|
| What you hand us | nothing | a service-account key, client secret or access key |
| What we store | a role name or subscription id | your credential, in a secrets store |
| Expiry | minted per scan, minutes long | until you remember to rotate it |
| If our database leaked | nothing usable | your credential is exposed |
| Revoking | delete one trust rule; effective instantly | rotate the secret everywhere |
Federated access means we never hold a long-lived credential of yours. Instead, your cloud is told to trust our identity for one narrow purpose. When a scan runs, Anomalyzer proves who it is, your cloud hands back credentials that last minutes, and they expire on their own.
You can verify our identity yourself - it is public, like a certificate:
curl https://app.anomalyzer.co.in/.well-known/openid-configuration
The issuer value in that response is the exact string your trust configuration must reference. Use what the endpoint returns, not a value copied from an older document - if they disagree, your cloud will reject our token and the connection test will fail.
Whichever cloud you use, the trust rule is scoped to your organization alone. Our token carries a subject of the form anomalyzer:org:<your-org>:<cloud> and your trust configuration matches it exactly, so a token minted for one customer cannot be used against another.
In the app: open Cloud connections → Connect a cloud, pick your cloud, and leave Connection method on Federated - no secret. The screen shows these commands with your own ids already filled in, and a Copy button. Run them, paste back the one value asked for, then use Test connection - it must pass before Connect read-only will save.
Microsoft Azure
Least work of the three. No app registration, no client secret, nothing to create except role assignments. You paste back two ids you already know.
Before you start
- The Azure CLI. Check with
az version; install from <https://learn.microsoft.com/cli/azure/install-azure-cli> if missing. - Sign in with
az login. - You need Owner or User Access Administrator on the subscription. If you do not have it, send this section to whoever does.
- Decide the scope. Assigning at subscription level lets us see the whole estate. Assigning to a single resource group means we only ever see that group, and your Services list will show only what is inside it.
Federated - recommended
--assignee is the object id of Anomalyzer's service principal. It is the same for every customer and is not a secret.
Linux and macOS (bash, zsh)
SUB="<your-subscription-id>"
ANOMALYZER_SP="5daae419-c4a9-403f-838d-0825e543b8ce"
# Spend - lets us quantify the waste
az role assignment create \
--assignee "$ANOMALYZER_SP" \
--role "Cost Management Reader" \
--scope "/subscriptions/$SUB"
# Inventory and utilization - lets us name the services behind the number.
# Without this you get a total with nothing attributed to it.
az role assignment create \
--assignee "$ANOMALYZER_SP" \
--role "Reader" \
--scope "/subscriptions/$SUB"
Windows (PowerShell)
$Sub = "<your-subscription-id>"
$AnomalyzerSp = "5daae419-c4a9-403f-838d-0825e543b8ce"
# Spend - lets us quantify the waste
az role assignment create `
--assignee $AnomalyzerSp `
--role "Cost Management Reader" `
--scope "/subscriptions/$Sub"
# Inventory and utilization - lets us name the services behind the number.
# Without this you get a total with nothing attributed to it.
az role assignment create `
--assignee $AnomalyzerSp `
--role "Reader" `
--scope "/subscriptions/$Sub"
Then paste your tenant id and subscription id into Anomalyzer. Find them with az account show --query "{tenant:tenantId, subscription:id}".
Key-based - if federation is not an option
Create a service principal with Reader only, then add the two data roles:
Linux and macOS
SUB="<your-subscription-id>"
az ad sp create-for-rbac \
--name "anomalyzer-reader" \
--role "Reader" \
--scopes "/subscriptions/$SUB"
# Cost Management Reader -> spend; Monitoring Reader -> utilization
APP_ID="<appId from the output above>"
az role assignment create --assignee "$APP_ID" \
--role "Cost Management Reader" --scope "/subscriptions/$SUB"
az role assignment create --assignee "$APP_ID" \
--role "Monitoring Reader" --scope "/subscriptions/$SUB"
Windows (PowerShell)
$Sub = "<your-subscription-id>"
az ad sp create-for-rbac `
--name "anomalyzer-reader" `
--role "Reader" `
--scopes "/subscriptions/$Sub"
$AppId = "<appId from the output above>"
az role assignment create --assignee $AppId `
--role "Cost Management Reader" --scope "/subscriptions/$Sub"
az role assignment create --assignee $AppId `
--role "Monitoring Reader" --scope "/subscriptions/$Sub"
create-for-rbac prints appId, password and tenant once. Copy them straight into Anomalyzer; the password cannot be retrieved later, only reset.
Removing Azure access
az role assignment delete --assignee "5daae419-c4a9-403f-838d-0825e543b8ce" \
--scope "/subscriptions/<your-subscription-id>"
For the key-based route, az ad sp delete --id <APP_ID> removes it entirely.
Google Cloud
Before you start
- The gcloud CLI. Check with
gcloud version; install from <https://cloud.google.com/sdk/docs/install> if missing. - Sign in with
gcloud auth loginand select the project withgcloud config set project <your-project-id>. - You need Project IAM Admin (
roles/resourcemanager.projectIamAdmin). - Billing roles live on the billing account, not the project, and usually need Billing Account Administrator. That is often a different person - worth checking before you start.
Federated - recommended
This is Google's direct workload identity federation: no service account is created, so there is no key to hold, rotate or leak.
Linux and macOS (bash, zsh)
PROJECT_ID="<your-project-id>"
PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')"
ORG_SLUG="<your-anomalyzer-org-slug>" # shown on the Connect a cloud screen
POOL="anomalyzer-pool-$ORG_SLUG"
gcloud iam workload-identity-pools create "$POOL" \
--project="$PROJECT_ID" --location="global" --display-name="Anomalyzer reader"
gcloud iam workload-identity-pools providers create-oidc anomalyzer-oidc \
--project="$PROJECT_ID" --location="global" --workload-identity-pool="$POOL" \
--issuer-uri="https://app.anomalyzer.co.in" \
--attribute-mapping="google.subject=assertion.sub" \
--allowed-audiences="//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL/providers/anomalyzer-oidc"
MEMBER="principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL/*"
# Inventory and utilization
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--role="roles/viewer" --condition=None --member="$MEMBER"
# Spend. On the BILLING ACCOUNT, not the project.
BILLING_ACCOUNT="$(gcloud billing projects describe "$PROJECT_ID" \
--format='value(billingAccountName)')"
gcloud billing accounts add-iam-policy-binding "${BILLING_ACCOUNT#billingAccounts/}" \
--role="roles/billing.viewer" --member="$MEMBER"
Windows (PowerShell)
Note the last step: ${VAR#prefix} is a bash-only expansion that does nothing in PowerShell, so the billing id would keep its billingAccounts/ prefix and the binding would be rejected. Use -replace instead.
$ProjectId = "<your-project-id>"
$ProjectNumber = (gcloud projects describe $ProjectId --format="value(projectNumber)")
$OrgSlug = "<your-anomalyzer-org-slug>"
$Pool = "anomalyzer-pool-$OrgSlug"
gcloud iam workload-identity-pools create $Pool `
--project=$ProjectId --location="global" --display-name="Anomalyzer reader"
gcloud iam workload-identity-pools providers create-oidc anomalyzer-oidc `
--project=$ProjectId --location="global" --workload-identity-pool=$Pool `
--issuer-uri="https://app.anomalyzer.co.in" `
--attribute-mapping="google.subject=assertion.sub" `
--allowed-audiences="//iam.googleapis.com/projects/$ProjectNumber/locations/global/workloadIdentityPools/$Pool/providers/anomalyzer-oidc"
$Member = "principalSet://iam.googleapis.com/projects/$ProjectNumber/locations/global/workloadIdentityPools/$Pool/*"
gcloud projects add-iam-policy-binding $ProjectId `
--role="roles/viewer" --condition=None --member=$Member
$Billing = (gcloud billing projects describe $ProjectId `
--format="value(billingAccountName)") -replace '^billingAccounts/', ''
gcloud billing accounts add-iam-policy-binding $Billing `
--role="roles/billing.viewer" --member=$Member
Then paste your project id and project number into Anomalyzer.
Key-based - if federation is not an option
This route creates a JSON key file. Treat it like a password: anyone holding it has the access you granted, until you delete the key.
Linux and macOS
PROJECT_ID="<your-project-id>"
SA="anomalyzer-reader"
gcloud iam service-accounts create "$SA" \
--project="$PROJECT_ID" --display-name="Anomalyzer read-only"
EMAIL="$SA@$PROJECT_ID.iam.gserviceaccount.com"
# viewer -> see resources
# monitoring.viewer -> utilization metrics
# cloudasset.viewer -> fast inventory
for ROLE in roles/viewer roles/monitoring.viewer roles/cloudasset.viewer; do
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:$EMAIL" --role="$ROLE" --condition=None
done
# Downloads the key to a file in the CURRENT directory.
gcloud iam service-accounts keys create anomalyzer-key.json \
--iam-account="$EMAIL"
Windows (PowerShell)
$ProjectId = "<your-project-id>"
$Sa = "anomalyzer-reader"
gcloud iam service-accounts create $Sa `
--project=$ProjectId --display-name="Anomalyzer read-only"
$Email = "$Sa@$ProjectId.iam.gserviceaccount.com"
foreach ($Role in @("roles/viewer","roles/monitoring.viewer","roles/cloudasset.viewer")) {
gcloud projects add-iam-policy-binding $ProjectId `
--member="serviceAccount:$Email" --role=$Role --condition=None
}
gcloud iam service-accounts keys create anomalyzer-key.json `
--iam-account=$Email
What to do with anomalyzer-key.json:
- It is written to the folder you ran the command in. Find it with
ls anomalyzer-key.json(ordir anomalyzer-key.jsonon Windows). - Open it in a text editor - Notepad, VS Code,
cat, anything. It is a single JSON object starting{ "type": "service_account", .... - Select all of it and copy - the whole file including the outer braces, not just the
private_keyline. - Paste it into the Service account key (JSON) box in Anomalyzer and save.
- Delete the file from your machine. Anomalyzer stores it encrypted; a copy sitting in your Downloads folder is the part that gets leaked.
If you prefer the console: IAM & Admin → Service Accounts → your account → Keys → Add key → Create new key → JSON. The browser downloads it; the same five steps apply.
Removing Google Cloud access
# Federated
gcloud iam workload-identity-pools delete "$POOL" --location=global --project="$PROJECT_ID"
# Key-based
gcloud iam service-accounts delete "$EMAIL" --project="$PROJECT_ID"
Amazon Web Services
Before you start
- Permission to create IAM roles, or someone who has it.
- For the federated route, the ability to run a CloudFormation stack.
Federated - recommended
We never receive an access key or a secret key. Your account grants a narrow, read-only trust directly to our identity using AWS's own AssumeRoleWithWebIdentity. You can revoke it from your console at any time, with nothing to coordinate with us.
What gets created, all inside your own account:
- An IAM OIDC identity provider trusting
https://app.anomalyzer.co.in. - An IAM role whose trust policy accepts only tokens carrying your organization's exact subject (
anomalyzer:org:<your-slug>:aws). The OIDC provider is shared infrastructure across our customers; the role condition is what makes it yours, so no other customer's token can assume it. - A read-only managed policy attached to that role. It is a specific list of
Describe,List,Getand Cost Explorer actions, not the AWS-managedReadOnlyAccesspolicy. Nothing in it can create, modify or delete.
One CloudFormation template creates all three together.
Step 1 - download the template. On Cloud connections → Connect a cloud → AWS, set the method to Federated - no secret (recommended), then click Download CloudFormation template. Always download fresh rather than reusing an older file; the permission list changes as we add support for more services.
Step 2 - create the stack. Read this step carefully. In the AWS console go to CloudFormation → Stacks, click the small arrow next to Create stack (not the button itself) and choose "With new resources (standard)".
The other option, "With existing resources (import resources)", is a different operation entirely: it attaches resources that already exist. If the wizard starts asking you for "identifier values" for a role that does not exist yet, you are in the wrong one. Cancel and start again.
Then:
- Prepare template: leave "Choose an existing template".
- Specify template: choose Upload a template file and select the file you downloaded.
- Stack details: any stack name (for example
anomalyzer-reader). Leave Audience at its defaultsts.amazonaws.com. For OrgSlug, enter the exact value shown in the Anomalyzer drawer. - Accept the defaults on "Configure stack options".
- On Review, tick the acknowledgement that IAM resources will be created.
- Submit, and wait for
CREATE_COMPLETE.
Step 3 - connect. Open the stack's Outputs tab, copy the value labelled RoleArn, paste it into the Anomalyzer drawer and press Test connection. That makes one live read-only call (sts:GetCallerIdentity after assuming the role) before anything is saved. Then press Connect read-only.
Cost data needs one more thing, in AWS
Inventory works as soon as the role exists. Cost does not, and the reason is not obvious: we read per-resource cost through Cost Explorer's GetCostAndUsageWithResources, and that call needs resource-level data enabled in the payer account. That is a preference in your own billing console, unrelated to the CloudFormation stack. Without it the call succeeds and returns nothing, so the estate shows every service correctly with $0 against all of them.
It is two separate toggles, and this is the part that catches people out. Turning on the first without the second leaves EC2 and EBS at zero no matter how long you wait.
- Go to Billing and Cost Management → Cost Management Preferences. Depending on console version this may appear as Cost Explorer → Preferences; searching the console bar for "Cost Management Preferences" gets you there directly.
- Open the Cost Explorer tab on that page.
- Under Granular data → Daily granularity, confirm Resource-level data at daily granularity is ticked, with All services selected. This covers most services, including S3.
- Under Hourly granularity, separately tick EC2-Instances (Elastic Compute Cloud - Compute) resource-level data. This is a genuinely different toggle from the one above. EC2, and EBS which bills under the same service category, will not report resource-level cost without it.
- Save preferences.
This takes effect going forward, not retroactively, and can take up to a day to appear. AWS charges a small fee for hourly resource-level records, in the region of $0.01 per 1,000 records, which for a typical account is a fraction of a dollar a month.
If you cannot find the page at all, or get an access-denied message on it despite holding admin IAM permissions, a separate root-account setting called IAM User and Role Access to Billing Information may be switched off. Only the account's actual root user can turn that on.
When we widen the permissions
Adding support for a new AWS service means new read actions on the policy. Your role and its ARN do not change, so nothing is re-entered on our side:
- Download the template again from the drawer.
- CloudFormation → Stacks → your stack → Update stack (not Create).
- Replace current template → Upload a template file, select the new file.
- Keep the same OrgSlug. Continue to
UPDATE_COMPLETE.
If the update reports "The submitted information didn't contain changes", your browser served a cached copy of the older template. Download it again in a private window and check the file actually differs before re-uploading.
How we find your resources
Discovery works in three layers, each catching what the one before it cannot:
- Purpose-built collectors for EC2, EBS, RDS, S3, EKS, App Runner and SageMaker. These carry the most detail: instance type, engine version, storage class and so on.
- A tagging sweep across every other service, using the AWS Resource Groups Tagging API. It needs no setup from you, but it can only see resources carrying at least one tag.
- An optional AWS Config sweep, described next.
The first two run automatically and cover the large majority of what most accounts hold.
Optional: AWS Config, for untagged resources
The tagging sweep cannot see an untagged resource. If your account has meaningful untagged infrastructure, which is common for older accounts or anything created before a tagging policy existed, enabling AWS Config closes that gap: Config records every resource type it supports regardless of tags.
This is your decision, and it is off by default. We never enable it on your behalf. Config is a separate AWS service with its own billing, in the region of $0.003 per configuration item plus a small monthly recorder fee. For a typical account that is a few dollars a month or less, but it is a real, ongoing AWS charge for as long as it stays on, independent of anything we charge.
If you want it: Config → set recording strategy to All resource types with customizable overrides, choose Continuous recording, leave AWS's default exclusion of IAM resource types in place (we do not need Config to track IAM), create the Config service-linked role, let it create a bucket for delivery, leave the SNS topic blank, and skip Config Rules. Repeat per region if your estate spans several.
Allow up to an hour for the first inventory of pre-existing resources. Config's initial baseline is slower than its ongoing recording, so an empty Resource Inventory shortly after enabling is normal rather than a fault.
To turn it off later: Config → Settings → Turn off recording. Deleting our CloudFormation stack does not affect Config, because Config is yours and separately managed.
What the role can actually do
Every action in the policy is read-only. The whole list, by verb:
| Verb | Count | What it means |
|---|---|---|
Get* | 8 | Retrieve one existing value: cost data, bucket location, function or endpoint configuration. |
List* | 9 | Enumerate existing things: functions, clusters, endpoints, tags, buckets. |
Describe* | 12 | Retrieve detail about existing things: instances, volumes, snapshots, DB instances, clusters, nodegroups, services, endpoints, Config recorder status. |
Select* | 1 | A SQL-style query against data AWS Config has already recorded. Read-only by definition, the same category as a database SELECT. |
There are zero occurrences of Create, Put, Delete, Update, Modify, Attach, Detach, Start, Stop, Terminate, Reboot, Authorize, or any other state-changing verb anywhere in it.
Two things worth understanding if you are reviewing this properly. First, the policy's Resource: "*" scope does not escalate anything: it controls which resources these already-read-only actions apply to, not what may be done to them. A read-only action list applied broadly is still read-only. Second, the sts:AssumeRoleWithWebIdentity statement in the trust policy is a different kind of statement altogether. It governs who may assume the role at all, scoped to tokens minted for your organization only, rather than granting any permission over your resources. Once assumed, everything that identity can do is bounded by the read-only list above.
The full policy is visible in the CloudFormation template before you ever run it, and we would rather you read it there than take this summary as the last word.
Key-based - if federation is not an option
Linux and macOS
aws iam create-user --user-name anomalyzer-reader
aws iam attach-user-policy --user-name anomalyzer-reader \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# Cost Explorer is a separate grant from ReadOnlyAccess
aws iam attach-user-policy --user-name anomalyzer-reader \
--policy-arn arn:aws:iam::aws:policy/job-function/Billing
aws iam create-access-key --user-name anomalyzer-reader
Windows (PowerShell)
aws iam create-user --user-name anomalyzer-reader
aws iam attach-user-policy --user-name anomalyzer-reader `
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam attach-user-policy --user-name anomalyzer-reader `
--policy-arn arn:aws:iam::aws:policy/job-function/Billing
aws iam create-access-key --user-name anomalyzer-reader
create-access-key prints AccessKeyId and SecretAccessKey once. Copy both into Anomalyzer immediately; the secret cannot be retrieved again.
Removing AWS access
Delete the CloudFormation stack. That removes the role, the OIDC provider and the managed policy together, in one action: CloudFormation → Stacks → your stack → Delete. Nothing is needed on our side; the connection simply stops working the next time it is attempted.
For the key-based route, detach its policies and then aws iam delete-user --user-name anomalyzer-reader.
If AWS does not look right
| What you see | Why | What to do |
|---|---|---|
| The wizard asks for "identifier values" for a role that does not exist | You picked "With existing resources (import resources)" | Cancel, and start again with With new resources (standard) |
| Test connection fails right after the stack completes | AWS can take a moment to propagate a new IAM role | Wait a minute or two and retry, changing nothing |
| Test connection fails mentioning the token issuer | Not something to diagnose from the AWS side | Send us the exact error at info@confluentis.co.in |
Services appear, but every one shows $0 | Cost Explorer resource-level data is not enabled, or the role predates our current policy | Enable resource-level data in the payer account, then update the stack as above |
| An update says "didn't contain changes" | Your browser served a cached copy of the template | Download again in a private window and confirm the file differs |
| Connected, but no services at all | Usually a filter on the Services page, most often Environment | Clear the filters first; if it persists, update the stack |
A few more symptoms worth knowing, with what each one actually means:
| Symptom | Cause | Fix |
|---|---|---|
The Create stack wizard asks for "identifier values" for AnomalyzerReaderRole | You picked "With existing resources (import resources)" instead of "With new resources (standard)" | Cancel, go back to Stacks, and start again from the correct dropdown option |
| A stack update says "The submitted information didn't contain changes" | Your browser served a cached copy of the template, so the file is identical to what is already deployed | Download again in a private window, confirm the file differs, then re-upload |
| Test connection fails immediately after the stack completes | AWS's own propagation of a brand-new IAM role sometimes takes a minute | Wait 1 to 2 minutes and retry without changing anything |
| Connected, but no resources under Services | Most often a filter on the Services page, especially Environment, is hiding results that are genuinely there. Failing that, the role may predate support for a service in your account | Clear the filters first. If that is not it, update the stack as described above |
| Resources appear but everything shows $0 | The resource-level cost preferences above are not both enabled, most commonly the EC2 hourly one | Enable both toggles, then allow up to a day |
| Cost is right for some resource types and missing for others | Different AWS services report cost at different granularity, some by ID and some by name. This is normal AWS behaviour that we already account for | If a specific type still shows nothing after 24 to 48 hours despite real spend, tell us the type so we can look |
| AWS Config shows 0 resources shortly after you enable it | Config's first baseline scan of pre-existing resources is slower than its recording of new changes | Allow up to an hour, and check Config's own Resources page before assuming a fault |
Bring your own LLM
Already using OpenAI, Azure OpenAI, Anthropic, Gemini, or another enterprise LLM? Bring your own model and credentials, and we'll help you integrate it securely with your existing authentication and access controls.
Contact info@confluentis.co.in and we'll help you with the authentication integrations.
How to verify we cannot write
Any of these prove it independently of our word:
- Azure -
az role assignment list --assignee <APP_ID> --all -o table. You should see only Reader, Cost Management Reader, Monitoring Reader. - Google Cloud -
gcloud projects get-iam-policy $PROJECT --flatten="bindings[].members" --filter="bindings.members:anomalyzer-reader". Only the three viewer roles should appear. - AWS - IAM console → the role or user → attached policies should be view-only.
- Any cloud - activity/audit logs will show read calls from our principal and nothing else.
Removing access
Deleting the service principal, service account or IAM role instantly and permanently cuts our access; nothing else is required. In the app, Cloud connections → Remove deletes the stored credential, and an admin can purge the entire organization and its data from the admin portal.
Pause instead of Remove if you only want scanning to stop: it keeps what we have already collected, so resuming restores your estate rather than starting discovery from nothing.
Questions, or anything in this guide that looks wrong: info@confluentis.co.in
← Back to the registration form