diff --git a/bash/habitual/secrets.functions b/bash/habitual/secrets.functions new file mode 100644 index 0000000..9a57fd4 --- /dev/null +++ b/bash/habitual/secrets.functions @@ -0,0 +1,102 @@ +# vim: et sr sw=4 ts=4 smartindent syntax=sh: +# +# @overview +# > +# > agnostic public interface for pluggable secrets management functions. +# > + +# ... defines user-defined provider e.g. ssm - see [known_providers()](#known_providers) +SECRETS_PROVIDER="${SECRETS_PROVIDER,,}" + +__SECRETS_HANDLERS_DIR="$(cd $(dirname $BASH_SOURCE) && pwd)/secrets" + +# @desc Configures secrets management functions to use the expected provider. +# +# Pass a valid secrets provider. See [known_providers()](#known_providers) for getting the list. +# Sets `$SECRETS_PROVIDER` for the user. +set_secrets_provider() { + local p="$1" + __valid_provider "$p" || return 1 + __run_init "$p" || return 1 + export SECRETS_PROVIDER="$p" + return 0 +} + +__run_init() { + if declare -f "${p}_init" &>/dev/null + then + d "running init function ${p}_init()" + ${p}_init || return 1 + fi + + return 0 +} + +__valid_provider() { + local p="$1" # ... provider name + local handler_file="$__SECRETS_HANDLERS_DIR/${p}.functions" + + [[ -z "$p" ]] && red_e "expects a provider name for secrets management." && return 1 + + if ! known_providers | grep -Po "(\b$p\b)" >/dev/null + then + red_e "valid provider names are:\n$(known_providers)" + return 1 + fi + + # check file exists for sourcing + if [[ ! -r "$handler_file" ]]; then + red_e "readable handler file not found at $handler_file." + return 1 + fi + + ! . $handler_file && red_e "unable to source $handler_file" && return 1 + + __imported_required_funcs "$p" || return 1 + + return 0 +} + +# @desc Prints supported secrets providers to STDOUT. +# +# We expect a file named after this provider that contains [required functions](#required_funcs). +# +# This is verified when [set_secrets_provider()](#set_secrets_provider) is run. +# +known_providers() { + cat << EOF | sort | uniq + credstash + ssm +EOF +} + +__imported_required_funcs() { + local p="$1" # provider + local func="" failed="" rc=0 + + for func in $(required_funcs) ; do + if ! declare -f ${func} &>/dev/null + then + failed="${failed}function missing: ${func}\n" + rc=1 + fi + done + + if [[ $rc -eq 1 ]]; then + red_e "provider $p not implemented correctly:\n$failed" + fi + + return $rc +} + +# @desc Prints names of functions that must be defined. +# We expect them to be defined in the provider's handler file - +# the file that [set_secrets_provider()](#set_secrets_provider) sources. +# +required_funcs() { + cat < +> agnostic public interface for pluggable secrets management functions. +> + +* [GLOBALS](#globals) + +* [FUNCTIONS](#functions) + +--- + +# GLOBALS + +* `$SECRETS_PROVIDER`: _... defines user-defined provider e.g. ssm - see [known\_providers()](#known\_providers)_ + * reads env var `$SECRETS_PROVIDER,,` + + + +# FUNCTIONS + +* [set\_secrets\_provider()](#set_secrets_provider) +* [known\_providers()](#known_providers) +* [required\_funcs()](#required_funcs) + +--- + +### set\_secrets\_provider() + +Configures secrets management functions to use the expected provider. + +Pass a valid secrets provider. See [known_providers()](#known_providers) for getting the list. +Sets `$SECRETS_PROVIDER` for the user. + +--- + +### known\_providers() + +Prints supported secrets providers to STDOUT. + +We expect a file named after this provider that contains [required functions](#required_funcs). + +This is verified when [set_secrets_provider()](#set_secrets_provider) is run. + + +--- + +### required\_funcs() + +Prints names of functions that must be defined. +We expect them to be defined in the provider's handler file - +the file that [set_secrets_provider()](#set_secrets_provider) sources. + + +--- + diff --git a/bash/habitual/secrets/ssm.functions b/bash/habitual/secrets/ssm.functions new file mode 100644 index 0000000..46062f4 --- /dev/null +++ b/bash/habitual/secrets/ssm.functions @@ -0,0 +1,24 @@ +# vim: et sr sw=4 ts=4 smartindent syntax=sh: + +# @overview +# > +# > Functions for interacting with AWS SSM parameter store secret values. + +# @desc TODO ... we must check that the aws libs have been sourced. +# This function gets loaded automatically when +# [set_secrets_provider()](../secrets.functions.md#set_secrets_provider) is run. +ssm_init() { + # ... check that AWS libs are loaded + # ... check that aws cli exists on path + : +} + +# @desc TODO ... for a given param name, get the secret +get_secret() { + : +} + +# @desc TODO ... set a value for a given param name +put_secret() { + : +} diff --git a/bash/habitual/secrets/ssm.functions.md b/bash/habitual/secrets/ssm.functions.md new file mode 100644 index 0000000..8decb2c --- /dev/null +++ b/bash/habitual/secrets/ssm.functions.md @@ -0,0 +1,36 @@ +# habitual/secrets/ssm.functions + +> +> Functions for interacting with AWS SSM parameter store secret values. + +--- + + +# FUNCTIONS + +* [ssm\_init()](#ssm_init) +* [get\_secret()](#get_secret) +* [put\_secret()](#put_secret) + +--- + +### ssm\_init() + +TODO ... we must check that the aws libs have been sourced. +This function gets loaded automatically when +[set_secrets_provider()](../secrets.functions.md#set_secrets_provider) is run. + +--- + +### get\_secret() + +TODO ... for a given param name, get the secret + +--- + +### put\_secret() + +TODO ... set a value for a given param name + +--- + diff --git a/bash/habitual/std.functions b/bash/habitual/std.functions index aaaec7a..f1aaf22 100644 --- a/bash/habitual/std.functions +++ b/bash/habitual/std.functions @@ -261,6 +261,76 @@ _semver_a_gt_b() { [[ "$a" == "$b" ]] || [[ $(echo -e "$a\n$b" | sort -V | head -n 1) != "$a" ]] } +# @desc Concatenates a multiline string, converting newlines to \n +# +# STDOUT: concatenated line +# *NOTE: the argument is double quoted if a variable, to preserve real newlines.* +# +# @example +# my_str="This is +# a multiline string" +# multiline_to_single "$my_str" +# +# # ... would print something like: +# # This is\na multiline string +# +multiline_to_single() { + local lines="$1" + if [[ -z "$line" ]]; then + red_e 'pass lines to combine with slash n e.g. single_line=$(multiline_to_single "$lines")' + return 1 + fi + sed ':a;N;s/\n/\\n/;ta' <(echo "$lines") +} + +# @desc Splits a string on '\n' to multiple lines with real newlines. +# +# STDOUT: Multiple lines +# +# These are an alternative to the `base64_encode` and +# `base64_decode` functions, for example, when base64 produces +# too long a string. Try storing an ssh key in AWS parameter store +# and see what I mean ... +# +# @example +# my_str="This will be\na multiline string" +# single_to_multiline "$my_str" +# +# # ... would print something like: +# # This will be +# # a multiline string +# +single_to_multiline() { + local line="$1" + [[ -z "$line" ]] && red_e 'pass a line to split on slash-n' && return 1 + echo "$line" | sed -e 's/\\n/\n/g' +} + +# @desc Encodes a string (or mulitline string) as Base64 +# +# You can use this and `base64_decode` when you need to pass +# or store multiline strings as a single line. +# +# These are an alternative to the `single_to_multiline` and +# `multiline_to_single` functions when you wish to preserve existing +# '\n' instances in your strings, and not convert them to newlines +# or vice-versa. +# +base64_encode() { + local str="$1" + echo "$str" | base64 -w0 +} + +# @desc Decodes a base64 string +# +# You can use this and `base64_decode` when you need to pass +# or store multiline strings as a single line. +# +base64_decode() { + local str="$1" + echo "$str" | base64 -d +} + # @desc Exports $BUILD_URL if available from a number of possible sources. # # $BUILD_URL is a link to a CI/CD job's run. diff --git a/bash/habitual/std.functions.md b/bash/habitual/std.functions.md index dc2ec7b..32bcaa8 100644 --- a/bash/habitual/std.functions.md +++ b/bash/habitual/std.functions.md @@ -35,6 +35,10 @@ * [envsubst\_tokens\_list()](#envsubst_tokens_list) * [random\_str()](#random_str) * [semver\_a\_ge\_b()](#semver_a_ge_b) +* [multiline\_to\_single()](#multiline_to_single) +* [single\_to\_multiline()](#single_to_multiline) +* [base64\_encode()](#base64_encode) +* [base64\_decode()](#base64_decode) * [export\_build\_url()](#export_build_url) ## LOG MESSAGE FUNCTIONS --- @@ -222,6 +226,79 @@ semver_a_ge_b 0.99.0-beta V0.99.0-alpha # true (as beta beats alpha) ``` +--- + +### multiline\_to\_single() + +Concatenates a multiline string, converting newlines to \n + +STDOUT: concatenated line +*NOTE: the argument is double quoted if a variable, to preserve real newlines.* + +#### Example + +```bash +my_str="This is +a multiline string" +multiline_to_single "$my_str" + +# ... would print something like: +# This is\na multiline string + +``` + + +--- + +### single\_to\_multiline() + +Splits a string on '\n' to multiple lines with real newlines. + +STDOUT: Multiple lines + +These are an alternative to the `base64_encode` and +`base64_decode` functions, for example, when base64 produces +too long a string. Try storing an ssh key in AWS parameter store +and see what I mean ... + +#### Example + +```bash +my_str="This will be\na multiline string" +single_to_multiline "$my_str" + +# ... would print something like: +# This will be +# a multiline string + +``` + + +--- + +### base64\_encode() + +Encodes a string (or mulitline string) as Base64 + +You can use this and `base64_decode` when you need to pass +or store multiline strings as a single line. + +These are an alternative to the `single_to_multiline` and +`multiline_to_single` functions when you wish to preserve existing +'\n' instances in your strings, and not convert them to newlines +or vice-versa. + + +--- + +### base64\_decode() + +Decodes a base64 string + +You can use this and `base64_decode` when you need to pass +or store multiline strings as a single line. + + --- ### export\_build\_url()