Skip to content

Commit

Permalink
Integrated vault access into server definition file
Browse files Browse the repository at this point in the history
Details:

* Integrated vault access into server definition file. The server definition
  files can now optionally specify the path name of a vault file. If specified,
  the vault file is loaded as well and the secrets for a server defined in
  the vault file are available in the ServerDefinition object as a new `secrets`
  property. (issue #20)

Signed-off-by: Andreas Maier <andreas.r.maier@gmx.de>
  • Loading branch information
andy-maier committed Mar 30, 2021
1 parent adcd6c2 commit 5e13595
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 17 deletions.
6 changes: 6 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ Released: not yet

**Enhancements:**

* Integrated vault access into server definition file. The server definition
files can now optionally specify the path name of a vault file. If specified,
the vault file is loaded as well and the secrets for a server defined in
the vault file are available in the ServerDefinition object as a new `secrets`
property. (issue #20)

**Cleanup:**

**Known issues:**
Expand Down
49 changes: 44 additions & 5 deletions easy_server/_srvdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# limitations under the License.

"""
Encapsulation of a single server definition.
A single server definition including secrets.
"""

from __future__ import absolute_import, print_function
Expand All @@ -21,7 +21,8 @@

class ServerDefinition(object):
"""
Represents a single server definition from a server definition file.
Represents a single server definition from a server definition file,
and optionally the corresponding secrets from a vault file.
Example for a server definition item in a server definition file:
Expand All @@ -33,23 +34,52 @@ class ServerDefinition(object):
access_via: "VPN to dev network"
user_defined: # user-defined part
stuff: morestuff
Example for a corresponding secrets item in a vault file:
.. code-block:: yaml
myserver1: # nickname of the server
host: "10.11.12.13"
username: myuser1
password: mypass1
"""

def __init__(self, nickname, server_dict):
def __init__(self, nickname, server_dict, secrets_dict=None):
"""
Parameters:
nickname (:term:`unicode string`):
Nickname of the server.
server_dict (dict):
Dictionary with the properties of the server item for the
nickname in the server definition file, with unspecified
properties defaulted.
secrets_dict (dict):
Dictionary with the properties of the secrets item for the
nickname in the vault file, or `None` if no vault file is specified
in the server definition file, or if the vault file does not
contain an entry for the server nickname.
"""
self._nickname = nickname
self._description = server_dict['description']
self._contact_name = server_dict.get('contact_name', None)
self._access_via = server_dict.get('access_via', None)
self._user_defined = server_dict.get('user_defined', None)
self._secrets = secrets_dict

def __repr__(self):
secrets = "{...}" if self._secrets else "None"
return "ServerDefinition(" \
"nickname={s._nickname!r}, " \
"description={s._description!r}, " \
"contact_name={s._contact_name!r}, " \
"access_via={s._access_via!r}, " \
"user_defined={s._user_defined!r})". \
format(s=self)
"user_defined={s._user_defined!r}, " \
"secrets={secrets})". \
format(s=self, secrets=secrets)

@property
def nickname(self):
Expand Down Expand Up @@ -101,3 +131,12 @@ def user_defined(self):
user-defined structure.
"""
return self._user_defined

@property
def secrets(self):
"""
dict: Secrets defined in the vault file for the server, or `None` if
no vault file is specified in the server definition file, or if the
vault file does not contain an entry for the server nickname.
"""
return self._secrets
75 changes: 69 additions & 6 deletions easy_server/_srvdef_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,35 @@
"""

from __future__ import absolute_import, print_function
import os
import yaml
import jsonschema

from ._exceptions import ServerDefinitionFileOpenError, \
ServerDefinitionFileFormatError
from ._srvdef import ServerDefinition
from ._vault_file import VaultFile

__all__ = ['ServerDefinitionFile']


# JSON schema describing the structure of the server definition files
SERVER_DEFINITION_FILE_SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "JSON schema for server definition files",
"title": "JSON schema for easy-server server definition files",
"definitions": {},
"type": "object",
"required": [
"servers",
],
"additionalProperties": False,
"properties": {
"vault_file": {
"type": "string",
"description":
"Path name of vault file. Relative path names are relative to "
"the directory of the server definition file",
},
"servers": {
"type": "object",
"description": "The servers in the server definition file",
Expand Down Expand Up @@ -133,25 +141,58 @@ class ServerDefinitionFile(object):
An object of this class is tied to a single server definition file.
The server definition file is loaded when this object is initialized. If
the server definition file specifies a vault file, the vault file is also
loaded.
For a description of the file format, see section
:ref:`Server definition files`.
"""

def __init__(self, filepath):
def __init__(
self, filepath, password=None, use_keyring=True, verbose=False):
"""
Parameters:
filepath (:term:`unicode string`):
Path name of the server definition file.
Path name of the server definition file. Relative path names are
relative to the current directory.
password (:term:`unicode string`):
Password for decrypting the vault file if needed. May be `None`
only if the keyring is used.
use_keyring (bool):
Use the keyring (`True`) or not (`False`) for the password of the
vault file. This applies to both retrieving and storing the
password.
verbose (bool):
Print additional messages. Note that the password prompt (if needed)
is displayed regardless of verbose mode.
Raises:
ServerDefinitionFileOpenError: Error opening server definition file
ServerDefinitionFileFormatError: Invalid server definition file
format
VaultFileOpenError: Error with opening the vault file
VaultFileDecryptError: Error with decrypting the vault file
VaultFileFormatError: Invalid vault file format
"""
self._filepath = filepath
self._filepath = os.path.abspath(filepath)
self._data = _load_server_definition_file(filepath)

self._vault_file = self._data['vault_file']
if self._vault_file:
if not os.path.isabs(self._vault_file):
self._vault_file = os.path.join(
os.path.dirname(self._filepath), self._vault_file)
self._vault = VaultFile(
self._vault_file, password=password, use_keyring=use_keyring,
verbose=verbose)
else:
self._vault = None

# The following attributes are for faster access
self._servers = self._data['servers']
self._server_groups = self._data['server_groups']
Expand All @@ -160,10 +201,22 @@ def __init__(self, filepath):
@property
def filepath(self):
"""
:term:`unicode string`: Path name of the server definition file.
:term:`unicode string`: Absolute path name of the
server definition file.
"""
return self._filepath

@property
def vault_file(self):
"""
:term:`unicode string`: Absolute path name of the vault file specified
in the server definition file, or `None` if no vault file was specified.
Vault files specified with a relative path name are relative to the
directory of the server definition file.
"""
return self._vault_file

def get_server(self, nickname):
"""
Get server definition for a given server nickname.
Expand All @@ -187,7 +240,14 @@ def get_server(self, nickname):
format(nickname, self._filepath))
new_exc.__cause__ = None
raise new_exc # KeyError
return ServerDefinition(nickname, server_dict)
if self._vault:
try:
secrets_dict = self._vault.get_secrets(nickname)
except KeyError:
secrets_dict = None
else:
secrets_dict = None
return ServerDefinition(nickname, server_dict, secrets_dict)

def list_servers(self, nickname):
"""
Expand Down Expand Up @@ -305,6 +365,9 @@ def _load_server_definition_file(filepath):
if 'default' not in data:
data['default'] = None

if 'vault_file' not in data:
data['vault_file'] = None

# Check dependencies in the file

server_nicks = list(data['servers'].keys())
Expand Down
4 changes: 2 additions & 2 deletions easy_server/_vault_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# JSON schema describing the structure of the vault files
VAULT_FILE_SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "JSON schema for vault files used by the easy-server package",
"title": "JSON schema for easy-server vault files",
"definitions": {},
"type": "object",
"required": [
Expand Down Expand Up @@ -57,7 +57,7 @@ class VaultFile(object):
A vault file that specifies the sensitive portion of server definitions,
i.e. the secrets for accessing the servers.
An object of this class is tied to a single "easy-vault" file.
An object of this class is tied to a single vault file.
For a description of the file format, see section :ref:`Vault files`.
Expand Down

0 comments on commit 5e13595

Please sign in to comment.