Skip to content

feat(provisioning): declare default bucket encryption - #285

Open
shurkus wants to merge 2 commits into
rustfs:mainfrom
shurkus:feat/bucket-encryption
Open

shurkus wants to merge 2 commits into
rustfs:mainfrom
shurkus:feat/bucket-encryption

Conversation

@shurkus

@shurkus shurkus commented Oct 19, 2026

Copy link
Copy Markdown

Closes #284

Depends on #282 (this branch is stacked on it); only the last commit, 701d2df, belongs to this PR.

What

spec.buckets[].encryption declares the bucket's default server-side encryption:

buckets:
  - name: main
    encryption:
      mode: SSE-S3          # PutBucketEncryption AES256
  - name: archive
    encryption:
      mode: SSE-KMS         # aws:kms
      kmsKeyId: archive-key # optional; RustFS default KMS key when omitted
  - name: legacy
    encryption:
      state: Absent         # DeleteBucketEncryption

Ownership follows objectLockConfiguration and lifecycle:

  • If encryption is omitted, the operator makes no encryption calls.
  • If the live configuration matches the spec, it is adopted without a write.
  • A different live configuration is replaced, or removed for Absent, only while its hash matches encryptionLastAppliedHash. Otherwise the bucket reports BucketEncryptionConflict and is left untouched.
  • encryptionDesiredHash and encryptionLastAppliedHash are recorded in the bucket status.

Validation:

  • CEL: Present requires mode; Absent forbids mode and kmsKeyId; kmsKeyId requires SSE-KMS.
  • kmsKeyId is 1–1024 characters and must match ^[A-Za-z0-9._:/+=@-]+$.
  • SSE-KMS is rejected up front unless spec.encryption.enabled. Without it, RustFS answers 500 KMS service not initialized and the bucket would stay Pending.

Default encryption only applies to objects written after it is set. Existing objects are not re-encrypted or rewritten.

RustFS behaviour handled (1.0.2-preview.2)

  • GET ?encryption without a configuration returns 400 ServerSideEncryptionConfigurationNotFoundError, not 404. The client treats it as absent.
  • The GET body has no XML namespace. The parser is strict: one Rule, known elements only, entity references rejected.
  • PUT with aws:kms and no key ID stores the default KMS key. Ownership is therefore the hash of the configuration read back after the PUT, and SSE-KMS without kmsKeyId matches any live key.
  • DELETE ?encryption is idempotent (204).

Changes

  • rustfs-admin: BucketEncryptionConfiguration with XML encode and parse, plus get_bucket_encryption, put_bucket_encryption and delete_bucket_encryption.
  • API types: BucketEncryptionSpec, two status hashes, and the reasons BucketEncryptionApplyFailed and BucketEncryptionConflict.
  • Reconcile: reconcile_bucket_encryption runs after the bucket policy and before lifecycle.
  • Regenerated Tenant CRD, OpenAPI and console types.
  • User guide (en and zh-CN), chart README, CHANGELOG.

Testing

  • make pre-commit: fmt, clippy, unit tests, e2e check, console test, lint, build and format.
  • New unit tests cover:
    • XML round trip, including the shape RustFS returns;
    • parser rejection cases;
    • CRD schema;
    • spec validation before any call;
    • reconcile against a mock:
      • omitted encryption makes no calls;
      • adopt without a write;
      • unowned configuration is neither replaced nor removed;
      • owned configuration follows a spec change to SSE-KMS (RustFS default key filled in);
      • Absent removes only an owned configuration;
      • a changed owned configuration is a conflict;
      • SSE-KMS requires tenant KMS;
      • the update is verified by reading it back after a success response.
  • Live on RustFS 1.0.2-preview.2 with this build:
    • apply on a bucket without a configuration;
    • adopt a pre-set AES256 with no PUT;
    • a hand-made change (BucketKeyEnabled) gives BucketEncryptionConflict and is not overwritten;
    • restoring the configuration returns the bucket to Ready;
    • state: Absent removes the configuration.

`spec.tiers` registers S3, RustFS and MinIO remote tiers through the
RustFS tier admin API, so bucket lifecycle transitions can reference a
tier managed by the same Tenant. Tiers are reconciled before buckets.

Credentials come from `tiers[].credsSecret` (`accesskey`/`secretkey`).
RustFS never returns a tier secret key, so the applied Secret
resourceVersion is recorded in `status.provisioning.tiers`; a Secret
change, a different Secret, or an access key listed by RustFS that
differs from the Secret updates the credentials with the edit API.

Type, endpoint, bucket, prefix and region cannot change on a registered
tier: a live tier with the same name and other settings is reported as
`TierConflict` without writes, and a matching one is adopted. RustFS
rejects a remote prefix that already holds objects with 409; that is
reported as `TierConflict` too, and `force: true` registers the tier
anyway. Removed tiers are retained, like other provisioned items.

Closes rustfs#280
Buckets accept `encryption` with `mode: SSE-S3` (S3 `AES256`) or
`mode: SSE-KMS` (`aws:kms`, optional `kmsKeyId`), and `state: Absent`
to remove the configuration. Ownership follows objectLockConfiguration
and lifecycle: omission makes no encryption calls, a matching live
configuration is adopted without a write, and a different one is
replaced or removed only while its hash matches the configuration last
recorded in status; otherwise the bucket reports
BucketEncryptionConflict.

SSE-KMS requires `spec.encryption.enabled`; without it RustFS answers
PutBucketEncryption with 500 "KMS service not initialized", which would
otherwise keep the bucket Pending forever. RustFS records its default
KMS key when none is sent, so ownership is the hash of the verified
live configuration, and SSE-KMS without `kmsKeyId` matches any key.

RustFS answers GetBucketEncryption without a configuration with 400
ServerSideEncryptionConfigurationNotFoundError rather than 404; the
client treats that code as absent.

Closes rustfs#284
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: default bucket encryption in the Tenant CRD

1 participant