From 983a9594e90d91e7ae17288dcfc3c1e5b67779be Mon Sep 17 00:00:00 2001 From: "Waylon S. Walker" Date: Mon, 3 Nov 2025 08:35:09 -0600 Subject: [PATCH] init managetoken --- README.md | 17 ++++- justfile | 9 +++ managetoken.py | 169 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 managetoken.py diff --git a/README.md b/README.md index ad950a1..11fc212 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ # try-rustfs -Looking for a minio alternative and trying out rustfs +Looking for a minio alternative and trying out rustfs. Currently all the +operations are documented in the justfile. + +Startup time is insane... I also checked minio, and it is also crazy fast, I +did not realize how fast these things can start up. + +``` bash +just start list-buckets list-dropper 0.81s user 0.29s system 73% cpu 1.494 total +just list-buckets list-dropper 0.67s user 0.22s system 74% cpu 1.203 total +``` ## .env @@ -9,3 +18,9 @@ this would be a bad idea for anything connected to the internet. This only ever only ever ran on my local system. **never** commit real secrets to your repo. + +## Token Management + +I ran into issues using the `mcli` to manage tokens. I'm working on creating +something based on a request in the console. Gippity created managetoken.py +for me based on the request. diff --git a/justfile b/justfile index 1574e74..937b803 100644 --- a/justfile +++ b/justfile @@ -5,6 +5,9 @@ start: mkdir -p logs podman run -d -p 9000:9000 -v $(pwd)/data:/data -v $(pwd)/logs:/logs docker.io/rustfs/rustfs:alpha +start-minio: + podman run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs docker.io/minio/minio server /data + make-bucket: #!/bin/bash set +e @@ -30,3 +33,9 @@ post-presigned-url: echo $URL curl -X PUT -T hello.txt $URL +ls: + aws s3 ls + aws s3 ls s3://dropper + +createtoken: + ./managetoken.py diff --git a/managetoken.py b/managetoken.py new file mode 100644 index 0000000..cecf3b7 --- /dev/null +++ b/managetoken.py @@ -0,0 +1,169 @@ +#!/usr/bin/env -S uv run --quiet --script +# /// script +# requires-python = ">=3.12" +# dependencies = ["requests"] +# /// + +import datetime as dt +import hashlib +import hmac +import json +import urllib.parse as urlparse + +import requests + +# ====== CONFIG ====== +ENDPOINT = "http://localhost:9000" +ACCESS_KEY = "rustfsadmin" +SECRET_KEY = "rustfsadmin" +SESSION_TOKEN = None # set to your x-amz-security-token if using STS, else leave None +REGION = "us-east-1" +SERVICE = "s3" + +# The admin call you shared: +path = "/rustfs/admin/v3/add-canned-policy" +query_params = {"name": "testmepy"} +payload = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ObjectActions", + "Effect": "Allow", + "Action": ["s3:*"], + "Resource": [ + "arn:aws:s3:::k8s-pages/dropper/*", + "arn:aws:s3:::k8s-pages/dropper-dev/*", + "arn:aws:s3:::dropper/*", + "arn:aws:s3:::dropper-dev/*", + "arn:aws:s3:::k8s-pages/dropper", + "arn:aws:s3:::k8s-pages/dropper-dev", + "arn:aws:s3:::dropper", + "arn:aws:s3:::dropper-dev", + ], + } + ], +} +# ===================== + +def amz_date_now(): + now = dt.datetime.utcnow() + return now.strftime("%Y%m%dT%H%M%SZ"), now.strftime("%Y%m%d") + +def sha256_hex(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + +def hmac_sha256(key: bytes, msg: str) -> bytes: + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + +def get_signing_key(secret_key: str, datestamp: str, region: str, service: str) -> bytes: + k_date = hmac_sha256(("AWS4" + secret_key).encode("utf-8"), datestamp) + k_region = hmac_sha256(k_date, region) + k_service = hmac_sha256(k_region, service) + k_signing = hmac_sha256(k_service, "aws4_request") + return k_signing + +def canonical_query_string(qs: dict) -> str: + # AWS requires query params sorted by key, then value; values URL-encoded + parts = [] + for k in sorted(qs.keys()): + v = qs[k] + parts.append( + urlparse.quote(str(k), safe="-_.~") + "=" + urlparse.quote(str(v), safe="-_.~") + ) + return "&".join(parts) + +def sign_request(method: str, url: str, host_header: str, params: dict, body: bytes, + access_key: str, secret_key: str, region: str, service: str, session_token: str | None): + amz_datetime, datestamp = amz_date_now() + + canonical_uri = urlparse.quote(urlparse.urlparse(url).path or "/", safe="/-_.~") + canonical_qs = canonical_query_string(params or {}) + + # We intentionally use UNSIGNED-PAYLOAD to match your curl + payload_hash = "UNSIGNED-PAYLOAD" + + # Required headers (and only these will be signed) + headers = { + "host": host_header, + "x-amz-content-sha256": payload_hash, + "x-amz-date": amz_datetime, + } + if session_token: + headers["x-amz-security-token"] = session_token + + signed_headers_list = ["host", "x-amz-content-sha256", "x-amz-date"] + if session_token: + signed_headers_list.append("x-amz-security-token") + signed_headers = ";".join(signed_headers_list) + + canonical_headers = "".join(f"{h}:{headers[h]}\n" for h in signed_headers_list) + + canonical_request = "\n".join( + [ + method, + canonical_uri, + canonical_qs, + canonical_headers, + signed_headers, + payload_hash, + ] + ) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = f"{datestamp}/{region}/{service}/aws4_request" + string_to_sign = "\n".join( + [ + algorithm, + amz_datetime, + credential_scope, + sha256_hex(canonical_request.encode("utf-8")), + ] + ) + + signing_key = get_signing_key(secret_key, datestamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = ( + f"{algorithm} Credential={access_key}/{credential_scope}, " + f"SignedHeaders={signed_headers}, Signature={signature}" + ) + + # Build final headers for the HTTP request + final_headers = { + "Authorization": authorization, + "x-amz-date": amz_datetime, + "x-amz-content-sha256": payload_hash, + "Host": host_header, + "Content-Type": "application/json", + } + if session_token: + final_headers["x-amz-security-token"] = session_token + + return final_headers + +def main(): + full_url = ENDPOINT.rstrip("/") + path + host_header = urlparse.urlparse(ENDPOINT).netloc or "localhost:9000" + body_bytes = json.dumps(payload, separators=(",", ":")).encode("utf-8") + + headers = sign_request( + method="PUT", + url=full_url, + host_header=host_header, + params=query_params, + body=body_bytes, + access_key=ACCESS_KEY, + secret_key=SECRET_KEY, + region=REGION, + service=SERVICE, + session_token=SESSION_TOKEN, + ) + + r = requests.put(full_url, params=query_params, data=body_bytes, headers=headers) + print("Status:", r.status_code) + print("Response:", r.text) + +if __name__ == "__main__": + main() + +