diff --git a/homeassistant/config.py b/homeassistant/config.py index ee48ece67ab..6be0e776f3f 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -23,7 +23,7 @@ from homeassistant.const import ( from homeassistant.core import callback, DOMAIN as CONF_CORE from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import get_component, get_platform -from homeassistant.util.yaml import load_yaml +from homeassistant.util.yaml import load_yaml, SECRET_YAML import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as date_util, location as loc_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM @@ -70,8 +70,8 @@ frontend: config: http: - # Uncomment this to add a password (recommended!) - # api_password: PASSWORD + # Secrets are defined in the file secrets.yaml + # api_password: !secret http_password # Uncomment this if you are using SSL/TLS, running in Docker container, etc. # base_url: example.duckdns.org:8123 @@ -111,6 +111,11 @@ group: !include groups.yaml automation: !include automations.yaml script: !include scripts.yaml """ +DEFAULT_SECRETS = """ +# Use this file to store secrets like usernames and passwords. +# Learn more at https://home-assistant.io/docs/configuration/secrets/ +http_password: welcome +""" PACKAGES_CONFIG_SCHEMA = vol.Schema({ @@ -181,6 +186,7 @@ def create_default_config(config_dir, detect_location=True): CONFIG_PATH as CUSTOMIZE_CONFIG_PATH) config_path = os.path.join(config_dir, YAML_CONFIG_FILE) + secret_path = os.path.join(config_dir, SECRET_YAML) version_path = os.path.join(config_dir, VERSION_FILE) group_yaml_path = os.path.join(config_dir, GROUP_CONFIG_PATH) automation_yaml_path = os.path.join(config_dir, AUTOMATION_CONFIG_PATH) @@ -209,7 +215,7 @@ def create_default_config(config_dir, detect_location=True): # Writing files with YAML does not create the most human readable results # So we're hard coding a YAML template. try: - with open(config_path, 'w') as config_file: + with open(config_path, 'wt') as config_file: config_file.write("homeassistant:\n") for attr, _, _, description in DEFAULT_CORE_CONFIG: @@ -221,6 +227,9 @@ def create_default_config(config_dir, detect_location=True): config_file.write(DEFAULT_CONFIG) + with open(secret_path, 'wt') as secret_file: + secret_file.write(DEFAULT_SECRETS) + with open(version_path, 'wt') as version_file: version_file.write(__version__) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 7d8789c507b..c484fe3372a 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -21,7 +21,7 @@ from homeassistant.exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) _SECRET_NAMESPACE = 'homeassistant' -_SECRET_YAML = 'secrets.yaml' +SECRET_YAML = 'secrets.yaml' __SECRET_CACHE = {} # type: Dict @@ -133,7 +133,7 @@ def _include_dir_merge_named_yaml(loader: SafeLineLoader, mapping = OrderedDict() # type: OrderedDict loc = os.path.join(os.path.dirname(loader.name), node.value) for fname in _find_files(loc, '*.yaml'): - if os.path.basename(fname) == _SECRET_YAML: + if os.path.basename(fname) == SECRET_YAML: continue loaded_yaml = load_yaml(fname) if isinstance(loaded_yaml, dict): @@ -146,7 +146,7 @@ def _include_dir_list_yaml(loader: SafeLineLoader, """Load multiple files from directory as a list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) return [load_yaml(f) for f in _find_files(loc, '*.yaml') - if os.path.basename(f) != _SECRET_YAML] + if os.path.basename(f) != SECRET_YAML] def _include_dir_merge_list_yaml(loader: SafeLineLoader, @@ -156,7 +156,7 @@ def _include_dir_merge_list_yaml(loader: SafeLineLoader, node.value) # type: str merged_list = [] # type: List for fname in _find_files(loc, '*.yaml'): - if os.path.basename(fname) == _SECRET_YAML: + if os.path.basename(fname) == SECRET_YAML: continue loaded_yaml = load_yaml(fname) if isinstance(loaded_yaml, list): @@ -216,7 +216,7 @@ def _env_var_yaml(loader: SafeLineLoader, def _load_secret_yaml(secret_path: str) -> Dict: """Load the secrets yaml from path.""" - secret_path = os.path.join(secret_path, _SECRET_YAML) + secret_path = os.path.join(secret_path, SECRET_YAML) if secret_path in __SECRET_CACHE: return __SECRET_CACHE[secret_path] @@ -264,6 +264,8 @@ def _secret_yaml(loader: SafeLineLoader, _LOGGER.debug("Secret %s retrieved from keyring", node.value) return pwd + global credstash # pylint: disable=invalid-name + if credstash: try: pwd = credstash.getSecret(node.value, table=_SECRET_NAMESPACE) @@ -272,6 +274,9 @@ def _secret_yaml(loader: SafeLineLoader, return pwd except credstash.ItemNotFound: pass + except Exception: # pylint: disable=broad-except + # Catch if package installed and no config + credstash = None _LOGGER.error("Secret %s not defined", node.value) raise HomeAssistantError(node.value) diff --git a/tests/test_config.py b/tests/test_config.py index 1cb5e00bee9..400acbef17a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -16,6 +16,7 @@ from homeassistant.const import ( CONF_TIME_ZONE, CONF_ELEVATION, CONF_CUSTOMIZE, __version__, CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT) from homeassistant.util import location as location_util, dt as dt_util +from homeassistant.util.yaml import SECRET_YAML from homeassistant.util.async import run_coroutine_threadsafe from homeassistant.helpers.entity import Entity from homeassistant.components.config.group import ( @@ -32,6 +33,7 @@ from tests.common import ( CONFIG_DIR = get_test_config_dir() YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) +SECRET_PATH = os.path.join(CONFIG_DIR, SECRET_YAML) VERSION_PATH = os.path.join(CONFIG_DIR, config_util.VERSION_FILE) GROUP_PATH = os.path.join(CONFIG_DIR, GROUP_CONFIG_PATH) AUTOMATIONS_PATH = os.path.join(CONFIG_DIR, AUTOMATIONS_CONFIG_PATH) @@ -62,6 +64,9 @@ class TestConfig(unittest.TestCase): if os.path.isfile(YAML_PATH): os.remove(YAML_PATH) + if os.path.isfile(SECRET_PATH): + os.remove(SECRET_PATH) + if os.path.isfile(VERSION_PATH): os.remove(VERSION_PATH) @@ -85,6 +90,7 @@ class TestConfig(unittest.TestCase): config_util.create_default_config(CONFIG_DIR, False) assert os.path.isfile(YAML_PATH) + assert os.path.isfile(SECRET_PATH) assert os.path.isfile(VERSION_PATH) assert os.path.isfile(GROUP_PATH) assert os.path.isfile(AUTOMATIONS_PATH) diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 918a684f322..50e271008a2 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -302,7 +302,7 @@ class TestSecrets(unittest.TestCase): config_dir = get_test_config_dir() yaml.clear_secret_cache() self._yaml_path = os.path.join(config_dir, YAML_CONFIG_FILE) - self._secret_path = os.path.join(config_dir, yaml._SECRET_YAML) + self._secret_path = os.path.join(config_dir, yaml.SECRET_YAML) self._sub_folder_path = os.path.join(config_dir, 'subFolder') self._unrelated_path = os.path.join(config_dir, 'unrelated') @@ -351,7 +351,7 @@ class TestSecrets(unittest.TestCase): def test_secret_overrides_parent(self): """Test loading current directory secret overrides the parent.""" expected = {'api_password': 'override'} - load_yaml(os.path.join(self._sub_folder_path, yaml._SECRET_YAML), + load_yaml(os.path.join(self._sub_folder_path, yaml.SECRET_YAML), 'http_pw: override') self._yaml = load_yaml(os.path.join(self._sub_folder_path, 'sub.yaml'), 'http:\n' @@ -365,7 +365,7 @@ class TestSecrets(unittest.TestCase): def test_secrets_from_unrelated_fails(self): """Test loading secrets from unrelated folder fails.""" - load_yaml(os.path.join(self._unrelated_path, yaml._SECRET_YAML), + load_yaml(os.path.join(self._unrelated_path, yaml.SECRET_YAML), 'test: failure') with self.assertRaises(HomeAssistantError): load_yaml(os.path.join(self._sub_folder_path, 'sub.yaml'),