#18716·kops

Deprecate and remove cloud storage ACL support

Author: hakmanCreated Aug 18, 2026Updated Aug 19, 2026
Labelskind/cleanup

Summary

kOps still emits object ACLs through KOPS_STATE_S3_ACL, the public-read fallback for service account issuer discovery, GCS state-store writes, and GoogleCloudBucketAcl. This issue proposes deprecation in 1.37, restriction and removal of user-facing ACL behavior in 1.38, and final plumbing removal in 1.39. kOps will stop managing ACLs; it will not audit or repair pre-existing grants on objects it does not rewrite, and bucket owners must migrate equivalent access before disabling ACLs.

Context

AWS recommends S3 Object Ownership with BucketOwnerEnforced, the default for new buckets since April 2023. GCP recommends uniform bucket-level access, which becomes irreversible after 90 days. Bucket policy and IAM are centrally auditable, while write-time ACL grants drift and can conflict with bucket ownership.

The non-obvious AWS case is #15296: a bucket can be public by policy but still produce unreadable discovery objects when ACLs remain enabled and a different account owns the objects. Migration must therefore address both public access and ownership. bucket-owner-full-control remains temporarily supported because kOps currently documents it for cross-account state stores.

The non-obvious GCE case is that write-time object ACLs can still be load-bearing on non-UBLA state buckets: kOps grants the project default compute service account READER on each object it writes. This is the only read grant nodes get when the cluster runs on that same account, because the model builder historically granted nothing for cluster-level service accounts. When the cluster uses a custom cluster-level service account the ACL grants the wrong identity and is dead weight, and clusters using per-instance-group service accounts already receive declarative bucket IAM. GCS discovery stores exist too: they work when the bucket is public by IAM, while the non-public fallback path currently fails with an unhelpful error.

None of this affects running clusters: the ACL code is wired only into the kops CLI, and nodeup, kops-controller, and channels do not write through it.

History

  • GCS bucket-policy-only support and the GoogleCloudBucketAcl warning landed in #8493 and #8747.
  • Discovery and file-repository ACL use was reduced in #14788, #15368, and #15726.
  • User-facing failures on ACL-disabled buckets and the resulting Terraform fixes (2023-2024) are tracked in #15353, #15454, #16818, #16828, and #16830. Only #15296 (2023) remains open.

General plan

Release Change
1.37 Warn once per command and bucket when an ACL is emitted or deprecated ACL configuration is used. Update documentation and release notes.
1.38 Add a breaking-changes entry covering required migration before upgrade. All new failures are fail-fast errors before any mutation; no configuration silently changes meaning.
1.39 Remove the remaining internal VFS and managed-file ACL plumbing. Bucket-configuration checks introduced in 1.38 are independent of that plumbing and stay.

AWS specific

Plan

Release Change
1.37 Deprecate KOPS_STATE_S3_ACL and warn when the discovery public-read fallback is used. Perform best-effort ownership checks on both discovery and state-store buckets without adding required IAM permissions; treat denied or unsupported checks as unknown.
1.38 Remove the discovery public-read fallback and require discovery stores to use bucket-policy access. Reject public-granting KOPS_STATE_S3_ACL values in both direct writes and Terraform output; retain deprecated bucket-owner-full-control in both, for cross-account compatibility. Never issue AWS-only policy or ownership calls to custom-endpoint stores (discriminated by S3_ENDPOINT, not the URL scheme).
1.39 Error if KOPS_STATE_S3_ACL is still set (not silently ignore it, since dropping bucket-owner-full-control on an ACL-enabled cross-account bucket would silently change object ownership) and remove ACL Terraform rendering.

Migration

  • Discovery using public-read: add a narrowly scoped public bucket policy, verify anonymous issuer and JWKS GETs, then enable BucketOwnerEnforced; use a separate discovery bucket when Block Public Access protects the state store.
  • KOPS_STATE_S3_ACL: replace public grants with bucket policy before 1.38; users of bucket-owner-full-control should enable BucketOwnerEnforced and unset the variable before 1.39.
  • S3-compatible or custom endpoints: verify state access without object ACLs and configure the provider's bucket-level mechanism before 1.38.

GCE specific

Plan

Release Change
1.37 Deprecate GCS object ACL writes and GoogleCloudBucketAcl, warning when either is used. Warn on every state write to a non-UBLA GCS bucket regardless of cloud provider, so gs:// state stores of non-GCE clusters get the same warning cycle. Update docs/getting_started/gce.md to create state buckets with --uniform-bucket-level-access. Use UniformBucketLevelAccess, not the deprecated BucketPolicyOnly field.
1.38 Remove GCS object ACL writes and GCS ACL Terraform rendering. Fail with an instructive error only where the write-time ACL is load-bearing: a non-UBLA bucket and no IAM grant for the service account the nodes run as; warn otherwise, since forcing the irreversible UBLA flip on clusters that never depended on the ACL (per-instance-group service accounts, default-object-ACL buckets) is broader than the deprecation needs. Give non-public gs:// discovery stores the same instructive validation error as AWS. Turn GoogleCloudBucketAcl into a hard error rather than unregistering it, because unknown feature flags are otherwise silently ignored. Preserve the per-instance-group IAM work currently built by StorageAclBuilder; remove only its ACL branch and rename it.
1.39 Remove the GoogleCloudBucketAcl flag and the GCE ACL tasks; otherwise only the internal cleanup in the general plan.

Migration

  • Non-UBLA state buckets using the project default compute service account: this is the load-bearing case. Preferred migration is per-instance-group service accounts, noting the cost: nodes change identity, requiring a rolling update and re-granting any external IAM held by the old account. The stopgap is granting the shared account bucket IAM (roles/storage.objectViewer, plus roles/storage.objectAdmin where it writes etcd backups), understanding that a shared account gives worker nodes the same access as the control plane. Grant IAM to the account the VMs actually run as, which may differ from the account the old ACLs named. Then enable UBLA and verify node bootstrap and an etcd backup after the flip, since UBLA immediately voids all existing bucket, object, and default object ACLs.
  • Non-UBLA state buckets using per-instance-group service accounts: existing IAM grants are already declarative; verify them and enable UBLA.
  • Terraform-managed ACLs: remove ACL resources from Terraform state before enabling UBLA; enabling UBLA first can prevent Terraform from deleting those resources.
  • GoogleCloudBucketAcl: move to per-instance-group GCE service accounts before 1.38, with the same node-identity cost as above.

/kind cleanup