From 6139de8e97447b61babac3382aca29bb63a07515 Mon Sep 17 00:00:00 2001 From: cmuraru Date: Mon, 12 Aug 2019 16:24:33 +0300 Subject: [PATCH] Use default AWS profile when resolving SSM secrets Signed-off-by: cmuraru --- src/ops/hierarchical/config_generator.py | 2 +- src/ops/hierarchical/inject_secrets.py | 4 +- src/ops/hierarchical/interpolation.py | 66 +++++++++++++++++--- src/ops/hierarchical/secret_resolvers.py | 15 +++-- src/ops/terraform/terraform_cmd_generator.py | 2 +- 5 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/ops/hierarchical/config_generator.py b/src/ops/hierarchical/config_generator.py index eecf4583..333da510 100755 --- a/src/ops/hierarchical/config_generator.py +++ b/src/ops/hierarchical/config_generator.py @@ -156,7 +156,7 @@ def add_dynamic_data(self): self.merge_value(self.generated_data, remote_states) def resolve_interpolations(self): - resolver = InterpolationResolver(self.generated_data) + resolver = InterpolationResolver() self.generated_data = resolver.resolve_interpolations(self.generated_data) def validate_interpolations(self): diff --git a/src/ops/hierarchical/inject_secrets.py b/src/ops/hierarchical/inject_secrets.py index 519c397c..5312da75 100644 --- a/src/ops/hierarchical/inject_secrets.py +++ b/src/ops/hierarchical/inject_secrets.py @@ -22,8 +22,8 @@ class SecretInjector(object): {{vault.kv2.path(ethos/k8s-ethos-config/thrash/aws/ClusterIngressTLS).field(Key)}} """ - def __init__(self): - self.resolver = AggregatedSecretResolver() + def __init__(self, default_aws_profile=None): + self.resolver = AggregatedSecretResolver(default_aws_profile) def is_interpolation(self, value): return value.startswith('{{') and value.endswith('}}') diff --git a/src/ops/hierarchical/interpolation.py b/src/ops/hierarchical/interpolation.py index 79843576..12d8e390 100644 --- a/src/ops/hierarchical/interpolation.py +++ b/src/ops/hierarchical/interpolation.py @@ -15,6 +15,36 @@ def is_interpolation(input): return '{{' in input and '}}' in input +class InterpolationResolver(object): + + def resolve_interpolations(self, data): + # Resolve from dictionary. Do one iteration before secret resolving, in order to resolve interpolations such as + # the aws.profile + # Example: + # my_profile: test + # aws: + # profile: "{{my_profile}}" + from_dict_injector = DictInterpolationResolver(data, FromDictInjector()) + from_dict_injector.resolve_interpolations(data) + + # Resolve interpolations representing secrets + # Example: + # value1: "{{ssm.path(mysecret)}}" + secrets_injector = SecretsInterpolationResolver(self.get_secret_injector(data)) + secrets_injector.resolve_interpolations(data) + + # Perform another resolving, in case some secrets are used as interpolations. + # Example: + # value1: "{{ssm.mysecret}}" + # value2: "something-{{value1}} <--- this will be resolved at this step + from_dict_injector = DictInterpolationResolver(data, FromDictInjector()) + from_dict_injector.resolve_interpolations(data) + + def get_secret_injector(self, data): + default_aws_profile = data['aws']['profile'] if 'aws' in data and 'profile' in data['aws'] else None + return SecretInjector(default_aws_profile) + + class DictIterator(): def loop_all_items(self, data, process_func): @@ -33,12 +63,9 @@ def loop_all_items(self, data, process_func): return data -class InterpolationResolver(DictIterator): - - def __init__(self, data, secrets_injector=SecretInjector()): - self.generated_data = data - self.secrets_injector = secrets_injector - self.from_dict_injector = FromDictInjector() +class AbstractInterpolationResolver(DictIterator): + def __init__(self): + pass def resolve_interpolations(self, data): return self.loop_all_items(data, self.resolve_interpolation) @@ -46,10 +73,29 @@ def resolve_interpolations(self, data): def resolve_interpolation(self, line): if not is_interpolation(line): return line + return self.do_resolve_interpolation(line) + + def do_resolve_interpolation(self, line): + pass + + +class DictInterpolationResolver(AbstractInterpolationResolver): + def __init__(self, data, from_dict_injector): + AbstractInterpolationResolver.__init__(self) + self.data = data + self.from_dict_injector = from_dict_injector + + def do_resolve_interpolation(self, line): + return self.from_dict_injector.resolve(line, self.data) + + +class SecretsInterpolationResolver(AbstractInterpolationResolver): + def __init__(self, secrets_injector): + AbstractInterpolationResolver.__init__(self) + self.secrets_injector = secrets_injector - updated_line = self.secrets_injector.inject_secret(line) - updated_line = self.from_dict_injector.resolve(updated_line, self.generated_data) - return updated_line + def do_resolve_interpolation(self, line): + return self.secrets_injector.inject_secret(line) class InterpolationValidator(DictIterator): @@ -85,7 +131,7 @@ def resolve(self, line, data): continue elif isinstance(value, (int, bool)): return value - else: + elif not is_interpolation(value): line = line.replace(placeholder, value) return line diff --git a/src/ops/hierarchical/secret_resolvers.py b/src/ops/hierarchical/secret_resolvers.py index 6031c178..0ed1b10f 100644 --- a/src/ops/hierarchical/secret_resolvers.py +++ b/src/ops/hierarchical/secret_resolvers.py @@ -19,12 +19,18 @@ def resolve(self, secret_type, secret_params): class SSMSecretResolver(SecretResolver): + def __init__(self, default_aws_profile=None): + self.default_aws_profile = default_aws_profile + def supports(self, secret_type): return secret_type == "ssm" def resolve(self, secret_type, secret_params): + aws_profile = secret_params.get("aws_profile", self.default_aws_profile) + if not aws_profile: + raise Exception("Could not find the aws_profile in the secret params: {}".format(secret_params)) + path = self.get_param_or_exception("path", secret_params) - aws_profile = self.get_param_or_exception("aws_profile", secret_params) region_name = secret_params.get("region_name", "us-east-1") ssm = SimpleSSM(aws_profile, region_name) return ssm.get(path) @@ -46,13 +52,14 @@ def resolve(self, secret_type, secret_params): class AggregatedSecretResolver(SecretResolver): - SECRET_RESOLVERS = (SSMSecretResolver(), VaultSecretResolver()) + def __init__(self, default_aws_profile=None): + self.secret_resolvers = (SSMSecretResolver(default_aws_profile), VaultSecretResolver()) def supports(self, secret_type): - return any([resolver.supports(secret_type) for resolver in self.SECRET_RESOLVERS]) + return any([resolver.supports(secret_type) for resolver in self.secret_resolvers]) def resolve(self, secret_type, secret_params): - for resolver in self.SECRET_RESOLVERS: + for resolver in self.secret_resolvers: if resolver.supports(secret_type): return resolver.resolve(secret_type, secret_params) diff --git a/src/ops/terraform/terraform_cmd_generator.py b/src/ops/terraform/terraform_cmd_generator.py index 7979d540..507328fe 100644 --- a/src/ops/terraform/terraform_cmd_generator.py +++ b/src/ops/terraform/terraform_cmd_generator.py @@ -179,7 +179,7 @@ def generate(self, args): else: cmd = "cd {root_dir}/{terraform_path} && " \ "terraform apply " \ - "-refresh=true {state_out_argument} {plan_file} {variables_file}; code=$?; rm -f {plan_file}; exit $code".format( + "-refresh=true {state_out_argument} {plan_file}; code=$?; rm -f {plan_file}; exit $code".format( plan_file=plan_file, root_dir=self.root_dir, state_out_argument=state_out_argument,