Skip to content

Cloud credentials

The AWS and GCP connectors need cloud credentials to read. There are two modes, and they are orthogonal to the secret store: that one decides where secrets live, this one decides how cloud credentials are resolved.

Janela do terminal
RUNNER_AWS_CRED_MODE=static # default
RUNNER_AWS_CRED_MODE=chain
RUNNER_GCP_CRED_MODE=static # default
RUNNER_GCP_CRED_MODE=chain

Reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN from the secret store and injects them explicitly into the client.

Serves anyone handed ready-made keys, or keeping them in Vault or Secrets Manager. It’s the default for backward compatibility.

Injects no key at all. The SDK’s default credential chain resolves it: IRSA on EKS, task role on ECS, instance profile on EC2, environment variables, or the SSO cache.

It’s the correct mode when the runner runs inside AWS, and it’s what keeps static keys out of circulation.

Janela do terminal
RUNNER_AWS_CRED_MODE=chain
AWS_PROFILE=my-profile # in development, with aws sso login

A subtlety that causes an invisible connector

Section titled “A subtlety that causes an invisible connector”

The served set is secret-aware: a connector whose required keys are missing drops out of the announced set. But in chain mode the credential doesn’t come from the secret store, so without special handling, the runner would not announce the aws.* operations even while working perfectly.

That’s why the runtime marks the aws connector as always served in chain mode. Worth knowing this special case exists, so the behavioral difference between the two modes doesn’t surprise you.

chain mode is single-account by construction. For several AWS accounts, run one runner per account, each with its own IRSA identity. The graph is merged on the control-plane side.

With RUNNER_CONNECTORS=real, the runner runs a best-effort STS probe at boot to discover the account and region, and reports that in the handshake. It’s what lets the control-plane resolve the tenant’s AWS context from the live runner, instead of manual configuration.

The probe never throws. If it fails, the information simply doesn’t go, and the control-plane uses its fallback.

The design is the exact sibling of the AWS side.

Reads the service account’s JSON key (GCP_SA_KEY, the JSON on a single line) from the secret store and builds the clients with it.

Injects no key. ADC resolves it: Workload Identity on GKE, metadata on GCE, the file pointed at by GOOGLE_APPLICATION_CREDENTIALS, or gcloud.

With chain, GCP_SA_KEY isn’t needed.

Janela do terminal
RUNNER_GCP_CRED_MODE=chain
GCP_PROJECT_ID=my-project

Also single-project: for several projects, one runner per project, each with its own Workload Identity GSA.

Here the design is not a sibling of the other two, and the difference is posture, not convenience.

MGC authenticates with an API key (X-API-Key header). There is no credential-chain analogue — nothing equivalent to IRSA on AWS or Workload Identity on GCP — so there is no RUNNER_MGC_CRED_MODE: only the static key, read from the customer’s secret manager. Inventing a mode that doesn’t exist would be worse than the absence.

Janela do terminal
MGC_API_KEY=…
MGC_REGION=br-se1 # optional; the region lives in the URL, one runner serves one region
MGC_TENANT_ID=… # optional; saves one call at boot
MGC_S3_ACCESS_KEY=… # object storage; the `key_pair_id` of the SAME key
MGC_S3_SECRET_KEY=… # object storage; the `key_pair_secret` of the SAME key

Scopes are the control, and they’re immutable

Section titled “Scopes are the control, and they’re immutable”

An MGC API key carries three components: api_key (what RootPilot sends in the X-API-Key header), key_pair_id and key_pair_secret — together the Object Storage S3 credential, which become the MGC_S3_ACCESS_KEY/MGC_S3_SECRET_KEY above.

Creating the key from the console with “everything” checked yields roughly 160 scopes over the whole account, billing and IAM included. Create it from the CLI with explicit --scopes and grant read-only — one read scope per product RootPilot will read:

Product What RootPilot reads
Virtual machine (virtual-machine.read) instances and their IPs
Network (network.read) VPCs, subnets, security groups, public IPs, NAT, load balancers
Object storage (object-storage.read) buckets and exposure posture
Block storage volumes, attachment and encryption
DBaaS database instances, clusters and replicas
Kubernetes (MKE) clusters, node pools and who can reach the API server
Container registry registries, repositories and images
Audit who did what, when, and to which resource

The identifiers for the last four come from the console/CLI scope listing itself — do not guess them from the first three. A key missing one product’s scope does not break RootPilot: only that product’s reads come back auth_error, and the rest of the connector keeps serving. In practice the product simply goes invisible without a sound, so it is worth checking the table above against what you expect to see.

That’s better than it sounds: RootPilot’s read-only posture becomes enforced in the credential, not only in the code. The connector refuses writes, and the key doesn’t even permit them. Scopes aren’t editable afterwards — if the need changes, recreate the key.

The “account” analogue on MGC is the tenant_id, which appears on VPCs, security groups and public IPs. There is no “project”.

Unlike GCP, it isn’t free: the API key is opaque, and there’s no workload-identity metadata server. So the runner uses MGC_TENANT_ID when set and otherwise derives it from one VPC listing — best-effort. With no key, no VPC, or a failing call, no account is announced and the control plane falls back to configuration. It never invents an id: announcing a non-existent account would route reads somewhere that doesn’t answer.

Variable Notes
AWS_REGION Used by the AWS connectors and required for aws-sts attestation.
MGC_API_KEY Magalu Cloud API key. Create it with read scopes — one per product RootPilot will read (see the table above).
MGC_REGION br-se1 (default) or br-ne1. Lives in the URL: one runner serves one region.
MGC_TENANT_ID Skips the discovery call at boot. Recommended in production.
MGC_S3_ACCESS_KEY The key_pair_id of the same API key. Object storage speaks S3 (SigV4) against a different host — a different credential, same owner. Optional: without it buckets stay invisible, with no boot error.
MGC_S3_SECRET_KEY The key_pair_secret of the same API key. What authorizes bucket reads is the object-storage.read scope on MGC_API_KEY, not this pair.
EKS_CLUSTER Without it, the EKS health check answers skipped: no cluster configured. The connector shows up as unconfigured in Fleet, which is different from broken and equally blind.
GCP_PROJECT_ID Target project.
GCP_REGION Target region.
GCP_BILLING_EXPORT_TABLE BigQuery billing-export table. Without it, the GCP cost operation fails explicitly.