Browse Source

Secrets handlers support

Add functionality that enables configurable secrets sources, with a
hard-coded `env` that provides the existing environmental variable
support.
pull/1140/head
Ross McFarland 2 years ago
parent
commit
ca2c7112a1
No known key found for this signature in database GPG Key ID: 943B179E15D3B22A
7 changed files with 102 additions and 15 deletions
  1. +15
    -14
      octodns/manager.py
  2. +3
    -0
      octodns/secret/__init__.py
  3. +11
    -0
      octodns/secret/base.py
  4. +32
    -0
      octodns/secret/environ.py
  5. +7
    -0
      octodns/secret/exception.py
  6. +3
    -1
      tests/test_octodns_manager.py
  7. +31
    -0
      tests/test_octodns_secret_environ.py

+ 15
- 14
octodns/manager.py View File

@ -10,7 +10,6 @@ from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as module_version
from json import dumps
from logging import getLogger
from os import environ
from sys import stdout
from . import __version__
@ -20,6 +19,7 @@ from .processor.meta import MetaProcessor
from .provider.base import BaseProvider
from .provider.plan import Plan
from .provider.yaml import SplitYamlProvider, YamlProvider
from .secret.environ import EnvironSecrets
from .yaml import safe_load
from .zone import Zone
@ -119,6 +119,8 @@ class Manager(object):
manager_config, enable_checksum
)
self.secret_handlers = {'env': EnvironSecrets('env')}
self.auto_arpa = self._config_auto_arpa(manager_config, auto_arpa)
self.global_processors = manager_config.get('processors', [])
@ -377,22 +379,21 @@ class Manager(object):
if isinstance(v, dict):
v = self._build_kwargs(v)
elif isinstance(v, str):
if v.startswith('env/'):
# expand env variables
if '/' in v:
handler, name = v.split('/', 1)
try:
env_var = v[4:]
v = environ[env_var]
handler = self.secret_handlers[handler]
except KeyError:
self.log.exception('Invalid provider config')
raise ManagerException(
f'Incorrect provider config, missing env var {env_var}, {source.context}'
# we don't have a matching handler, but don't want to
# make that an error b/c config values will often
# contain /. We don't want to print the values in case
# they're sensitive so just provide the key, and even
# that only at debug level.
self.log.debug(
'_build_kwargs: no handler found for the value of {k}'
)
try:
# try converting the value to a number to see if it
# converts
v = float(v)
except ValueError:
pass
else:
v = handler.fetch(name, source)
kwargs[k] = v


+ 3
- 0
octodns/secret/__init__.py View File

@ -0,0 +1,3 @@
#
#
#

+ 11
- 0
octodns/secret/base.py View File

@ -0,0 +1,11 @@
#
#
#
from logging import getLogger
class BaseSecrets:
def __init__(self, name):
self.log = getLogger(f'{self.__class__.__name__}[{name}]')
self.name = name

+ 32
- 0
octodns/secret/environ.py View File

@ -0,0 +1,32 @@
#
#
#
from os import environ
from .base import BaseSecrets
from .exception import SecretException
class EnvironSecretException(SecretException):
pass
class EnvironSecrets(BaseSecrets):
def fetch(self, name, source):
# expand env variables
try:
v = environ[name]
except KeyError:
self.log.exception('Invalid provider config')
raise EnvironSecretException(
f'Incorrect provider config, missing env var {name}, {source.context}'
)
try:
# try converting the value to a number to see if it
# converts
v = float(v)
except ValueError:
pass
return v

+ 7
- 0
octodns/secret/exception.py View File

@ -0,0 +1,7 @@
#
#
#
class SecretException(Exception):
pass

+ 3
- 1
tests/test_octodns_manager.py View File

@ -26,6 +26,7 @@ from octodns.manager import (
)
from octodns.processor.base import BaseProcessor
from octodns.record import Create, Delete, Record, Update
from octodns.secret.environ import EnvironSecretException
from octodns.yaml import safe_load
from octodns.zone import Zone
@ -68,7 +69,8 @@ class TestManager(TestCase):
self.assertTrue('provider config' in str(ctx.exception))
def test_missing_env_config(self):
with self.assertRaises(ManagerException) as ctx:
# details of the EnvironSecrets will be tested in dedicated tests
with self.assertRaises(EnvironSecretException) as ctx:
Manager(get_config_filename('missing-provider-env.yaml')).sync()
self.assertTrue('missing env var' in str(ctx.exception))


+ 31
- 0
tests/test_octodns_secret_environ.py View File

@ -0,0 +1,31 @@
#
#
#
from os import environ
from unittest import TestCase
from octodns.context import ContextDict
from octodns.secret.environ import EnvironSecretException, EnvironSecrets
class TestEnvironSecrets(TestCase):
def test_environ_secrets(self):
# put some secrets into our env
environ['THIS_EXISTS'] = 'and has a val'
environ['THIS_IS_AN_INT'] = '42'
environ['THIS_IS_A_FLOAT'] = '43.44'
es = EnvironSecrets('env')
source = ContextDict({}, context='xyz')
self.assertEqual('and has a val', es.fetch('THIS_EXISTS', source))
self.assertEqual(42, es.fetch('THIS_IS_AN_INT', source))
self.assertEqual(43.44, es.fetch('THIS_IS_A_FLOAT', source))
with self.assertRaises(EnvironSecretException) as ctx:
es.fetch('DOES_NOT_EXIST', source)
self.assertEqual(
'Incorrect provider config, missing env var DOES_NOT_EXIST, xyz',
str(ctx.exception),
)

Loading…
Cancel
Save