1
0
mirror of https://github.com/github/octodns.git synced 2024-05-11 05:55:00 +00:00

Secrets handlers support

Add functionality that enables configurable secrets sources, with a
hard-coded `env` that provides the existing environmental variable
support.
This commit is contained in:
Ross McFarland
2024-02-10 20:58:21 -05:00
parent 429a6be9ff
commit ca2c7112a1
7 changed files with 102 additions and 15 deletions

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

View File

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

11
octodns/secret/base.py Normal file
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
octodns/secret/environ.py Normal file
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

View File

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

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))

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),
)