diff options
| author | Tom Crasset <25140344+tcrasset@users.noreply.github.com> | 2025-01-17 10:03:17 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-17 01:03:17 -0800 |
| commit | c5f21b2b01deb10a542455b95285860a53f1f4d0 (patch) | |
| tree | 4d4ae04e47758a3f41e2071526a72306ac02a4f7 /docker/test.py | |
| parent | eab2e0e1127e2d8ccdee9ee518e0ae20ea8311ba (diff) | |
| download | seaweedfs-c5f21b2b01deb10a542455b95285860a53f1f4d0.tar.xz seaweedfs-c5f21b2b01deb10a542455b95285860a53f1f4d0.zip | |
fix S3 per-user-directory Policy (#6443)
* fix S3 per-user-directory Policy
* Delete docker/config.json
* add tests
* remove logs
* undo modifications of weed/shell/command_volume_balance.go
* remove modifications of docker-compose
* fix failing test
---------
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
Diffstat (limited to 'docker/test.py')
| -rwxr-xr-x | docker/test.py | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/docker/test.py b/docker/test.py new file mode 100755 index 000000000..8ac025b32 --- /dev/null +++ b/docker/test.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.12" +# dependencies = [ +# "boto3", +# ] +# /// + +import argparse +import json +import random +import string +import subprocess +from enum import Enum +from pathlib import Path + +import boto3 + +REGION_NAME = "us-east-1" + + +class Actions(str, Enum): + Get = "Get" + Put = "Put" + List = "List" + + +def get_user_dir(bucket_name, user, with_bucket=True): + if with_bucket: + return f"{bucket_name}/user-id-{user}" + + return f"user-id-{user}" + + +def create_power_user(): + power_user_key = "power_user_key" + power_user_secret = "power_user_secret" + command = f"s3.configure -apply -user poweruser -access_key {power_user_key} -secret_key {power_user_secret} -actions Admin" + print("Creating Power User...") + subprocess.run( + ["docker", "exec", "-i", "seaweedfs-master-1", "weed", "shell"], + input=command, + text=True, + stdout=subprocess.PIPE, + ) + print( + f"Power User created with key: {power_user_key} and secret: {power_user_secret}" + ) + return power_user_key, power_user_secret + + +def create_bucket(s3_client, bucket_name): + print(f"Creating Bucket {bucket_name}...") + s3_client.create_bucket(Bucket=bucket_name) + print(f"Bucket {bucket_name} created.") + + +def upload_file(s3_client, bucket_name, user, file_path, custom_remote_path=None): + user_dir = get_user_dir(bucket_name, user, with_bucket=False) + if custom_remote_path: + remote_path = custom_remote_path + else: + remote_path = f"{user_dir}/{str(Path(file_path).name)}" + + print(f"Uploading {file_path} for {user}... on {user_dir}") + + s3_client.upload_file(file_path, bucket_name, remote_path) + print(f"File {file_path} uploaded for {user}.") + + +def create_user(iam_client, user): + print(f"Creating user {user}...") + response = iam_client.create_access_key(UserName=user) + print( + f"User {user} created with access key: {response['AccessKey']['AccessKeyId']}" + ) + return response + + +def list_files(s3_client, bucket_name, path=None): + if path is None: + path = "" + print(f"Listing files of s3://{bucket_name}/{path}...") + try: + response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix=path) + if "Contents" in response: + for obj in response["Contents"]: + print(f"\t - {obj['Key']}") + else: + print("No files found.") + except Exception as e: + print(f"Error listing files: {e}") + + +def create_policy_for_user( + iam_client, user, bucket_name, actions=[Actions.Get, Actions.List] +): + print(f"Creating policy for {user} on {bucket_name}...") + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [f"s3:{action.value}*" for action in actions], + "Resource": [ + f"arn:aws:s3:::{get_user_dir(bucket_name, user)}/*", + ], + } + ], + } + policy_name = f"{user}-{bucket_name}-full-access" + + policy_json = json.dumps(policy_document) + filepath = f"/tmp/{policy_name}.json" + with open(filepath, "w") as f: + f.write(json.dumps(policy_document, indent=2)) + + iam_client.put_user_policy( + PolicyName=policy_name, PolicyDocument=policy_json, UserName=user + ) + print(f"Policy for {user} on {bucket_name} created.") + + +def main(): + parser = argparse.ArgumentParser(description="SeaweedFS S3 Test Script") + parser.add_argument( + "--s3-url", default="http://127.0.0.1:8333", help="S3 endpoint URL" + ) + parser.add_argument( + "--iam-url", default="http://127.0.0.1:8111", help="IAM endpoint URL" + ) + args = parser.parse_args() + + bucket_name = ( + f"test-bucket-{''.join(random.choices(string.digits + 'abcdef', k=8))}" + ) + sentinel_file = "/tmp/SENTINEL" + with open(sentinel_file, "w") as f: + f.write("Hello World") + print(f"SENTINEL file created at {sentinel_file}") + + power_user_key, power_user_secret = create_power_user() + + admin_s3_client = get_s3_client(args, power_user_key, power_user_secret) + iam_client = get_iam_client(args, power_user_key, power_user_secret) + + create_bucket(admin_s3_client, bucket_name) + upload_file(admin_s3_client, bucket_name, "Alice", sentinel_file) + upload_file(admin_s3_client, bucket_name, "Bob", sentinel_file) + list_files(admin_s3_client, bucket_name) + + alice_user_info = create_user(iam_client, "Alice") + bob_user_info = create_user(iam_client, "Bob") + + alice_key = alice_user_info["AccessKey"]["AccessKeyId"] + alice_secret = alice_user_info["AccessKey"]["SecretAccessKey"] + bob_key = bob_user_info["AccessKey"]["AccessKeyId"] + bob_secret = bob_user_info["AccessKey"]["SecretAccessKey"] + + # Make sure Admin can read any files + list_files(admin_s3_client, bucket_name) + list_files( + admin_s3_client, + bucket_name, + get_user_dir(bucket_name, "Alice", with_bucket=False), + ) + list_files( + admin_s3_client, + bucket_name, + get_user_dir(bucket_name, "Bob", with_bucket=False), + ) + + # Create read policy for Alice and Bob + create_policy_for_user(iam_client, "Alice", bucket_name) + create_policy_for_user(iam_client, "Bob", bucket_name) + + alice_s3_client = get_s3_client(args, alice_key, alice_secret) + + # Make sure Alice can read her files + list_files( + alice_s3_client, + bucket_name, + get_user_dir(bucket_name, "Alice", with_bucket=False) + "/", + ) + + # Make sure Bob can read his files + bob_s3_client = get_s3_client(args, bob_key, bob_secret) + list_files( + bob_s3_client, + bucket_name, + get_user_dir(bucket_name, "Bob", with_bucket=False) + "/", + ) + + # Update policy to include write + create_policy_for_user(iam_client, "Alice", bucket_name, actions=[Actions.Put, Actions.Get, Actions.List]) # fmt: off + create_policy_for_user(iam_client, "Bob", bucket_name, actions=[Actions.Put, Actions.Get, Actions.List]) # fmt: off + + print("############################# Make sure Alice can write her files") + upload_file( + alice_s3_client, + bucket_name, + "Alice", + sentinel_file, + custom_remote_path=f"{get_user_dir(bucket_name, 'Alice', with_bucket=False)}/SENTINEL_by_Alice", + ) + + + print("############################# Make sure Bob can write his files") + upload_file( + bob_s3_client, + bucket_name, + "Bob", + sentinel_file, + custom_remote_path=f"{get_user_dir(bucket_name, 'Bob', with_bucket=False)}/SENTINEL_by_Bob", + ) + + + print("############################# Make sure Alice can read her new files") + list_files( + alice_s3_client, + bucket_name, + get_user_dir(bucket_name, "Alice", with_bucket=False) + "/", + ) + + + print("############################# Make sure Bob can read his new files") + list_files( + bob_s3_client, + bucket_name, + get_user_dir(bucket_name, "Bob", with_bucket=False) + "/", + ) + + + print("############################# Make sure Bob cannot read Alice's files") + list_files( + bob_s3_client, + bucket_name, + get_user_dir(bucket_name, "Alice", with_bucket=False) + "/", + ) + + print("############################# Make sure Alice cannot read Bob's files") + + list_files( + alice_s3_client, + bucket_name, + get_user_dir(bucket_name, "Bob", with_bucket=False) + "/", + ) + + + +def get_iam_client(args, access_key, secret_key): + iam_client = boto3.client( + "iam", + endpoint_url=args.iam_url, + region_name=REGION_NAME, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + ) + return iam_client + + +def get_s3_client(args, access_key, secret_key): + s3_client = boto3.client( + "s3", + endpoint_url=args.s3_url, + region_name=REGION_NAME, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + ) + return s3_client + + +if __name__ == "__main__": + main() |
