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 importlib.metadata import version as module_version
from json import dumps from json import dumps
from logging import getLogger from logging import getLogger
from os import environ
from sys import stdout from sys import stdout
from . import __version__ from . import __version__
@ -20,6 +19,7 @@ from .processor.meta import MetaProcessor
from .provider.base import BaseProvider from .provider.base import BaseProvider
from .provider.plan import Plan from .provider.plan import Plan
from .provider.yaml import SplitYamlProvider, YamlProvider from .provider.yaml import SplitYamlProvider, YamlProvider
from .secret.environ import EnvironSecrets
from .yaml import safe_load from .yaml import safe_load
from .zone import Zone from .zone import Zone
@ -119,6 +119,8 @@ class Manager(object):
manager_config, enable_checksum manager_config, enable_checksum
) )
self.secret_handlers = {'env': EnvironSecrets('env')}
self.auto_arpa = self._config_auto_arpa(manager_config, auto_arpa) self.auto_arpa = self._config_auto_arpa(manager_config, auto_arpa)
self.global_processors = manager_config.get('processors', []) self.global_processors = manager_config.get('processors', [])
@ -377,22 +379,21 @@ class Manager(object):
if isinstance(v, dict): if isinstance(v, dict):
v = self._build_kwargs(v) v = self._build_kwargs(v)
elif isinstance(v, str): elif isinstance(v, str):
if v.startswith('env/'):
# expand env variables
if '/' in v:
handler, name = v.split('/', 1)
try: try:
env_var = v[4:]
v = environ[env_var]
handler = self.secret_handlers[handler]
except KeyError: 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 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.processor.base import BaseProcessor
from octodns.record import Create, Delete, Record, Update from octodns.record import Create, Delete, Record, Update
from octodns.secret.environ import EnvironSecretException
from octodns.yaml import safe_load from octodns.yaml import safe_load
from octodns.zone import Zone from octodns.zone import Zone
@ -68,7 +69,8 @@ class TestManager(TestCase):
self.assertTrue('provider config' in str(ctx.exception)) self.assertTrue('provider config' in str(ctx.exception))
def test_missing_env_config(self): 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() Manager(get_config_filename('missing-provider-env.yaml')).sync()
self.assertTrue('missing env var' in str(ctx.exception)) 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