Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@
"tmpfiles",
"rustls",
"webpki",
"affordances"
"affordances",
"GCS",
"GCP",
"WIF",
"presigned",
"gcloud",
"gsutil",
"SigV4",
"ExternalId",
"RoleSessionName"
]
}
7 changes: 7 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@
"learn/releases/manage",
"learn/releases/staging-area"
]
},
{
"group": "Buckets",
"pages": [
"learn/buckets/aws",
"learn/buckets/gcs"
]
}
]
},
Expand Down
86 changes: 86 additions & 0 deletions docs/learn/buckets/aws.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: "Amazon S3"
description: "Configure an Amazon S3 bucket so Miru can verify and use it for uploads."
---

Miru writes uploads to your own Amazon S3 bucket. It never uses long-lived access keys: instead it assumes a cross-account IAM role in your account, scoped by an external id that is your Miru workspace id. This guide walks through creating the bucket, the role, and its policies, then registering the bucket in Miru.

<Note>
You need Miru's AWS integration role ARN and account id to write the trust policy. Copy them from the bucket-setup screen in the Miru dashboard, or ask Miru support. This guide shows them as the placeholders `<miru-aws-account>` and `<miru-connector-role>`.
</Note>

## Prerequisites

- An AWS account where you can create S3 buckets and IAM roles.
- Your Miru workspace id (`wsp_…`). Miru shows it as the read-only `external_id` field when you create the bucket, and it is the value your trust policy must pin.
- Miru's AWS integration role ARN and account id (see the note above).

## Create the S3 bucket

Create a bucket in the AWS region you want Miru to use, and note the bucket name and region — you enter both into Miru later. No public access is required.

## Create the IAM role

Create an IAM role that Miru assumes. The next two sections define the role's trust policy (who may assume it) and permissions policy (what it may do); nothing else about the role matters to Miru.

## Configure the trust policy

The trust policy must allow Miru's integration role to assume this role, and must require your workspace id as the `sts:ExternalId`:

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<miru-aws-account>:role/<miru-connector-role>" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "wsp_your_workspace_id" }
}
}
]
}
```

<Warning>
The `sts:ExternalId` condition must be an exact `StringEquals` match on your workspace id. A `StringLike` or wildcard condition, or omitting the external-id condition entirely, will fail verification: during setup Miru actively probes that your trust policy enforces the external id, and rejects roles that do not.
</Warning>

## Attach a permissions policy

Grant the role write access to the bucket. `s3:PutObject` is used both for uploads and for the create-time verification probe, which writes a zero-byte object under the `.miru/probe/` prefix:

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<your-bucket-name>/*"
},
{
"Effect": "Allow",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::<your-bucket-name>/.miru/probe/*"
}
]
}
```

The `s3:DeleteObject` statement is optional and scoped to the probe prefix. Miru best-effort-deletes each probe object after writing it, and verification still succeeds if the delete is denied; granting it simply keeps your bucket tidy.

## Expected access-denied events

To confirm your trust policy actually enforces the external id, Miru runs a deliberate negative probe during verification: it attempts to assume your role once *without* the external id and requires that attempt to be denied. As a result you see benign `AccessDenied` AssumeRole events for your role in CloudTrail. These are expected and are not a sign of misconfiguration.

## Recommended lifecycle rule

<Tip>
Add an S3 lifecycle rule that expires objects under the `.miru/probe/` prefix (for example, after one day). This cleans up any probe objects left behind when a best-effort delete was denied.
</Tip>

## Create the bucket in Miru

In the Miru dashboard or via the Platform API, create an S3 bucket, providing the `region` and the `role_arn` of the role you created. The `external_id` (your workspace id) is shown read-only — it is the value Miru sends as `sts:ExternalId`. Because Miru verifies the bucket at creation (a probe write and delete plus the external-id enforcement check), finish the trust and permissions policies first. See the [Platform API reference](/developers/platform-api/overview) for the bucket-create endpoint.
99 changes: 99 additions & 0 deletions docs/learn/buckets/gcs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: "Google Cloud Storage"
description: "Configure a Google Cloud Storage bucket so Miru can verify and use it for uploads."
---

Miru writes uploads to your own Google Cloud Storage bucket. It authenticates with Workload Identity Federation rather than service-account keys: Miru's AWS identity (`MiruGCSIntegrationRole`) federates into your GCP project and impersonates a service account you control. Miru binds the federation to your workspace by setting the STS session name to your workspace id, so you can pin your provider to exactly your tenant.

<Note>
You need Miru's AWS account id to write the provider's attribute condition. Copy it from the bucket-setup screen in the Miru dashboard, or ask Miru support. This guide shows it as the placeholder `<miru-aws-account>`. The role name `MiruGCSIntegrationRole` is fixed.
</Note>

## Prerequisites

- A GCP project where you can create buckets, service accounts, and Workload Identity pools.
- Your Miru workspace id (`wsp_…`). Miru shows it as the read-only `session_name` field, and it is the session value your provider must pin.
- Miru's AWS account id (`<miru-aws-account>`) and the fixed role name `MiruGCSIntegrationRole`.

## Create the bucket

Create a GCS bucket in your project. Uniform bucket-level access is recommended.

## Create a service account

Create the service account Miru impersonates, and grant it `roles/storage.objectAdmin` on the bucket (bucket-scoped, not project-wide). This write and delete access covers uploads and the create-time verification probe, which writes a zero-byte object under the `.miru/probe/` prefix.

## Set up workload identity federation

Create a Workload Identity pool and an AWS provider that trusts Miru's `MiruGCSIntegrationRole`. The provider's attribute condition must pin the full assumed-role ARN including your workspace id as the session name — this is what binds federation to your tenant:

<CodeGroup>

```bash gcloud
gcloud iam workload-identity-pools create miru-pool \
--location="global" --display-name="Miru"

gcloud iam workload-identity-pools providers create-aws miru-provider \
--location="global" --workload-identity-pool="miru-pool" \
--account-id="<miru-aws-account>" \
--attribute-mapping="google.subject=assertion.arn" \
--attribute-condition="assertion.arn == 'arn:aws:sts::<miru-aws-account>:assumed-role/MiruGCSIntegrationRole/<workspace-id>'"
```

```hcl Terraform
resource "google_iam_workload_identity_pool" "miru" {
workload_identity_pool_id = "miru-pool"
}

resource "google_iam_workload_identity_pool_provider" "miru_aws" {
workload_identity_pool_id = google_iam_workload_identity_pool.miru.workload_identity_pool_id
workload_identity_pool_provider_id = "miru-provider"

attribute_mapping = { "google.subject" = "assertion.arn" }
attribute_condition = "assertion.arn == 'arn:aws:sts::<miru-aws-account>:assumed-role/MiruGCSIntegrationRole/<workspace-id>'"

aws {
account_id = "<miru-aws-account>"
}
}
```

</CodeGroup>

<Warning>
Pin the session, not just the role. The attribute condition must match the full ARN ending in `/MiruGCSIntegrationRole/<workspace-id>`. A condition that matches only the role prefix — accepting any session name — will fail verification: Miru probes that your provider rejects a wrong session name, and rejects providers that do not enforce it.
</Warning>

## Grant impersonation to the pinned session

Grant `roles/iam.workloadIdentityUser` on the service account to exactly your workspace's federated session:

<CodeGroup>

```bash gcloud
gcloud iam service-accounts add-iam-policy-binding <service-account-email> \
--role="roles/iam.workloadIdentityUser" \
--member="principal://iam.googleapis.com/<pool-name>/subject/arn:aws:sts::<miru-aws-account>:assumed-role/MiruGCSIntegrationRole/<workspace-id>"
```

```hcl Terraform
resource "google_service_account_iam_member" "miru" {
service_account_id = google_service_account.uploader.name
role = "roles/iam.workloadIdentityUser"
member = "principal://iam.googleapis.com/<pool-name>/subject/arn:aws:sts::<miru-aws-account>:assumed-role/MiruGCSIntegrationRole/<workspace-id>"
}
```

</CodeGroup>

Binding the member as `principal://…/subject/…` (not a role-wide `principalSet`) ties impersonation to your workspace's exact federated session, so no other tenant can impersonate your service account.

## Recommended lifecycle rule

<Tip>
Add a lifecycle rule that deletes objects under the `.miru/probe/` prefix after one day, as a safety net for any probe objects left behind.
</Tip>

## Create the bucket in Miru

In the Miru dashboard or via the Platform API, create a GCS bucket, providing the `wip_provider` (the full provider resource name, `//iam.googleapis.com/projects/…/locations/global/workloadIdentityPools/…/providers/…`) and the `service_account_email`. The `session_name` (your workspace id) is shown read-only. Because Miru verifies at creation by federating, impersonating the service account, and running the write probe, finish the federation setup first. See the [Platform API reference](/developers/platform-api/overview) for the bucket-create endpoint.
Loading
Loading