Using HeadBucket for backups is dangerous in multi-tenant S3 buckets#1717
Using HeadBucket for backups is dangerous in multi-tenant S3 buckets#1717DimitriosLisenko wants to merge 1 commit into
Conversation
|
This PR is required for HeadBucket to be removed from the backup flow, and also for the operator to stop using HeadBucket. The operator work is currently at percona/percona-xtradb-cluster-operator#2326 - are the same people able to review this item as well? |
|
@satya-bodapati could you please take a look when you have the chance? |
|
I tested this against a few IAM shapes against MinIO before pulling it into our 8.4 backport. Full reproduction and tests are on PXB-3654. The first one is the push-only uploader: a user with The delete-only retention case is the same story. The prefix-scoped tenant is more subtle. Their IAM grants To be fair: the case your PR is built around, the multi-tenant tenant with prefix-conditioned I have a candidate fix in commit #4 of PXB-3654-xbcloud-pr1717-regression-tests that keeps your A couple of things I have not tested. GCS goes through the same |
…1717 https://perconadev.atlassian.net/browse/PXB-3654 Adds permission_multi_tenant.sh (R4), which exercises the exact scenario PR percona#1717 ("Using HeadBucket for backups is dangerous in multi-tenant S3 buckets") was filed to enable: a tenant of a shared S3 bucket whose IAM policy is s3:ListBucket on bucket Condition: StringLike s3:prefix = tenant1/* s3:GetObject / s3:PutObject / s3:DeleteObject on bucket/tenant1/* i.e. prefix-conditioned ListBucket only (so HeadBucket is denied because it carries no s3:prefix in the request) plus full object permissions inside the tenant's own prefix. backup_name is chosen to live inside the tenant's prefix (tenant1/backup_<ts>_<pid>) so all the runtime object keys match the tenant's IAM Resource and Condition. R4 is intentionally complementary to R1 / R1b / R2 in the previous commit: R1, R1b, R2 user shapes that currently work and PR percona#1717 breaks (regressions caused by replacing HeadBucket with HeadObject on bucket/<backup_name>) R4 user shape that currently does NOT work and PR percona#1717 aims to enable (the motivating use case) Expected results at each step of the branch sequence: rel-8406 HEAD (no fix) R1 R1b R2 pass, R4 fails (today's state) rel-8406 + PR percona#1717 R1 R1b R2 fail, R4 passes (the trade-off) rel-8406 + PR percona#1717 + our delta all four pass (full coverage) The two-commit "test-only baseline" (R1/R1b/R2 then R4) is in place before any code change, so reviewers can apply PR percona#1717 verbatim and observe the trade-off via test results, then apply our delta and observe full coverage.
https://perconadev.atlassian.net/browse/PXB-3654 PR percona#1717 ("Don't use HeadBucket for pre-flight checks, instead use HeadObject") fixes the multi-tenant bucket case (R4) but introduces regressions for three currently-working user shapes (see R1, R1b and R2 in the regression-tests commit earlier in this branch): - users with s3:ListBucket + s3:PutObject only (push-only credentials) - users with s3:ListBucket + s3:DeleteObject only (delete-only retention credentials) - users with s3:ListBucket + prefix-scoped Get/Put/Delete where the backup name equals the prefix (HeadObject ARN does not match the IAM Resource) All three fail at "Probe failed" after PR percona#1717 because the new HeadObject-on-bucket/<backup_name> probe requires s3:GetObject, which those users do not have. This commit replaces PR percona#1717's HeadObject probe with a permission- tolerant HeadBucket probe (probe_endpoint, declared private in s3.h) while keeping PR percona#1717's xbcloud_put change (the removal of the container_exists / create_container pre-checks). probe_endpoint preserves PR percona#1717's intent - tenants that lack unconditional s3:ListBucket no longer need it to pass the probe - because we accept any well-formed S3 response, including 403 AccessDenied, as evidence that the request signature was honoured. The probe only fails over to the next (lookup, signature-version) combination on transport errors or on the specific 403s that mean the signature was rejected (SignatureDoesNotMatch, InvalidAccessKeyId). See the implementation comment in s3.cc for the full contract. Concretely, the changes vs PR percona#1717 are: - probe_api_version_and_lookup goes back to taking only `bucket`. - object_exists() (added by PR percona#1717) is removed - it was only used by the probe and is no longer needed. - probe_endpoint() is added as a new private helper on S3_client. - xbcloud-t.cc test fixtures revert to the single-arg probe call. - xbcloud.cc call sites revert to the single-arg probe call. - xbcloud_put's container_exists / create_container removal is left unchanged from PR percona#1717. Combined with the preceding commits, this branch: test commits R1, R1b, R2, R4 in place PR percona#1717 cherry-pick R1, R1b, R2 fail; R4 passes (PR percona#1717's trade-off) this commit R1, R1b, R2, R4 all pass The change matches the behaviour of AWS CLI and rclone, which do not pre-flight bucket existence either and rely on the first real request to surface NoSuchBucket.
|
Another issue with removing the probe: https://perconadev.atlassian.net/browse/PXB-3654?focusedCommentId=463526 |
Proposal
This project uses HeadBucket to determine the signature version to use, as well as whether the bucket exists before creating backups. The problem with this is that in IAM, there is literally no way to let a user call HeadBucket while also preventing them from listing objects inside that bucket. This is because HeadBucket requires the
s3:ListBucketpermission.There are zero air-tight IAM workarounds that would prevent this permission from also allowing users to list objects inside the bucket. Here are some examples:
"Null": {"s3:prefix": "true"}- this actually still allows listing all objects in the bucket, just not specifying a prefix.*/*- can still specify a prefix without a slash and list objects.There has been extensive discussion around this issue as it relates to the CNPG plugin:
EnterpriseDB/barman#929
EnterpriseDB/barman#1101
hashicorp/terraform-provider-aws#17195
The initial suggestion was to use ListObjectsV2 with a prefix rather than HeadBucket - this works in terms of security, but it is more expensive in terms of AWS API calls, and so was rejected.
The final solution as it relates to CNPG was to completely remove cloud connectivity + bucket existence checks before running backups. I propose the same here, which would require to remove the signature checks and require that it is provider; or instead to use HeadObject, which can be scoped to a directory.
Use-Case
The use case is for multi-tenant S3 buckets. Using HeadBucket in this scenario is a non-starter because it would allow users to see backups of other users.
Anything else?
This is a requirement to resolve percona/percona-xtradb-cluster-operator#2251, because that operator calls HeadBucket in itself, as well as by virtue of calling xbcloud.