MitchDrage opened a new pull request, #14170:
URL: https://github.com/apache/cloudstack/pull/14170
## Description
Object storage credentials are provisioned per CloudStack account today:
every bucket an account owns shares one access/secret key pair, and there is no
way to rotate it.
This PR introduces per-bucket keys, a rotation mechanism, and the migration
from per-account to per-bucket keys.
Fixes #14167
Discussed on dev@: "[DISCUSS] Per-bucket credentials and key rotation for
object storage".
This PR's changes:
- Each bucket gets its own credential on the backend, so a leaked key
exposes one bucket rather than every bucket the account owns.
- Each credential holds two independent key slots, so a key can be rotated
while consumers still use the other one, and revoked when they have moved.
- Existing buckets keep working exactly as they do now. Nothing is migrated
automatically and the schema change is additive.
- An administrator moves an account onto per-bucket credentials explicitly,
per object store, then moves each bucket, then rotates the account key to
finish. Existing keys keep working throughout.
- On Ceph this is built on RGW accounts, so it needs Ceph Squid or later.
Older gateways, and providers that do not offer this, keep today's behaviour
and say so rather than half-working.
- New APIs: `rotateBucketKey`, `revokeBucketKey`, `migrateBucketCredential`,
`migrateObjectStoreAccount` and `rotateObjectStoreAccountKey`. `listBuckets`
and `listObjectStoragePools` gain fields and filters for the new state.
- New UI: a Keys tab per bucket, a Credential Scope column and filter, a
migrate action on buckets, an Object Storage tab on accounts, and a readiness
line on the object store itself.
- Details of storage backend's are only shared with the root
administrator, e.g. backend type, versions, overall-readiness for migration.
General users are only told that the backend doesn't support per-bucket
credentials.
- MinIO, Cloudian and the Simulator are untouched and report that they do
not offer per-bucket credentials.
### How it works
- Two new tables, `bucket_credential` (one dedicated backend identity per
bucket) and `bucket_credential_key` (one row per key slot, secret encrypted
with `@Encrypt`). A bucket without a `bucket_credential` row behaves exactly as
today (account-scoped key) - the schema change is additive and no data is
migrated.
- The existing `bucket.access_key`/`bucket.secret_key` columns become a
mirror of the bucket's newest active key, so every existing consumer of them
(`BucketTO`, the policy/versioning driver calls, the UI object browser) keeps
working unchanged.
- New provider-agnostic driver operations on `ObjectStoreDriver`
(`createBucketCredential`, `createBucketCredentialKey`,
`removeBucketCredentialKey`, `deleteBucketCredential`, plus the account-level
`supportsBucketCredentials` / `accountSupportsBucketCredentials` /
`migrateAccountForBucketCredentials`). `BaseObjectStoreDriverImpl` provides
"unsupported" defaults, so MinIO and Cloudian are untouched; the Simulator gets
a stateless fake so the service layer is exercisable in CI.
- New APIs: `rotateBucketKey`, `revokeBucketKey`, `migrateBucketCredential`
(user-level, bucket-scoped) and, for root/domain admins,
`migrateObjectStoreAccount` and `rotateObjectStoreAccountKey`. The latter
completes a migration: once no bucket of the account on a store still uses the
account key, it issues CloudStack a fresh account key and revokes the old one,
so a shared key copied before the migration no longer opens every bucket. It is
refused while legacy buckets remain, and is never run automatically.
`listObjectStoragePools` accepts `accountid` and then reports, per store,
whether the store supports per-bucket credentials at all, whether the account
is migrated and how many buckets still use the account key. `listBuckets`
responses gain `credentialscope` (`bucket`/`account`) and a `keys` list, and
accept `credentialscope` as a filter so the buckets still using the account
credential can be listed directly; secret keys are marked sensitive for API-log
redaction.
- Global setting `object.storage.per.bucket.credentials` (default `true`)
applies **only** when an account is first given an identity on an object store.
It never changes an account that already has one:
| The account on that store | `true` (default) | `false` |
|---|---|---|
| has no identity yet | is created so that each of its buckets gets its own
credential | is created sharing one credential across its buckets, as in
earlier releases |
| shares one credential | unchanged - moving to per-bucket credentials is
always an explicit admin action | unchanged |
| has been migrated | each new bucket gets its own credential | the same:
buckets keep getting their own credentials. Sharing one here would mean writing
the account's root key onto a bucket, where every user of the account can read
it, which undoes the migration |
- To drive an account's object storage, CloudStack has to keep a credential
of its own for that account on each store: the RGW account id and the account's
root key. Account details are the natural place to keep it, but they are handed
back to API callers as `accountdetails` in `listAccounts` responses, and this
credential is CloudStack's rather than the account's. Storing it there as-is
would therefore publish it, which would defeat the migration: the root key
opens every bucket the account owns, and the point of rotating it at the end of
the migration is that nobody else holds it.
So these rows are named with a reserved prefix, `objectstore-`, held as a
constant (`ObjectStore.ACCOUNT_DETAIL_PREFIX`) so that the driver writing them
and the API layer agree on it, and `ApiDBUtils.getAccountDetails` drops/hides
rows carrying that prefix before the response is built. The secret is
additionally encrypted with `DBEncryptionUtil`. The namespace is the only thing
removed; every other account detail, including those existing providers already
store, is returned exactly as before.
- UI: a Keys tab per bucket, a Credential Scope column and filter, a migrate
action on buckets, an Object Storage tab on accounts, and a readiness line on
the object store itself. Each screen is listed under "What changes in the UI"
below.
### Points a reviewer may want to ponder on
These are deliberate choices I've made along the way:
- I've made all references to versions (API's, etc) to the 24.0.0 version
based on the vote to drop the '4'. Please also let me know if this need to go
in a version other than 24.
- **Ceph Squid or later only.** Per-bucket identities on plain RGW users
fragment object ownership, which breaks public bucket policies and leaves the
old key with access to old objects. RGW accounts fix the cause but arrived in
Squid. Older gateways keep today's behaviour.
- **Account migration cannot be undone.** Adopting a user into an RGW
account is permanent at the backend, so the action is explicit, admin-only and
warned about, and CloudStack never does it implicitly.
- **The global setting changed meaning.** It now decides how an account is
set up the first time it uses a store, and no longer forces a migrated account
back onto a shared credential. Without that, turning the setting off would
quietly undo a completed migration.
- **Shared UI code is touched.** List sections gain an `optionalColumns`
option, and a details list can now vary per record. Both are additive and inert
elsewhere, but they are shared files.
- **Account details are filtered in a shared response path.**
`ApiDBUtils.getAccountDetails` now strips the reserved namespace this feature
writes. Nothing else is removed, but every account response goes through that
method.
- **Secrets are still returned by the API.** A bucket's keys appear in
`listBuckets` for callers who pass its access check, as the account-scoped key
does today. Marking them sensitive keeps them out of logs; hiding them from the
response would break the object browser and existing automation.
- **A bucket's own identity policy grants `s3:*` on that bucket**, which
includes changing the bucket's policy. It is confined to the bucket the key
belongs to, but a narrower action list is arguable.
- **An interrupted rotation can leave an untracked key**, and rotation
reports it rather than deleting it, because CloudStack did not create it and it
may be an operator's own.
- **`listBuckets` gained a per-bucket gateway call** for buckets still on
the account credential of a migrated account, to decide whether the migrate
action applies. Accounts with no migration record answer from the database
alone.
- **An existing parameter starts working.** `listBuckets`'s
`objectstorageid` has been accepted and ignored since 4.19.0; anything relying
on it returning every bucket will see a filtered list.
- **Why a store cannot support the feature is shown only to root admins**,
on the object store itself, because the causes name the gateway's own admin
credential and its capabilities. A domain admin refused a migration is told to
contact their platform administrator instead.
- **The driver contract gained an optional explanation**
(`bucketCredentialsUnsupportedReason`). It is a default method, so no provider
has to implement it, but adding a method to `ObjectStoreEntity` means an
incremental build has to rebuild the implementing module.
<details>
<summary><b>UI changes people will see</b></summary>
**Buckets list**
- A **Credential Scope** column, reading "Per-Bucket" or "Account". It stays
hidden until the viewer can see at least one bucket with its own credential,
then appears on its own; after that the column picker decides.
- A **Credential Scope** filter in the search panel, with the same two
values.
**A bucket's Details tab**
- **Credential Scope** is shown.
- Once a bucket has its own credential, the access key and secret are no
longer listed here. They move to the Keys tab, so there is one place to find
them. A bucket still on the account credential is unchanged.
**A bucket's Keys tab (new, only on buckets with their own credential;
viewable/usable by the bucket owner, admins and domain admins)**
- Two rows, one per key slot, each showing its state, its access key with a
copy button, its secret masked behind a reveal, and when it was created.
- Per row: rotate an active slot, create a key in an empty slot, or revoke.
Revoke is disabled on the last active key, with a tooltip saying why.
- Each action confirms first, in the standard dialog.
**A bucket's actions**
- **Migrate to Per-Bucket Credential (new, viewable/usable by the bucket
owner, admins and domain admins)**, on buckets still using the account
credential. It only appears once the owning account has been set up on that
store, so it cannot be started in a state that would be refused.
**An account's Object Storage tab (new, viewable/usable by admins and domain
admins)**
- One card per object store, supported stores first.
- While a store has work outstanding: a four-stage progress display (account
key, migrate buckets, rotate account key, complete), the count of buckets still
on the account credential, a **Migrate Account to Per-Bucket Credentials**
button, a **Show Buckets** link filtered to that store's remaining buckets,
and, once every bucket has been moved, a warning that the account key still
opens everything with a **Rotate Account Key** button.
- When a store is finished: a single line saying per-bucket credentials are
in use, with no stages and no buttons.
- When a store cannot support the feature: a plain notice saying so and to
contact the platform administrator. No backend or version or other information
is named in order to hide platform internals from users.
- The explanation of the migration only appears while some store still has a
stage left. After is has been migrated, the steps to migrate are replaced by a
note saying that it's been migrated.
**Infrastructure, Object Storage, a store's Details tab (root admins only)**
- **Per-Bucket Credentials: Ready** or **Not ready**.
- When not ready, **To Resolve Before Migration** naming the cause: a
missing `info` or `accounts` capability on the store's admin credential, a
gateway that predates RGW accounts, a provider that does not offer the feature,
or an unreachable admin API.
**Events**
- Key rotation, key revocation, bucket credential migration, account
migration and account key rotation each raise their own event, visible on the
bucket's and account's Events tabs.
</details>
<details>
<summary><b>Changes to shared UI code, and why they are needed</b></summary>
Four files outside this feature are touched. Each is additive and gated so
they don't apply to any other section, but they are shared so they are listed
here for completeness.
| File | Change | Effect elsewhere |
|---|---|---|
| `views/AutogenView.vue` | a section may declare `optionalColumns`: columns
that stay unticked until a condition first holds, then are ticked once and left
to the viewer thereafter | none unless a section declares it; the method
returns on its first line when the route has no such declaration, and only the
buckets section has one |
| `config/router.js` | carries that declaration into the route metadata,
beside the existing `columns` | none; the property is only copied when present |
| `components/view/DetailsTab.vue` | a `details()` function is now called
with the record: `details(this.resource)` | none; twelve of the thirteen
existing definitions take no arguments and ignore it |
| `components/view/ListView.vue` | renders a `credentialscope` cell through
the translation table, so it reads "Per-Bucket" rather than `bucket` | none;
the branch is keyed on a column name no other section uses |
| `components/view/SearchView.vue` | `credentialscope` added to the list of
filters drawn as a dropdown, with its two options | none; both additions are
keyed on that filter name |
Why they are needed rather than done in the section config:
- **The new column in the bucket list is hidden until it's needed** - This
cannot be expressed in a section config today: the column list is fixed, and
every column in it is selected by default. A column stating that every bucket
has "Account" credentials on every row until the first migration is noisy. I
decided that showing it when one or more of the buckets has per-bucket
credentials is cleaner and more useful. It is not specific to this feature, so
it was added as a general option rather than a special case for buckets.
- **A bucket's Details tab drops the key fields once that bucket has its own
credential** - This is likewise not expressible today: `details` may be a
function, but it was called with no arguments, so it could vary per route and
not per record, and this needs to vary for one bucket and not another. Passing
the record is backwards compatible, since an existing zero-argument function
ignores it.
- **The Credential Scope search filter is a dropdown of the two values
rather than a free-text box** - This cannot be expressed either: a section
names its filters, but whether one draws as a text box or a dropdown is decided
by a list of names inside `SearchView.vue`.
- **The column reads "Per-Bucket" and "Account" rather than the raw values**
- This one could have been done in the section config: a column may be given as
an object carrying a render function, as `plugin/quota.js` does. I put it in
`ListView.vue` because that mechanism replaces the row's value with the
rendered text, so the record would then carry "Per-Bucket" where other code
reads `bucket`. It is a trade-off, and I will move it if reviewers prefer the
config route.
</details>
<details>
<summary><b>Ceph RGW implementation - requires Ceph Squid (v19) or
later</b></summary>
On plain RGW users (the existing CloudStack approach to RGW credentals),
giving an existing bucket its own identity cannot move the ownership of the
objects already in it. The admin REST API can relink a bucket to another owner,
but only the `radosgw-admin bucket chown` command rewrites the objects inside.
CloudStack accesses the gateway over REST and S3 with no shell access to it, so
it has no way to migrate ownership. That leaves migrated buckets with split
ownership: public bucket policies would 403 on the old objects and the old key
would retain access to them. RGW **accounts** remove the cause: the account
owns every bucket and object regardless of which identity wrote it. So the Ceph
driver maps:
- one RGW account per CloudStack account **per object store** (an account
can be migrated on one store and still be legacy on another; the state is kept
per store in `account_details`), whose root user is the existing account-UUID
RGW user;
- one IAM user per bucket, with an inline policy scoped to that bucket and
its access keys mapping onto the two key slots;
- a bucket-policy statement naming the IAM user, so it can also reach
objects the account root wrote before the account migration (identity policies
alone cannot; verified on 20.2.4). CloudStack's public/private bucket policy is
regenerated together with that statement so the two features compose.
CloudStack's records can disagree with the gateway: a database restored from
another environment, or a store re-pointed at a different cluster, leaves
CloudStack believing an account was migrated when that cluster has never heard
of it. The driver therefore asks the gateway whether the account's user really
is an account root there before any account-level operation.
An account counts as unmigrated only when the gateway answers and says so.
An unreachable admin API, a 403 while an admin credential's capabilities are
being changed, or a probe that flaps during a rolling gateway upgrade all leave
the migration record standing, since migration is permanent at the backend.
Treating those as unmigrated would drop the account back to its shared
credential and write the account root key onto its next bucket. Where a
per-bucket credential genuinely cannot be issued, bucket creation fails with
the gateway's error.
Existing accounts are **never migrated implicitly**: adopting a user into an
RGW account is permanent, so it takes an explicit `migrateObjectStoreAccount`
by a root or domain admin. Until then the account keeps today's behaviour, new
buckets included. A brand-new account on a store that supports the feature is
set up for it directly.
The Ceph plugin gains a dependency on `aws-java-sdk-iam` (already used by
the Cloudian driver) and a small SigV2 client for the `/admin/account` and
`/admin/info` endpoints that radosgw-admin4j does not cover.
Verified against three RGW releases (single-container clusters, same script):
| | Reef 18.2.7 | Squid 19.2.3 | Tentacle 20.2.4 |
|---|---|---|---|
| `/admin/info` | 200 | 200 | 200 |
| `accounts` capability | cannot be granted | granted | granted |
| `POST /admin/account` | 405 | works; duplicate → 409
`AccountAlreadyExists` | same |
| `GET /admin/account` | 405 | 403 whatever capabilities are granted, as
does DELETE | 404/200 as expected |
| adoption, IAM users/keys/policies, bucket-policy grant | n/a | identical
to Tentacle | verified |
Reef cannot support this at all: its account endpoint answers 405, and
`radosgw-admin caps add` refuses the `accounts` capability, so no credential on
it could hold what the endpoint requires. Squid is the floor.
The driver never looks an account up or deletes one, since Squid answers 403
to `GET` and `DELETE /admin/account` whatever capabilities the credential
holds. The account id is derived from the CloudStack account UUID (`RGW`
followed by 17 digits), so the same account always produces the same id, and
`createAccount` treats a 409 as success.
An RGW account root is not a listed IAM user: it never appears in
`ListUsers`, and IAM calls that name it are rejected. `rotateAccountKey`
manages its keys with the no-`UserName` form of `CreateAccessKey` and
`DeleteAccessKey`, authenticated as the root itself, which is how an AWS
account's own root credentials work.
The plugin registers its own signer (`RgwIamSigner`) for IAM calls. The AWS
Java SDK signs the request before the HTTP layer attaches the form
`Content-Type`, so that header never reaches the signature, and Tentacle
answers 403 to a request whose content type was unsigned.
CloudStack works out whether a store supports this by asking the gateway:
`/admin/info` must answer, the account endpoint must not return 405, and the
store's admin credential must hold **both** `info` and `accounts` (with
`accounts` alone the gateway answers 403 on `/admin/info`). The three causes
are indistinguishable from outside, so a root admin refused a migration is told
which one applies, for example "the object store's admin credential is missing
the 'info' capability". The probe re-checks automatically, so upgrading a
gateway is picked up with no CloudStack configuration change.
</details>
### When things are interrupted or the backend changes
Every step that touches the gateway is repeatable. CloudStack records a
migration once the gateway work is done, so a management server that dies
mid-way leaves the account working. Running the same action again finishes the
job.
- **Interrupted account migration.** The RGW account is created (idempotent
on 409), the user is adopted, and only then is the migration recorded. Fail
before that record and CloudStack still sees a legacy account: its buckets keep
working on the key they already had, which adoption preserves, and the
bucket-level migrate action stays hidden. Running `migrateObjectStoreAccount`
again re-adopts harmlessly and records the result. Verified by deleting those
records after a real migration and retrying.
- **Interrupted bucket credential.** The IAM user, its policy and its key
are created before anything is written down. A retry re-uses the existing user
and clears any access key CloudStack is not tracking on it, so a half-finished
attempt cannot leave a key nobody knows about. That user belongs to the bucket
alone, which is what makes clearing it safe.
- **Interrupted account key rotation.** The new key is created, recorded,
and only then is the old one removed, so CloudStack always holds a key that
works. If recording fails, the new key is deleted again and the old one stays
in force.
- **What a bucket's users can see while it is being created.** Creating a
bucket is an asynchronous job, and the bucket row is listable from the moment
it is allocated. The driver therefore never writes the account key onto the row
of a bucket whose account is set up for per-bucket credentials, so the
account's root key does not pass through a place every user of the account can
read, and responses withhold a bucket's keys until it reaches the `Created`
state.
- **Logs.** The admin API client never logs a user record, since those carry
the user's secret keys; only the method, path and status are traced.
- **Two operations at once.** Account migration and account key rotation
take a lock for that account on that store, so two administrators, or two
management servers, cannot both issue a key and then revoke each other's. The
gateway calls made while that lock is held are bounded — a 20 second socket
timeout, one retry, and a 30 second ceiling on each call — so an unresponsive
gateway releases the lock in about a minute. The AWS SDK's defaults would have
allowed ten.
- **The gateway changes underneath.** A support probe that starts failing,
an admin credential that loses the `accounts` capability, or an unreachable
admin API cannot un-migrate an account: its record stands and its buckets keep
getting their own credentials. Operations that genuinely need the gateway fail
with the gateway's error rather than falling back to the shared credential.
### Incidental fix
`listBuckets` has accepted an `objectstorageid` parameter since 4.19.0 that
seems to have never worked. It declared `entityType =
StoragePoolResponse.class` (primary storage), so the UUID could never resolve
and the call failed with "entity does not exist"; and
`searchForBucketsInternal` never read the parameter at all, so once the UUID
did resolve every bucket was still returned. Both are fixed here: the entity
type is corrected to `ObjectStoreResponse.class`, and the search now applies
the filter. The account's Object Storage tab relies on it for its "Show
Buckets" link (root admin only, matching the parameter's existing
authorization, which is unchanged).
I haven't found an issue raised against this, but I needed this fixed for my
tests to work so I have fixed it here.
### Documentation
Operator documentation is being raised separately against the documentation
repository, and will be linked here once it is up. It covers the capabilities
the store's admin credential needs (`info` and `accounts`), what account
migration does and that it cannot be undone, and the stages an administrator
works through.
### Open question: how long should the account's Object Storage tab existin
the Account page?
The tab, and the migration it drives, exist only because deployments have
accounts that predate per-bucket credentials, and because some object storage
cannot support them. Both shrink over time. Once the providers CloudStack
supports offer per-bucket credentials, and versions that cannot are out of
support, there is a decision to make: deprecate support for the backends and
versions that cannot do this (Ceph before Squid is already end of life), at
which point new accounts are always per-bucket, the migration becomes a one-off
upgrade concern, and this tab can go. One suggestion I could make is to add the
Object Storage provider and version into the anonomised usage telemetry service
that has been discussed recently. That way, when we have a view on how much
Ceph Reef is out there, we can make an informed decision on when to stop
supporting it.
### Known limitations / follow-ups
- `bucket.secret_key` still holds the mirrored active secret in clear, as it
does today; only the new `bucket_credential_key` table is encrypted. Encrypting
the existing column would mean migrating the rows already in it, and a
rolling-upgrade window in which management servers disagree about the format,
so changing it is left out of this PR. It would also not change what the API
hands out, since that secret is already returned to callers who can see the
bucket and changing that may be a breaking change.
- Existing provider account details, such as the account-scoped keys the
Ceph and MinIO drivers have always written, are left exactly as they are; only
the details this feature adds use the reserved namespace.
- A rotation interrupted between issuing a key and recording it can leave a
key on the account's root user that CloudStack does not track, and which still
opens every bucket of the account. Rotation removes only the key it issued, and
logs a warning naming any others rather than deleting credentials it did not
create, since one of them may be an operator's own. Reviewing them is a manual
step on the gateway.
- Bulk migration of buckets and MinIO/Cloudian implementations are
follow-ups. I don't have familiarity with those software packages nor an
environment to test them on.
- Objects written before an account is migrated stay owned by its
pre-migration user, and the admin API cannot change that. CloudStack covers
them by granting the bucket's credential in the bucket policy as well as its
identity policy. A bucket policy written straight to the gateway drops that
grant, leaving the credential blind to everything from before the migration;
changing the bucket's public/private setting in CloudStack restores it.
## Types of changes
- [ ] Breaking change
- [x] New feature
- [ ] Bug fix
- [ ] Enhancement
- [ ] Cleanup
## Feature/Enhancement Scale or Bug Severity
- [x] Major
## Trying it out
Documentation is being raised separately, so here is enough to exercise the
feature.
**What you need.** A Ceph cluster running Squid (v19) or later with RGW. A
single container is enough. The RGW admin user whose keys you register the
object store with needs two capabilities, and it will look unsupported without
both:
```
radosgw-admin caps add --uid=<admin uid> --caps="info=*;accounts=*"
```
Register it in CloudStack as a Ceph object store in the normal way.
Infrastructure -> Object Storage -> the store's Details tab reports whether it
can provide per-bucket credentials, and what to resolve if it cannot. That
readiness information is root admin only.
**A new account (the short path).** An account that has never used this
store is set up for per-bucket credentials on its first bucket, with no
migration step.
1. Create a bucket in that account. Storage -> Buckets shows Credential
Scope "Per-Bucket".
2. Open the bucket -> Keys tab. Slot 1 holds a key, slot 2 is empty.
3. Use the slot 1 key with any S3 client. It reaches this bucket and is
denied on another bucket in the same account.
4. Rotate the key. Slot 2 fills with a different key. Both keys now work.
5. Revoke slot 1. That key is rejected by the gateway; slot 2 keeps working.
6. Try to revoke slot 2. It is refused, because a credential keeps at least
one active key.
7. Delete the bucket. `radosgw-admin user list` shows the bucket's IAM user
is gone.
**An existing account (the migration path).** This is the path that matters
for upgrades, and it is what the account's Object Storage tab walks an
administrator through. Use an account that already has buckets on the store.
1. Account -> Object Storage tab. It shows the store, the account's state on
it, and the next step.
2. **Migrate the account.** This adopts the account's existing RGW user as
the root of a new RGW account at the gateway. It cannot be undone (the UI warns
about this). The existing keys keep working afterwards: check with an S3 client
before and after. Existing buckets still read "Account" for Credential Scope.
3. **Give each bucket its own credential.** "Show Buckets" from the tab
lists the account's buckets on this store that still share the account key.
Each has a Migrate to Per-Bucket Credential action. A migrated bucket's own key
can read objects written before the migration.
4. **Rotate the account key.** Offered once no bucket is left on the account
key, and refused before that with the list of buckets still using it.
Afterwards the original account key is rejected by the gateway, and the
per-bucket keys are unaffected.
**An unsupported store.** Register a Reef (18.x) cluster, or drop the
`accounts` capability from the admin user of a Squid one. The account's Object
Storage tab says the store cannot be migrated. As root admin, the store's
Details tab names the cause; a domain admin is told to contact their platform
administrator.
**Checking at the gateway.**
```
radosgw-admin account list # one account per
migrated CS account
radosgw-admin user list # an IAM user per
migrated bucket
radosgw-admin bucket stats --bucket=<name> # owner is the account id
```
**Turning it off.** `object.storage.per.bucket.credentials=false` makes new
accounts use the shared credential as before. Accounts already migrated stay
migrated.
## How Has This Been Tested?
- Unit tests: `CephObjectStoreDriverImplTest` (37) and
`BucketApiServiceImplTest` (26), plus a new `ObjectStoreAccountDetailTest` (3)
and four cases added to `QueryManagerImplTest` for the bucket filters.
- RGW behaviour verified with scripted checks against single-container Ceph
18.2.7 (Reef), 19.2.3 (Squid) and 20.2.4 (Tentacle) clusters: account probe,
adoption keeps legacy keys working, IAM user/policy/key lifecycle, policy
scoping, public-policy coverage of IAM-written objects, bucket-policy grant for
pre-adoption objects, revocation, no two-key limit.
- End-to-end through a management server (simulator profile) with the Squid
and Tentacle containers registered as Ceph object stores:
- Tentacle, new account: create bucket -> dedicated credential,
account-owned bucket, IAM user with one key, key isolated to its bucket; rotate
into slot 2; revoke slot 1 (RGW rejects the old key); last-key revoke refused;
rotate back into slot 1; delete bucket removes the IAM user. 19/19.
- Squid, legacy account (seeded with
`object.storage.per.bucket.credentials=false`): `migrateObjectStoreAccount`
adopts the RGW user as account root with its keys still valid;
`migrateBucketCredential` gives the existing bucket a dedicated key that reads
the pre-migration object; then the same new-bucket sequence as above. 25/25.
- Ceph 20.2.2, legacy account with a pre-migration object: account
migration, bucket migration (bucket key reads the old object),
`rotateObjectStoreAccountKey` refused while a legacy bucket remains, then
succeeds once none remain - old account key rejected by RGW, root user holds
one new key, per-bucket keys unaffected, new buckets created with the rotated
key are per-bucket, a second rotation succeeds. Per-store scoping verified: the
same account reports legacy on the other two stores.
- Marvin: a `test_02_bucket_key_rotation` case added to `test_bucket.py`,
with the helpers it needs added to `marvin/lib/base.py`. It creates a Simulator
object store, then checks that a new bucket has one active key slot mirrored
onto the bucket, that rotation fills the free slot with a different key and
moves the mirror, that revoking leaves the other slot active, and that revoking
the last active key is refused. Run against a management server on the
simulator profile: both cases in the file pass.
- That file's existing `test_01_create_bucket` was failing before this PR,
since `createBucket` has a required `quota` parameter (from #10017 ?) that
wasn't filled. The one-line fix is included here, as our own case was written
from the same pattern.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]