From 5bbe28f5aa78157157c907c0e15485c4e77e6a80 Mon Sep 17 00:00:00 2001 From: Adil Ansari Date: Thu, 8 Oct 2026 15:16:02 -0700 Subject: [PATCH 1/3] docs(partner-integrations): fix access control and add permission recipes Audit the partner Access Control page against the gateway code and fix the claims that were wrong: the create-key path, what user_role gates, and the claim that IAM policies can restrict a bucket role. Document that the provisioned key owns its bucket, the order Tigris uses to evaluate a request, and recipes for common partner permission setups. Also fix the provision response example and add common partner questions to the architecture page, and correct the soft delete page: soft-deleted buckets are not purged when retention ends, and retention changes apply to objects that are already soft-deleted. --- docs/buckets/soft-delete.mdx | 43 ++- docs/partner-integrations/access-control.mdx | 338 ++++++++++++++---- docs/partner-integrations/architecture.mdx | 54 ++- .../diagrams/data/partner-access-control.json | 10 +- 4 files changed, 335 insertions(+), 110 deletions(-) diff --git a/docs/buckets/soft-delete.mdx b/docs/buckets/soft-delete.mdx index 7628e2ef..038c9b60 100644 --- a/docs/buckets/soft-delete.mdx +++ b/docs/buckets/soft-delete.mdx @@ -9,11 +9,10 @@ keeps the deleted object for a configurable retention window during which you can list it, restore it, or permanently remove it. Once the retention window expires, Tigris cleans the data up automatically. -The same retention policy also protects the bucket itself: deleting a bucket -with soft delete enabled moves the bucket into a soft-deleted state instead of -hard deleting it. A soft-deleted bucket can be restored back into an active -bucket within the retention window, and is permanently removed once the -retention window expires. +Soft delete also protects the bucket itself: deleting a bucket with soft delete +enabled moves the bucket into a soft-deleted state instead of hard deleting it. +A soft-deleted bucket stays until you restore it or permanently delete it. +Tigris does not remove it when the retention window expires. ## How soft delete works @@ -27,10 +26,10 @@ When soft delete is enabled on a bucket: them, restore them to a live state, or permanently delete them ahead of the schedule. - Deleting a bucket with soft delete enabled moves the bucket itself into a - soft-deleted state. The bucket and its data stay recoverable for the retention - window. -- After the retention window passes, Tigris permanently removes the soft-deleted - data and reclaims its storage. + soft-deleted state. The bucket and its data stay recoverable until you restore + the bucket or permanently delete it. +- After the retention window passes, Tigris permanently removes soft-deleted + objects from active buckets and reclaims their storage. - Soft-deleted data continues to consume storage and is billed at the same rate as live data until it is removed. @@ -43,8 +42,9 @@ bucket or object (recoverable) │ (async, after retention ◄─── restore ─────┘ ``` -Cleanup runs asynchronously, so data is removed shortly after the retention -window passes. +Cleanup runs asynchronously, so objects are removed shortly after the retention +window passes. A soft-deleted bucket is not cleaned up automatically. It is +removed only when you permanently delete it. ## Enabling soft delete on a bucket @@ -193,12 +193,12 @@ below rather than being permanently removed. A bucket deleted while soft delete was enabled is not removed immediately. It moves to a soft-deleted state where it is hidden from your normal bucket list -but stays recoverable until the retention window expires. +but stays recoverable until you restore it or permanently delete it. In the Tigris Dashboard sidebar, **Buckets** expands into **All**, **Forks**, **Owned by me**, and **Deleted**. Every bucket that was deleted while soft delete was enabled appears under **Deleted**. Each row in this view is a -soft-deleted bucket that is still within its retention window. +soft-deleted bucket. To restore a bucket, open **Buckets → Deleted** in the sidebar. Then open the row's overflow menu (`...`) and choose **Restore**: @@ -207,9 +207,8 @@ row's overflow menu (`...`) and choose **Restore**: After a successful restore, the bucket is fully active again — it moves back to the regular bucket list and every object it held at the time of deletion is live -and readable, exactly as it was before the bucket was deleted. If you want to -remove a soft-deleted bucket immediately instead of waiting for the retention -window to expire, choose **Delete** from the same menu. +and readable, exactly as it was before the bucket was deleted. To remove a +soft-deleted bucket permanently, choose **Delete** from the same menu. While a bucket is in the soft-deleted state, its name stays reserved — you cannot create a new bucket with the same name until either the bucket is @@ -745,9 +744,9 @@ earlier versions live and accessible. ## Things to note -- Retention applies per bucket. Every soft-deleted bucket and object in the - bucket uses the same retention window. -- Disabling soft delete on a bucket does not purge data that is already - soft-deleted. It continues to age out on its original retention schedule. -- Changing the retention value affects new soft deletes from that point forward. - Already soft-deleted data keeps the retention it was created with. +- Retention applies per bucket. Every soft-deleted object in the bucket uses the + bucket's current retention window. +- Disabling soft delete on a bucket also ends the protection for objects that + are already soft-deleted. Tigris removes them in the next cleanup. +- Changing the retention value also applies to objects that are already + soft-deleted. A shorter window can remove them sooner. diff --git a/docs/partner-integrations/access-control.mdx b/docs/partner-integrations/access-control.mdx index 57e9b88b..efa38d67 100644 --- a/docs/partner-integrations/access-control.mdx +++ b/docs/partner-integrations/access-control.mdx @@ -3,22 +3,24 @@ When you create access keys through the Partner API, three fields control what those keys can do: -- `user_role` controls what your user can do through the **management API** - (listing keys, updating org settings). +- `user_role` tells Tigris what the user in `user_id` can do in **this Partner + API call**: which access keys the call can manage, and which bucket roles it + can grant. - `buckets_roles` controls what **S3 operations** the access key can perform on specific buckets. - `access_key_scope` controls whether the key also receives [default allowed operations](/docs/iam/manage-access-key/#default-allowed-operations) such as create bucket and list buckets. -These fields are independent. They apply at different times and serve different -purposes. +`buckets_roles` and `access_key_scope` are stored on the access key and apply to +every request the key makes. `user_role` applies only to the call that you pass +it on. Tigris does not store it on the key. -| Field | Where it applies | What it controls | -| ------------------ | ------------------ | --------------------------------------------------------- | -| `user_role` | Partner API calls | Management scope: list all keys vs own keys, org settings | -| `buckets_roles` | S3 data operations | What the access key can read, write, or manage per bucket | -| `access_key_scope` | S3 and IAM APIs | Whether the key receives default allowed operations | +| Field | Where it applies | What it controls | +| ------------------ | -------------------- | ---------------------------------------------------------------- | +| `user_role` | One Partner API call | Which keys the call can manage and which bucket roles it can set | +| `buckets_roles` | S3 requests | What the access key can read, write, or manage per bucket | +| `access_key_scope` | S3 and IAM requests | Whether the key receives default allowed operations | import PartnerAccessControlDiagram from "@site/src/components/diagrams/PartnerAccessControlDiagram"; @@ -29,12 +31,12 @@ import PartnerAccessControlDiagram from "@site/src/components/diagrams/PartnerAc ## Organization Role (`user_role`) The `user_role` field tells Tigris what privileges this user has in your system. -You pass it on provisioning, access key, and org management endpoints. +You pass it on provisioning and access key calls. -| Value | What it means | -| -------- | ----------------------------------------------------------------------------------------- | -| `Admin` | Full org access. Can manage all access keys, update org settings, and view all resources. | -| `Member` | Standard access. Can only manage their own access keys and resources. | +| Value | What the call can do | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `Admin` | View, update, rotate, and delete any access key in the org. Grant any bucket role on any bucket in the org, including `"*"`. | +| `Member` | View, update, rotate, and delete only the access keys of `user_id`. Grant bucket roles only on buckets that `user_id` provisioned. | The behavioral difference shows up immediately. When you call [List Access Keys](/docs/partner-integrations/api/tigris-list-access-keys) with @@ -42,39 +44,42 @@ The behavioral difference shows up immediately. When you call `user_role: "Member"` (or omitted), you get back **only keys owned by that user**. -This same scoping applies to other management operations. Admin-only operations -include: +If you omit `user_role`, the call is treated as `Member`. A `Member` call that +grants a role on a bucket that `user_id` did not provision fails with a +permission error. If your backend manages access for all of your users, pass +`user_role: "Admin"`. -- **Manage all access keys** -- view, rotate, or delete any key in the org, not - just the user's own -- **Update org settings** -- change org name, quotas, active status -- **Invite and manage users** -- send invitations, update roles, remove members - -If you omit `user_role`, the user is treated as a `Member`. For most -integrations, `Member` is the right default. +The access key that the call creates does not become an admin. Its access comes +only from its `buckets_roles`, its `access_key_scope`, and its IAM policies. ## Bucket Roles (`buckets_roles`) The `buckets_roles` field is an array of `{bucket_name, role}` pairs stored on -the access key. Four roles are available: `ReadOnly`, `ReadWrite`, `Editor`, and -`Admin`. See the +the access key. See the [RBAC permissions table](/docs/concepts/authnz#role-based-access-control-rbac) for exactly which S3 operations each role permits. -Use `ReadWrite` for object reads and writes without bucket administration. Use -`Editor` when the key must also manage bucket configuration, public access, or -bucket deletion. Editors cannot manage bucket shares or IAM policies. +| Role | What the key can do on the bucket | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `ReadOnly` | Read and list objects. Create snapshots and forks. | +| `ReadWrite` | Everything in `ReadOnly`, plus write and delete objects. It cannot change bucket settings or delete the bucket. | +| `Editor` | Everything in `ReadWrite`, plus bucket settings, public access, and bucket deletion. It cannot manage bucket shares or IAM policies. | +| `Admin` | Every operation on every bucket in the org. Use it only with `"bucket_name": "*"`. | + +Use `"*"` as the bucket name to give a role on all buckets in the org, including +buckets created later. A key can have only one `"*"` entry, and only a +`user_role: "Admin"` call can grant it. -Use `"*"` as the bucket name to grant access to all buckets in the org. An -access key with `{"bucket_name": "*", "role": "Admin"}` is treated as a full org -admin, bypassing all bucket-level permission checks. +`{"bucket_name": "*", "role": "Admin"}` must be the only entry on the key. That +key is a full org admin. It can do every operation on every bucket, except the +deletes that [delete protection](#how-tigris-evaluates-a-request) blocks. -## Access key scope (`access_key_scope`) {#access-key-scope} +## Access Key Scope (`access_key_scope`) {#access-key-scope} This field controls the [default allowed operations](/docs/iam/manage-access-key/#default-allowed-operations) -on the key. A standard key can create buckets, list buckets, and create access -keys and IAM policies even with no bucket role. +on the key. A standard key can create buckets, list buckets, and manage its own +access keys and IAM policies, even with no bucket role. | Value | What it means | | -------------------- | -------------------------------------------------------- | @@ -82,67 +87,135 @@ keys and IAM policies even with no bucket role. | `no_default_allow` | The key does not receive the default allowed operations. | If you omit `access_key_scope`, the key uses `standard`. You can set the field -on [Create Access Key](/docs/partner-integrations/api/tigris-create-access-key) +on [Provision](/docs/partner-integrations/api/tigris-provisioning), +[Create Access Key](/docs/partner-integrations/api/tigris-create-access-key), and [Update Access Key](/docs/partner-integrations/api/tigris-update-access-key). If you omit the field on update, the current scope stays unchanged. -## How the fields interact +A key that creates a bucket becomes the owner of that bucket, and an owner can +do every operation on it. Use `no_default_allow` for keys that you give to your +users, so that they cannot create buckets that they own. + +## The Provisioned Access Key + +[Provision](/docs/partner-integrations/api/tigris-provisioning) returns an +access key, and that key is the **owner** of the bucket it provisions. The owner +can do every operation on the bucket. It can delete the bucket, change its +settings, and turn off soft delete. Bucket roles, IAM policies, and +`access_key_scope` cannot limit what an owner can do on its bucket. + +Keep the provisioned key in your backend. Give your users a separate key that +has only the access they need. See +[Give Users Data Access Without Bucket Admin](#give-users-data-access-without-bucket-admin). + +## How Tigris Evaluates a Request + +Tigris checks each S3 request in this order. It stops at the first step that +gives a result. + +| Step | Check | Result | +| ---- | ------------------------------------------------------------------------- | ------------- | +| 1 | The bucket has delete protection and the request deletes a bucket or data | Deny | +| 2 | The key has `{"bucket_name": "*", "role": "Admin"}` | Allow | +| 3 | The key owns the bucket | Allow | +| 4 | A bucket role on the key permits the operation | Allow | +| 5 | An IAM policy on the key matches. A `Deny` statement wins over an `Allow` | Deny or allow | +| 6 | The key uses `standard` and the operation is a default allowed operation | Allow | +| 7 | No step gave a result | Deny | + +A policy cannot take away an operation that steps 2 to 4 allow. For example, a +`Deny` on `s3:DeleteBucket` has no effect on a key with the `Editor` role, +because the role allows the delete at step 4. To limit a key, give it a smaller +role, or give it no role and grant access with a policy. + +## Permission Recipes -| Scenario | `user_role` | `buckets_roles` | `access_key_scope` | Result | -| --------------------- | ----------- | ---------------------------------------- | ------------------ | ------------------------------------------------- | -| **Developer uploads** | `Member` | `user-uploads: ReadWrite` | `no_default_allow` | User manages own key, can read/write their bucket | -| **Platform admin** | `Admin` | `*: Admin` | `standard` | Full management + full S3 access | -| **CI/CD pipeline** | `Member` | `artifacts: ReadOnly` | `no_default_allow` | Automated reads, no access-key or IAM management | -| **Mixed access** | `Member` | `assets: ReadOnly`, `uploads: ReadWrite` | `no_default_allow` | Read shared content, write user content | +Each recipe shows one request. Replace the IDs and bucket names with your own. -## Common Access Patterns +The recipes that grant a bucket role use `user_role: "Admin"`, so that your +backend can grant a role on any bucket in the org. You can use `Member` instead +when `user_id` is the user that provisioned the bucket. -### Read-only access to a bucket +### Give Users Data Access Without Bucket Admin + +Use this recipe when your users read and write their own data and your backend +manages the buckets. ```http -POST /v1/providers/{provider_id}/orgs/{org_id}/access-keys +POST /v1/providers/{provider_id}/orgs/{org_id}/key Content-Type: application/json { "user_id": "user-123", - "user_role": "Member", + "user_role": "Admin", + "name": "user-123-data", "access_key_scope": "no_default_allow", "buckets_roles": [ - { "bucket_name": "assets", "role": "ReadOnly" } + { "bucket_name": "user-data", "role": "ReadWrite" } ] } ``` -### Read/write access to a bucket +The key can read, write, and delete objects in `user-data`. It cannot delete the +bucket, change bucket settings, turn off soft delete, create buckets, or create +access keys. + +:::note + +With `user_role: "Member"`, this call works only when `user_id` is the user that +provisioned `user-data`. If each of your users has a different `user_id`, use +`Admin`. + +::: + +### Read-Only Access to One Bucket ```http -POST /v1/providers/{provider_id}/orgs/{org_id}/access-keys +POST /v1/providers/{provider_id}/orgs/{org_id}/key Content-Type: application/json { "user_id": "user-123", - "user_role": "Member", + "user_role": "Admin", "access_key_scope": "no_default_allow", "buckets_roles": [ - { "bucket_name": "uploads", "role": "ReadWrite" } + { "bucket_name": "assets", "role": "ReadOnly" } ] } ``` -This key can read and write objects in `uploads`. It cannot list buckets or -create buckets unless a policy grants those actions. It cannot manage access -keys or IAM policies. +The key can read and list objects in `assets`. It cannot write or delete +objects, and it cannot list or create buckets. -### Mixed permissions across buckets +### Read-Only Access to All Buckets ```http -POST /v1/providers/{provider_id}/orgs/{org_id}/access-keys +POST /v1/providers/{provider_id}/orgs/{org_id}/key +Content-Type: application/json + +{ + "user_id": "reporting", + "user_role": "Admin", + "access_key_scope": "no_default_allow", + "buckets_roles": [ + { "bucket_name": "*", "role": "ReadOnly" } + ] +} +``` + +The key can read every bucket in the org, including buckets created later. Only +a `user_role: "Admin"` call can grant a role on `"*"`. + +### Different Access per Bucket + +```http +POST /v1/providers/{provider_id}/orgs/{org_id}/key Content-Type: application/json { "user_id": "user-123", - "user_role": "Member", + "user_role": "Admin", "access_key_scope": "no_default_allow", "buckets_roles": [ { "bucket_name": "assets", "role": "ReadOnly" }, @@ -151,14 +224,61 @@ Content-Type: application/json } ``` -### Full admin key for org owner +The key can read `assets`, and it can read and write `uploads`. + +### Upload-Only Key + +Use a policy for this recipe, because no bucket role allows uploads without +reads. The key gets no bucket role, so Tigris evaluates the policy. ```http -POST /v1/providers/{provider_id}/orgs/{org_id}/access-keys +POST /v1/providers/{provider_id}/orgs/{org_id}/key Content-Type: application/json { - "user_id": "admin-user", + "user_id": "user-123", + "name": "uploader", + "access_key_scope": "no_default_allow", + "create_policies": [ + { + "name": "uploads-put-only", + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:PutObject", "s3:AbortMultipartUpload"], + "Resource": ["arn:aws:s3:::uploads/*"] + } + ] + } + } + ] +} +``` + +The key can upload objects to `uploads`, with multipart uploads too. It cannot +read, list, or delete objects. It can overwrite an object that has the same +name. + +`s3:PutObject` covers the create, upload part, and complete steps of a multipart +upload. Tigris rejects `s3:CreateMultipartUpload` and `s3:UploadPart` in a +policy. + +Policy names are unique in the org. To give the same policy to more keys, create +it one time, then pass its name in `attach_policies`. + +### Backend Admin Key + +Your backend can use the provisioned key for its bucket. To manage all buckets +in the org from one key, create an org admin key: + +```http +POST /v1/providers/{provider_id}/orgs/{org_id}/key +Content-Type: application/json + +{ + "user_id": "platform-backend", "user_role": "Admin", "buckets_roles": [ { "bucket_name": "*", "role": "Admin" } @@ -166,18 +286,92 @@ Content-Type: application/json } ``` -For most integrations, `user_role: "Member"` with per-bucket `ReadOnly` or -`ReadWrite` roles covers what you need. Use `Editor` only when the integration -must manage bucket configuration. Reserve `Admin` for org owners who need to -manage other users' keys or org-level settings. +Keep this key in your backend. Do not give it to your users. -## Fine-Grained Access with IAM Policies +### Protect Buckets From Accidental Deletion + +Turn on [soft delete](/docs/buckets/soft-delete/) when you provision the bucket: + +```http +POST /v1/providers/{provider_id}/orgs/{org_id}/provision +Content-Type: application/json + +{ + "bucket_name": "user-data", + "org_name": "Acme Corp", + "user_id": "user-123", + "bucket_options": { + "soft_delete": { "enabled": true, "retention_days": 30 } + } +} +``` + +For an existing bucket, use +[Set Soft Delete](/docs/partner-integrations/api/tigris-set-soft-delete). + +When a user deletes the bucket, it moves to the deleted buckets list. Use +[List Deleted Buckets](/docs/partner-integrations/api/tigris-list-deleted-buckets) +to find it, and +[Restore Bucket](/docs/partner-integrations/api/tigris-restore-bucket) to bring +it back. + +A soft-deleted bucket stays until you restore it or +[delete it permanently](/docs/partner-integrations/api/tigris-purge-bucket). It +counts toward storage usage until then. + +A `ReadWrite` key cannot turn off soft delete. The bucket owner, `Editor` keys, +and org admin keys can turn it off. + +### Change Bucket Settings for Your Users + +No bucket role allows bucket settings without bucket deletion. `Editor` includes +`DeleteBucket`, and a policy cannot take it away (see +[How Tigris Evaluates a Request](#how-tigris-evaluates-a-request)). + +Keep your users on `ReadWrite`, and change bucket settings from your backend +with the Partner API: -For access control more granular than per-bucket roles, like restricting to -object prefixes, IP ranges, or time windows, use -[IAM policies](/docs/iam/policies/). Policies are evaluated after role checks, -so they can further restrict what a role allows. +- [Update Bucket](/docs/partner-integrations/api/tigris-update-bucket) for + public access, storage class, regions, and object notifications +- [Set Lifecycle Rules](/docs/partner-integrations/api/tigris-set-lifecycle-rules) +- [Attach a Custom Domain](/docs/partner-integrations/api/tigris-set-custom-domain) +- [Set Soft Delete](/docs/partner-integrations/api/tigris-set-soft-delete) + +### Rotate or Revoke a Key + +To replace a leaked secret, call +[Rotate Access Key](/docs/partner-integrations/api/tigris-rotate-access-key). It +returns a new secret and keeps the key ID, so the key keeps its roles, policies, +and bucket ownership. + +To revoke a key, call +[Delete Access Key](/docs/partner-integrations/api/tigris-delete-access-key). +Deleting a key does not delete the buckets that it owns. + +:::note + +After you rotate or delete a key, the old secret can continue to work for up to +15 minutes. + +::: + +A `Member` call can rotate or delete only the keys of its `user_id`. A +`user_role: "Admin"` call can rotate or delete any key in the org. + +## Fine-Grained Access with IAM Policies -**Important:** `Admin` role bypasses policy evaluation entirely. If you need -policies to apply, use `Member` with `Editor`, `ReadWrite`, or `ReadOnly` roles -instead. +For access that bucket roles cannot give, use +[IAM policies](/docs/iam/policies/). You can create a policy with +[Create Policy](/docs/partner-integrations/api/tigris-create-policy) and attach +it with `attach_policies` on Create Access Key or `add_policies` on Update +Access Key. You can also create and attach a policy in one call with +`create_policies`. + +Policies only add access. Tigris reads them only when the key's bucket roles and +bucket ownership do not already allow the operation (see +[How Tigris Evaluates a Request](#how-tigris-evaluates-a-request)): + +- To grant access with a policy, give the key no bucket role on that bucket. +- A `Deny` statement blocks only access that a policy or a default allowed + operation gives. It does not block access that a bucket role, bucket + ownership, or the `"*"` `Admin` role gives. diff --git a/docs/partner-integrations/architecture.mdx b/docs/partner-integrations/architecture.mdx index fac8b574..2ca72f40 100644 --- a/docs/partner-integrations/architecture.mdx +++ b/docs/partner-integrations/architecture.mdx @@ -27,7 +27,7 @@ graph TD | **Provider** | Your partner account. This identifies your platform and holds your signing key for API authentication. You manage all customer organizations through this provider. | | **Organization** | An isolated container for a single tenant/customer. Each organization contains its own buckets, users, access keys, and has separate usage tracking and billing. | | **Bucket** | S3-compatible storage container within an organization. Organizations can have multiple buckets. | -| **User** | An identity within an organization with either `Admin` or `Member` role. Users can create access keys and manage resources within their organization. | +| **User** | A user ID from your system, passed as `user_id`. Each Partner API call says whether the user is an `Admin` or a `Member` with `user_role`. | | **Access Key** | S3-compatible credentials (access key ID + secret) scoped to an organization. Can optionally be restricted to specific buckets within that org. | ## Organizations Explained @@ -76,18 +76,21 @@ Content-Type: application/json The `org_id` should be a unique identifier for that tenant in your system (e.g., `tenant-abc123` or `customer-{uuid}`). -**2. Store the returned credentials** - The response includes an access key and -secret scoped to that organization: +**2. Store the returned credentials** - The response includes an access key that +owns the new bucket: ```json { + "bucket_name": "user-data", "access_key_id": "tid_...", - "secret_access_key": "tsec_...", - "bucket": "user-data", - "org_id": "tenant-abc123" + "secret_access_key": "tsec_..." } ``` +This key has full control of the bucket, including deleting it. Keep it in your +backend, and give your users a key with less access. See +[The Provisioned Access Key](/docs/partner-integrations/access-control#the-provisioned-access-key). + **3. Create additional buckets** as needed using the Partner API or standard S3 CreateBucket API. All buckets remain isolated within the tenant's organization. @@ -126,9 +129,9 @@ Content-Type: application/json } ``` -The `user_role` field controls what this user can do through management -endpoints. See [Access Control](/docs/partner-integrations/access-control) for -details on how roles and permissions work. +The `user_role` field applies to this call only. See +[Access Control](/docs/partner-integrations/access-control) for details on how +roles and permissions work. ### Offboarding Tenants @@ -160,11 +163,40 @@ Content-Type: application/json 3. Delete all buckets using the bucket deletion API 4. The organization remains for historical billing records +## Common Questions + +### Which `user_id` Should I Use? + +Each access key and each provisioned bucket belongs to a `user_id`. Two models +work: + +- **One backend user per organization**, such as your service account. All keys + and buckets belong to that user, so `Member` calls can manage all of them. +- **One user per person in your system.** Each person's keys stay separate, and + List Access Keys with `Member` returns only that person's keys. To grant a + role on a bucket that a different user provisioned, pass `user_role: "Admin"`. + +### Is `org_id` Unique Across Tigris? + +No. An `org_id` is unique only within your provider. Use your own account or +tenant ID. + +### Are Bucket Names Unique per Organization? + +No. Bucket names are global across all Tigris users, as in AWS S3. To prevent a +name conflict, add a prefix such as your `org_id`, or generate the names. + +### Which Units Do Usage and Invoices Use? + +Invoices show storage in GiB-month and data transfer in GiB. One GiB is 1024³ +bytes. Each metric from the usage API includes its own `unit`. The quota field +`org_quota.limit_bytes` is a number of bytes. + ## Next Steps - **[Access Control](/docs/partner-integrations/access-control)** -- Learn how - to use `user_role`, `buckets_roles`, and `access_key_scope` to control what - your tenants' access keys can do + to use `user_role`, `buckets_roles`, and `access_key_scope`, with permission + recipes for common setups - **[Partner Integrations API Reference](/docs/partner-integrations/api/)** -- Complete API documentation for all partner endpoints diff --git a/src/components/diagrams/data/partner-access-control.json b/src/components/diagrams/data/partner-access-control.json index 24fe7be8..c1a1b89e 100644 --- a/src/components/diagrams/data/partner-access-control.json +++ b/src/components/diagrams/data/partner-access-control.json @@ -27,7 +27,7 @@ "id": "create-key-text", "x": 485, "y": 60, - "text": "POST /access-keys", + "text": "POST /key", "fontSize": 18, "fontFamily": 1, "strokeColor": "#0A171E" @@ -170,7 +170,7 @@ "id": "admin-mgmt-desc", "x": 60, "y": 317, - "text": "All keys, org settings, users", + "text": "Any key; any bucket role", "fontSize": 14, "fontFamily": 1, "strokeColor": "#8fa3b0" @@ -203,7 +203,7 @@ "id": "member-mgmt-desc", "x": 60, "y": 402, - "text": "Own keys only", + "text": "Own keys; own buckets only", "fontSize": 14, "fontFamily": 1, "strokeColor": "#8fa3b0" @@ -370,7 +370,7 @@ "id": "admin-s3-desc", "x": 470, "y": 521, - "text": "All ops, bypasses all checks", + "text": "All ops, all buckets (\"*\")", "fontSize": 13, "fontFamily": 1, "strokeColor": "#8fa3b0" @@ -428,7 +428,7 @@ "id": "standard-scope-desc", "x": 880, "y": 321, - "text": "Create/list buckets; limited\nkey and policy management", + "text": "Create/list buckets; manage\nown keys and policies", "fontSize": 13, "fontFamily": 1, "strokeColor": "#8fa3b0" From 2d6bb949c29f32a98e86ed44c978b9ff068b9923 Mon Sep 17 00:00:00 2001 From: Adil Ansari Date: Fri, 9 Oct 2026 14:24:43 -0700 Subject: [PATCH 2/3] docs(partner-integrations): fix user_role scope and add key migration recipe user_role limits bucket roles and key ownership, not IAM policies. A Member call can create a policy for any bucket and attach it to any key in the org, so the Member row no longer says that it can update only its own keys. Add a recipe to move users off the provisioned key. Rotating the key is not enough, because keys that the user created with it keep working. Also: - Provision creates a new owner key on each call. Say that the backend can delete it and keep managing the bucket with the Partner API. - Soft delete does not stop owner, Editor, or admin keys. They can turn it off or purge a soft-deleted bucket over S3. - Role and scope changes can take up to 15 minutes, like rotate and delete. - Turning off soft delete does not remove objects in the next cleanup. Say that Tigris can remove them later. --- docs/buckets/soft-delete.mdx | 3 +- docs/partner-integrations/access-control.mdx | 72 +++++++++++++++++--- docs/partner-integrations/architecture.mdx | 2 +- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/docs/buckets/soft-delete.mdx b/docs/buckets/soft-delete.mdx index 038c9b60..a02ff315 100644 --- a/docs/buckets/soft-delete.mdx +++ b/docs/buckets/soft-delete.mdx @@ -747,6 +747,7 @@ earlier versions live and accessible. - Retention applies per bucket. Every soft-deleted object in the bucket uses the bucket's current retention window. - Disabling soft delete on a bucket also ends the protection for objects that - are already soft-deleted. Tigris removes them in the next cleanup. + are already soft-deleted. Tigris can remove them in a later cleanup, so do not + expect to restore them. - Changing the retention value also applies to objects that are already soft-deleted. A shorter window can remove them sooner. diff --git a/docs/partner-integrations/access-control.mdx b/docs/partner-integrations/access-control.mdx index efa38d67..a2d13dbb 100644 --- a/docs/partner-integrations/access-control.mdx +++ b/docs/partner-integrations/access-control.mdx @@ -33,10 +33,10 @@ import PartnerAccessControlDiagram from "@site/src/components/diagrams/PartnerAc The `user_role` field tells Tigris what privileges this user has in your system. You pass it on provisioning and access key calls. -| Value | What the call can do | -| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `Admin` | View, update, rotate, and delete any access key in the org. Grant any bucket role on any bucket in the org, including `"*"`. | -| `Member` | View, update, rotate, and delete only the access keys of `user_id`. Grant bucket roles only on buckets that `user_id` provisioned. | +| Value | What the call can do | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Admin` | View, update, rotate, and delete any access key in the org. Grant any bucket role on any bucket in the org, including `"*"`. | +| `Member` | View, rotate, and delete only the access keys of `user_id`. Change the bucket roles and the scope of only those keys. Grant bucket roles only on buckets that `user_id` provisioned. | The behavioral difference shows up immediately. When you call [List Access Keys](/docs/partner-integrations/api/tigris-list-access-keys) with @@ -49,6 +49,10 @@ grants a role on a bucket that `user_id` did not provision fails with a permission error. If your backend manages access for all of your users, pass `user_role: "Admin"`. +`user_role` does not limit IAM policies. A `Member` call can create a policy for +any bucket in the org. It can also attach a policy to, or detach a policy from, +any access key in the org. + The access key that the call creates does not become an admin. Its access comes only from its `buckets_roles`, its `access_key_scope`, and its IAM policies. @@ -105,10 +109,19 @@ can do every operation on the bucket. It can delete the bucket, change its settings, and turn off soft delete. Bucket roles, IAM policies, and `access_key_scope` cannot limit what an owner can do on its bucket. -Keep the provisioned key in your backend. Give your users a separate key that -has only the access they need. See +Keep the provisioned key in your backend, or delete it. Give your users a +separate key that has only the access they need. See [Give Users Data Access Without Bucket Admin](#give-users-data-access-without-bucket-admin). +Each Provision call creates a new owner key. If your backend manages the bucket +with the Partner API, it does not need this key. You can delete the key with +[Delete Access Key](/docs/partner-integrations/api/tigris-delete-access-key) +after Provision. The Partner API calls for the bucket continue to work, and no +access key owns the bucket. + +If your users already have the provisioned key, see +[Move Users Off the Provisioned Key](#move-users-off-the-provisioned-key). + ## How Tigris Evaluates a Request Tigris checks each S3 request in this order. It stops at the first step that @@ -169,6 +182,42 @@ provisioned `user-data`. If each of your users has a different `user_id`, use ::: +### Move Users Off the Provisioned Key + +Use this recipe when your users already have the provisioned key. You cannot +move the ownership of a bucket to a different key. You must replace the secret +of the provisioned key instead. + +With the provisioned key, a user can create more access keys. These keys +continue to work after you rotate the provisioned key. You must also delete +them. + +1. Create a key for the user with the + [Give Users Data Access Without Bucket Admin](#give-users-data-access-without-bucket-admin) + recipe. Give the new key to the user. +2. Rotate the provisioned key with + [Rotate Access Key](/docs/partner-integrations/api/tigris-rotate-access-key): + + ```http + POST /v1/providers/{provider_id}/orgs/{org_id}/key/rotate + Content-Type: application/json + + { + "user_id": "user-123", + "user_role": "Admin", + "id": "tid_provisioned_key_id" + } + ``` + +3. Keep the new secret from the response in your backend. +4. Get all of the access keys in the org with + [List Access Keys](/docs/partner-integrations/api/tigris-list-access-keys) + and `user_role: "Admin"`. +5. If a key is not in the records of your backend, delete it with + [Delete Access Key](/docs/partner-integrations/api/tigris-delete-access-key). + +The old secret and the deleted keys can continue to work for up to 15 minutes. + ### Read-Only Access to One Bucket ```http @@ -319,8 +368,12 @@ A soft-deleted bucket stays until you restore it or [delete it permanently](/docs/partner-integrations/api/tigris-purge-bucket). It counts toward storage usage until then. -A `ReadWrite` key cannot turn off soft delete. The bucket owner, `Editor` keys, -and org admin keys can turn it off. +Soft delete protects the bucket from a usual delete request. It does not stop a +key that can change bucket settings: + +- The bucket owner, `Editor` keys, and org admin keys can turn off soft delete. +- These keys can also permanently delete a soft-deleted bucket over S3. +- A `ReadWrite` key cannot do these operations. ### Change Bucket Settings for Your Users @@ -351,7 +404,8 @@ Deleting a key does not delete the buckets that it owns. :::note After you rotate or delete a key, the old secret can continue to work for up to -15 minutes. +15 minutes. After you change the bucket roles or the scope of a key, the old +access can also continue for up to 15 minutes. ::: diff --git a/docs/partner-integrations/architecture.mdx b/docs/partner-integrations/architecture.mdx index 2ca72f40..a8a8957d 100644 --- a/docs/partner-integrations/architecture.mdx +++ b/docs/partner-integrations/architecture.mdx @@ -88,7 +88,7 @@ owns the new bucket: ``` This key has full control of the bucket, including deleting it. Keep it in your -backend, and give your users a key with less access. See +backend or delete it, and give your users a key with less access. See [The Provisioned Access Key](/docs/partner-integrations/access-control#the-provisioned-access-key). **3. Create additional buckets** as needed using the Partner API or standard S3 From 40600cb0277909ecd5b7a8b118130bd8a4abd354 Mon Sep 17 00:00:00 2001 From: Adil Ansari Date: Fri, 9 Oct 2026 16:49:18 -0700 Subject: [PATCH 3/3] docs: separate bucket and object retention, and add warning callouts The soft delete page still said in three places that a deleted bucket is removed after the retention window. Split the lifecycle diagram into object and bucket flows, say that retention applies only to objects, and remove the retention claim from the delete dialog description. Put the "turn off soft delete" risk and the org admin key warning in warning callouts, and remove the Member note that repeats the recipe intro. --- docs/buckets/soft-delete.mdx | 33 +++++++++++++------- docs/partner-integrations/access-control.mdx | 15 ++++----- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/docs/buckets/soft-delete.mdx b/docs/buckets/soft-delete.mdx index a02ff315..29c5e4bd 100644 --- a/docs/buckets/soft-delete.mdx +++ b/docs/buckets/soft-delete.mdx @@ -36,10 +36,15 @@ When soft delete is enabled on a bucket: At a glance, the lifecycle looks like this: ``` - DELETE ──► soft-deleted state ──┬──► permanently removed -bucket or object (recoverable) │ (async, after retention) - │ - ◄─── restore ─────┘ + DELETE object ──► soft-deleted state ──┬──► permanently removed + (recoverable) │ (async, after retention) + │ + ◄─── restore ─────┘ + + DELETE bucket ──► soft-deleted state ──┬──► permanently removed + (recoverable) │ (only when you delete + │ it permanently) + ◄─── restore ─────┘ ``` Cleanup runs asynchronously, so objects are removed shortly after the retention @@ -67,9 +72,9 @@ delete enabled, showing **Enabled** or **Disabled** for each row. ### Retention window -The retention window controls how long deleted buckets and objects remain -recoverable. It must be between **7 and 90 days**, and defaults to **7 days** -when soft delete is first enabled. +The retention window controls how long deleted objects remain recoverable. It +does not apply to deleted buckets. It must be between **7 and 90 days**, and +defaults to **7 days** when soft delete is first enabled. ### Enabling soft delete programmatically @@ -179,8 +184,7 @@ delete and then deletes objects must wait before it relies on recovery. When you delete a bucket that has soft delete enabled, the delete confirmation dialog reflects that the action is recoverable instead of permanent. It tells -you the bucket can be restored within its retention period — and that after that -window passes, the deletion is permanent. The action button is labeled **Soft +you that you can restore the bucket. The action button is labeled **Soft Delete** rather than **Delete**, and you still confirm by typing the bucket name. @@ -746,8 +750,13 @@ earlier versions live and accessible. - Retention applies per bucket. Every soft-deleted object in the bucket uses the bucket's current retention window. -- Disabling soft delete on a bucket also ends the protection for objects that - are already soft-deleted. Tigris can remove them in a later cleanup, so do not - expect to restore them. - Changing the retention value also applies to objects that are already soft-deleted. A shorter window can remove them sooner. + +:::warning + +Do not turn off soft delete while you still need to restore deleted objects. +When soft delete is off, Tigris can remove the objects that are already +soft-deleted. You cannot restore them after that. + +::: diff --git a/docs/partner-integrations/access-control.mdx b/docs/partner-integrations/access-control.mdx index a2d13dbb..2516fdde 100644 --- a/docs/partner-integrations/access-control.mdx +++ b/docs/partner-integrations/access-control.mdx @@ -174,14 +174,6 @@ The key can read, write, and delete objects in `user-data`. It cannot delete the bucket, change bucket settings, turn off soft delete, create buckets, or create access keys. -:::note - -With `user_role: "Member"`, this call works only when `user_id` is the user that -provisioned `user-data`. If each of your users has a different `user_id`, use -`Admin`. - -::: - ### Move Users Off the Provisioned Key Use this recipe when your users already have the provisioned key. You cannot @@ -335,7 +327,12 @@ Content-Type: application/json } ``` -Keep this key in your backend. Do not give it to your users. +:::warning + +Keep this key in your backend. Do not give it to your users. The key can delete +every bucket in the org. + +::: ### Protect Buckets From Accidental Deletion