From 994fef9b302e4a6082d2443319e898380d26a633 Mon Sep 17 00:00:00 2001 From: tdviet Date: Sun, 26 Jun 2022 16:53:07 +0200 Subject: [PATCH 1/4] Add support binary files as secret values --- fedcloudclient/secret.py | 110 ++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 31 deletions(-) diff --git a/fedcloudclient/secret.py b/fedcloudclient/secret.py index 0010e69..7bbe13a 100644 --- a/fedcloudclient/secret.py +++ b/fedcloudclient/secret.py @@ -4,6 +4,7 @@ import base64 import json import os +import sys import click import hvac @@ -72,33 +73,40 @@ def read_data_from_file(input_format, input_file): """ if input_format is None or input_format == "auto-detect": - if input_file.endswith(".yaml"): - input_format = "yaml" - elif input_file.endswith(".json"): + if input_file.endswith(".json"): input_format = "json" else: + # default format input_format = "yaml" try: + + # read text/binary files to strings + if input_format == "binary": + with open(input_file, "rb") as f: + return base64.b64encode(f.read()).decode() + if input_format == "text": + with open(input_file, "r") as f: + return f.read() + + # reading YAML or JSON to dict with open(input_file) as f: if input_format == "yaml": data = yaml.safe_load(f) elif input_format == "json": data = json.load(f) - else: - data = f.read() - if input_format in ("yaml", "json"): - data = dict(data) + return dict(data) + except (ValueError, FileNotFoundError, YAMLError) as e: raise SystemExit( - f"Error: Error when reading file {input_file}. Error message: {e}" + f"Error: Error when reading file {input_file}. Error message: {type(e).__name__}: {e}" ) - return data -def secret_params_to_dict(params): +def secret_params_to_dict(params, binary_file=False): """ Convert secret params "key=value" to dict {"key":"value"} + :param binary_file: if reading files as binary :param params: input string in format "key=value" :return: dict {"key":"value"} """ @@ -122,7 +130,10 @@ def secret_params_to_dict(params): f"Error: Expecting 'key=value' arguments for secrets. '{param}' provided." ) if value.startswith("@"): - value = read_data_from_file("text", value[1:]) + if binary_file: + value = read_data_from_file("binary", value[1:]) + else: + value = read_data_from_file("text", value[1:]) result[key] = value return result @@ -177,58 +188,93 @@ def decrypt_data(decrypt_key, secrets): raise SystemExit(f"Error: Error during decryption. {e}") -def print_secrets(output_format, secrets): +def print_secrets(output_file, output_format, secrets): """ Print secrets in different formats + :param output_file: :param output_format: :param secrets: :return: """ - if output_format == "JSON": - print(json.dumps(secrets, indent=4)) - elif output_format == "YAML": - print(yaml.dump(secrets, sort_keys=False)) - else: - print(tabulate(secrets.items(), headers=["key", "value"])) + + try: + with open(output_file, "wt") if output_file else sys.stdout as f: + if output_format == "JSON": + json.dump(secrets, f, indent=4) + elif output_format == "YAML": + yaml.dump(secrets, f, sort_keys=False) + else: + print(tabulate(secrets.items(), headers=["key", "value"]),file=f) + + except (ValueError, FileNotFoundError, YAMLError) as e: + raise SystemExit( + f"Error: Error when wrting file {output_file}. Error message: {type(e).__name__}: {e}" + ) + + +def print_value(output_file, binary_file, value): + """ + Print secrets in different formats + :param output_file: + :param binary_file: + :param value: + :return: + """ + + try: + if binary_file: + with open(output_file, "wb") if output_file else sys.stdout.buffer as f: + f.write(base64.b64decode(value.encode())) + else: + with open(output_file, "wt") if output_file else sys.stdout as f: + f.write(value) + + except (ValueError, FileNotFoundError, TypeError) as e: + raise SystemExit( + f"Error: Error when writing file {output_file}. Error message: {type(e).__name__}: {e}" + ) @click.group() def secret(): """ - Commands for accessing secrets + Commands for accessing secret objects """ @secret.command() @oidc_params @click.option( - "--output-format", - "-f", - required=False, + "--output-format", "-f", + required=False, help="Output format", type=click.Choice(["text", "YAML", "JSON"], case_sensitive=False), ) -@click.option("--decrypt-key", required=False) @click.argument("short_path", metavar="[secret path]") @click.argument("key", metavar="[key]", required=False) +@click.option("--decrypt-key", "-d", metavar="[key]", required=False, help="Decryption key or passphrase") +@click.option("--binary-file", "-b", required=False, is_flag=True, help="True for reading binary files") +@click.option("--output-file", "-o", metavar="[filename]", required=False, help="Name of output file") def get( access_token, short_path, key, output_format, decrypt_key, + binary_file, + output_file, ): """ - Get a secret from the path. If a key is given, print only the value of the key + Get the secret object in the path. If a key is given, print only the value of the key """ response = secret_client(access_token, "read_secret", short_path, None) if decrypt_key: decrypt_data(decrypt_key, response["data"]) if not key: - print_secrets(output_format, response["data"]) + print_secrets(output_file, output_format, response["data"]) else: if key in response["data"]: - print(response["data"][key]) + print_value(output_file, binary_file, response["data"][key]) else: raise SystemExit(f"Error: {key} not found in {short_path}") @@ -241,7 +287,7 @@ def list_( short_path, ): """ - List secrets in the path + List secret objects in the path """ response = secret_client(access_token, "list_secrets", short_path, None) @@ -252,18 +298,20 @@ def list_( @oidc_params @click.argument("short_path", metavar="[secret path]") @click.argument("secrets", nargs=-1, metavar="[key=value...]") -@click.option("--encrypt-key", required=False) +@click.option("--encrypt-key", "-e", metavar="[key]", required=False, help="Encryption key or passphrase") +@click.option("--binary-file", "-b", required=False, is_flag=True, help="True for reading binary files") def put( access_token, short_path, secrets, encrypt_key, + binary_file, ): """ - Put a secret to the path. Secrets are provided in form key=value + Put a secret object to the path. Secrets are provided in form key=value """ - secret_dict = secret_params_to_dict(secrets) + secret_dict = secret_params_to_dict(secrets, binary_file) if encrypt_key: encrypt_data(encrypt_key, secret_dict) secret_client(access_token, "put", short_path, secret_dict) @@ -277,7 +325,7 @@ def delete( short_path, ): """ - Delete the secret in the path + Delete the secret object in the path """ secret_client(access_token, "delete_secret", short_path, None) From 7199eb61a3e6862335ab9825d56e94a795ca4269 Mon Sep 17 00:00:00 2001 From: tdviet Date: Sun, 26 Jun 2022 17:23:25 +0200 Subject: [PATCH 2/4] Add support binary files as secret values --- fedcloudclient/secret.py | 48 ++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/fedcloudclient/secret.py b/fedcloudclient/secret.py index 7bbe13a..9cf37e2 100644 --- a/fedcloudclient/secret.py +++ b/fedcloudclient/secret.py @@ -204,11 +204,11 @@ def print_secrets(output_file, output_format, secrets): elif output_format == "YAML": yaml.dump(secrets, f, sort_keys=False) else: - print(tabulate(secrets.items(), headers=["key", "value"]),file=f) + print(tabulate(secrets.items(), headers=["key", "value"]), file=f) except (ValueError, FileNotFoundError, YAMLError) as e: raise SystemExit( - f"Error: Error when wrting file {output_file}. Error message: {type(e).__name__}: {e}" + f"Error: Error when writing file {output_file}. Error message: {type(e).__name__}: {e}" ) @@ -245,15 +245,33 @@ def secret(): @secret.command() @oidc_params @click.option( - "--output-format", "-f", - required=False, help="Output format", + "--output-format", + "-f", + required=False, + help="Output format", type=click.Choice(["text", "YAML", "JSON"], case_sensitive=False), ) @click.argument("short_path", metavar="[secret path]") @click.argument("key", metavar="[key]", required=False) -@click.option("--decrypt-key", "-d", metavar="[key]", required=False, help="Decryption key or passphrase") -@click.option("--binary-file", "-b", required=False, is_flag=True, help="True for reading binary files") -@click.option("--output-file", "-o", metavar="[filename]", required=False, help="Name of output file") +@click.option( + "--decrypt-key", + "-d", + metavar="[key]", + required=False, + help="Decryption key or passphrase") +@click.option( + "--binary-file", + "-b", + required=False, + is_flag=True, + help="True for writing secrets to binary files") +@click.option( + "--output-file", + "-o", + metavar="[filename]", + required=False, + help="Name of output file" +) def get( access_token, short_path, @@ -298,8 +316,20 @@ def list_( @oidc_params @click.argument("short_path", metavar="[secret path]") @click.argument("secrets", nargs=-1, metavar="[key=value...]") -@click.option("--encrypt-key", "-e", metavar="[key]", required=False, help="Encryption key or passphrase") -@click.option("--binary-file", "-b", required=False, is_flag=True, help="True for reading binary files") +@click.option( + "--encrypt-key", + "-e", + metavar="[key]", + required=False, + help="Encryption key or passphrase" +) +@click.option( + "--binary-file", + "-b", + required=False, + is_flag=True, + help="True for reading secrets from binary files" +) def put( access_token, short_path, From 881d6bc1efba80034d70f85e8932016e6720df73 Mon Sep 17 00:00:00 2001 From: tdviet Date: Sun, 26 Jun 2022 17:30:24 +0200 Subject: [PATCH 3/4] Add support binary files as secret values --- fedcloudclient/secret.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fedcloudclient/secret.py b/fedcloudclient/secret.py index 9cf37e2..57964b5 100644 --- a/fedcloudclient/secret.py +++ b/fedcloudclient/secret.py @@ -264,13 +264,14 @@ def secret(): "-b", required=False, is_flag=True, - help="True for writing secrets to binary files") + help="True for writing secrets to binary files", +) @click.option( "--output-file", "-o", metavar="[filename]", required=False, - help="Name of output file" + help="Name of output file", ) def get( access_token, @@ -321,14 +322,14 @@ def list_( "-e", metavar="[key]", required=False, - help="Encryption key or passphrase" + help="Encryption key or passphrase", ) @click.option( "--binary-file", "-b", required=False, is_flag=True, - help="True for reading secrets from binary files" + help="True for reading secrets from binary files", ) def put( access_token, From 95147ed59034f81f732bb1bd212f26da0a163aaa Mon Sep 17 00:00:00 2001 From: tdviet Date: Sun, 26 Jun 2022 17:35:22 +0200 Subject: [PATCH 4/4] Add support binary files as secret values --- fedcloudclient/secret.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fedcloudclient/secret.py b/fedcloudclient/secret.py index 57964b5..9e7cf9c 100644 --- a/fedcloudclient/secret.py +++ b/fedcloudclient/secret.py @@ -258,7 +258,8 @@ def secret(): "-d", metavar="[key]", required=False, - help="Decryption key or passphrase") + help="Decryption key or passphrase", +) @click.option( "--binary-file", "-b",