Add support for using credstash as a secret store (#8494)
This commit is contained in:
parent
9d9ca64f26
commit
98568b5eb7
5 changed files with 104 additions and 0 deletions
71
homeassistant/scripts/credstash.py
Normal file
71
homeassistant/scripts/credstash.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
"""Script to get, put and delete secrets stored in credstash."""
|
||||||
|
import argparse
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
from homeassistant.util.yaml import _SECRET_NAMESPACE
|
||||||
|
|
||||||
|
REQUIREMENTS = ['credstash==1.13.2', 'botocore==1.4.93']
|
||||||
|
|
||||||
|
|
||||||
|
def run(args):
|
||||||
|
"""Handle credstash script."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=("Modify Home-Assistant secrets in credstash."
|
||||||
|
"Use the secrets in configuration files with: "
|
||||||
|
"!secret <name>"))
|
||||||
|
parser.add_argument(
|
||||||
|
'--script', choices=['credstash'])
|
||||||
|
parser.add_argument(
|
||||||
|
'action', choices=['get', 'put', 'del', 'list'],
|
||||||
|
help="Get, put or delete a secret, or list all available secrets")
|
||||||
|
parser.add_argument(
|
||||||
|
'name', help="Name of the secret", nargs='?', default=None)
|
||||||
|
parser.add_argument(
|
||||||
|
'value', help="The value to save when putting a secret",
|
||||||
|
nargs='?', default=None)
|
||||||
|
|
||||||
|
import credstash
|
||||||
|
import botocore
|
||||||
|
|
||||||
|
args = parser.parse_args(args)
|
||||||
|
table = _SECRET_NAMESPACE
|
||||||
|
|
||||||
|
try:
|
||||||
|
credstash.listSecrets(table=table)
|
||||||
|
except botocore.errorfactory.ClientError:
|
||||||
|
credstash.createDdbTable(table=table)
|
||||||
|
|
||||||
|
if args.action == 'list':
|
||||||
|
secrets = [i['name'] for i in credstash.listSecrets(table=table)]
|
||||||
|
deduped_secrets = sorted(set(secrets))
|
||||||
|
|
||||||
|
print('Saved secrets:')
|
||||||
|
for secret in deduped_secrets:
|
||||||
|
print(secret)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.name is None:
|
||||||
|
parser.print_help()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if args.action == 'put':
|
||||||
|
if args.value:
|
||||||
|
the_secret = args.value
|
||||||
|
else:
|
||||||
|
the_secret = getpass.getpass('Please enter the secret for {}: '
|
||||||
|
.format(args.name))
|
||||||
|
current_version = credstash.getHighestVersion(args.name, table=table)
|
||||||
|
credstash.putSecret(args.name,
|
||||||
|
the_secret,
|
||||||
|
version=int(current_version) + 1,
|
||||||
|
table=table)
|
||||||
|
print('Secret {} put successfully'.format(args.name))
|
||||||
|
elif args.action == 'get':
|
||||||
|
the_secret = credstash.getSecret(args.name, table=table)
|
||||||
|
if the_secret is None:
|
||||||
|
print('Secret {} not found'.format(args.name))
|
||||||
|
else:
|
||||||
|
print('Secret {}={}'.format(args.name, the_secret))
|
||||||
|
elif args.action == 'del':
|
||||||
|
credstash.deleteSecrets(args.name, table=table)
|
||||||
|
print('Deleted secret {}'.format(args.name))
|
|
@ -12,6 +12,11 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
keyring = None
|
keyring = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import credstash
|
||||||
|
except ImportError:
|
||||||
|
credstash = None
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -257,6 +262,15 @@ def _secret_yaml(loader: SafeLineLoader,
|
||||||
_LOGGER.debug("Secret %s retrieved from keyring", node.value)
|
_LOGGER.debug("Secret %s retrieved from keyring", node.value)
|
||||||
return pwd
|
return pwd
|
||||||
|
|
||||||
|
if credstash:
|
||||||
|
try:
|
||||||
|
pwd = credstash.getSecret(node.value, table=_SECRET_NAMESPACE)
|
||||||
|
if pwd:
|
||||||
|
_LOGGER.debug("Secret %s retrieved from credstash", node.value)
|
||||||
|
return pwd
|
||||||
|
except credstash.ItemNotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
_LOGGER.error("Secret %s not defined", node.value)
|
_LOGGER.error("Secret %s not defined", node.value)
|
||||||
raise HomeAssistantError(node.value)
|
raise HomeAssistantError(node.value)
|
||||||
|
|
||||||
|
|
2
pylintrc
2
pylintrc
|
@ -14,6 +14,8 @@ reports=no
|
||||||
# too-few-* - same as too-many-*
|
# too-few-* - same as too-many-*
|
||||||
# abstract-method - with intro of async there are always methods missing
|
# abstract-method - with intro of async there are always methods missing
|
||||||
|
|
||||||
|
generated-members=botocore.errorfactory
|
||||||
|
|
||||||
disable=
|
disable=
|
||||||
abstract-class-little-used,
|
abstract-class-little-used,
|
||||||
abstract-class-not-used,
|
abstract-class-not-used,
|
||||||
|
|
|
@ -115,6 +115,9 @@ blockchain==1.3.3
|
||||||
# homeassistant.components.tts.amazon_polly
|
# homeassistant.components.tts.amazon_polly
|
||||||
boto3==1.4.3
|
boto3==1.4.3
|
||||||
|
|
||||||
|
# homeassistant.scripts.credstash
|
||||||
|
botocore==1.4.93
|
||||||
|
|
||||||
# homeassistant.components.sensor.broadlink
|
# homeassistant.components.sensor.broadlink
|
||||||
# homeassistant.components.switch.broadlink
|
# homeassistant.components.switch.broadlink
|
||||||
broadlink==0.5
|
broadlink==0.5
|
||||||
|
@ -136,6 +139,9 @@ colorlog>2.1,<3
|
||||||
# homeassistant.components.binary_sensor.concord232
|
# homeassistant.components.binary_sensor.concord232
|
||||||
concord232==0.14
|
concord232==0.14
|
||||||
|
|
||||||
|
# homeassistant.scripts.credstash
|
||||||
|
credstash==1.13.2
|
||||||
|
|
||||||
# homeassistant.components.sensor.crimereports
|
# homeassistant.components.sensor.crimereports
|
||||||
crimereports==1.0.0
|
crimereports==1.0.0
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
import logging
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
@ -372,6 +373,16 @@ class TestSecrets(unittest.TestCase):
|
||||||
_yaml = load_yaml(self._yaml_path, yaml_str)
|
_yaml = load_yaml(self._yaml_path, yaml_str)
|
||||||
self.assertEqual({'http': {'api_password': 'yeah'}}, _yaml)
|
self.assertEqual({'http': {'api_password': 'yeah'}}, _yaml)
|
||||||
|
|
||||||
|
@patch.object(yaml, 'credstash')
|
||||||
|
def test_secrets_credstash(self, mock_credstash):
|
||||||
|
"""Test credstash fallback & get_password."""
|
||||||
|
mock_credstash.getSecret.return_value = 'yeah'
|
||||||
|
yaml_str = 'http:\n api_password: !secret http_pw_credstash'
|
||||||
|
_yaml = load_yaml(self._yaml_path, yaml_str)
|
||||||
|
log = logging.getLogger()
|
||||||
|
log.error(_yaml['http'])
|
||||||
|
self.assertEqual({'api_password': 'yeah'}, _yaml['http'])
|
||||||
|
|
||||||
def test_secrets_logger_removed(self):
|
def test_secrets_logger_removed(self):
|
||||||
"""Ensure logger: debug was removed."""
|
"""Ensure logger: debug was removed."""
|
||||||
with self.assertRaises(yaml.HomeAssistantError):
|
with self.assertRaises(yaml.HomeAssistantError):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue