CPD-101: Key Storage Encryption¶
Metadata¶
- CPD Version: 1
- Status: Accepted
Description¶
Today we are holding keys the same way that secrets are used in some container managers.
Instead of holding keys in base64 and assuming that the Storage
instance is used
only for Commissaire, we could encrypt keys, credentials and other secrets to add
another layer of safety.
Rationale¶
The likelihood of having a Storage
system that is used only for Commissaire seems
low. More than likely the same instance will be used for other applications as well.
By adding encryption to sensitive data we could mitigate access from those with direct
access to data dumps and storage systems.
Design¶
The StorageService
would be updated to know what data would be backed through Custodia
rather than the other storage handler(s).
- Install and setup of a Custodia instance would be part of a Commissaire install and configuration.
- Custodia would be configured to use an etcd backend.
- Custodia would be configured to use unix socket communication.
- Commissaire would proxy access to Custodia and enforce it’s authentication
- Commissaire would generate an api id and api key for StorageService access
- StorageService would have an api id and api key in it’s config to access Custodia
- Commissaire’s Storage Service would be updated to store credentials and ssh keys via Custodia.
+-----------------+
Data Request | |
+---------------->+ Storage Service | +---------------+
| | | |
+-------+---------+ | Custodia |
| | (Proxied) |
| | (API KEY/ID) |
| +---------------+
v ^
+-------+-------+ |
| | Yes|
| Secret? +---------+
| | |
+---------------+ No|
|
|
v
+-----------------------+
| |
| Storage Handler(s) |
| |
+-----------------------+
Additional Libraries¶
Custodia would be a required subsystem. Custodia would be installed as part of Commissaire.
StorageService Updates¶
The StorageService would need to to know when to use Custodia versus the configured
StorageHandler``(s). It would look at the ``_secrets
attribute on the instance and,
if set to True
would use the secrets handler.
The secrets handler would be automatically added to the StorageHandlerManager
and
would require no special configuration by the operator. However, additional configuration
keys would be added so that the secrets handler could authenticate to the secrets
store.
Lastly, the StorageService
would need to have a way to query for the secrets api endpoint.
There are many possible designs for this. The decision is left up to the implementation.
New HTTP Handler¶
A new handler called secrets
could be added. This would proxy requests back to the
Custodia instance.
AuthenticationManager Updates¶
A way to allow for proxied authentication will be required. This can be done by providing a list of self authenticated endpoints which bypasses the authentication stack and sends the request directly through to the handler.
Model Updates¶
Sensitive items would be pulled out from the Host
model into it’s own model. For simplicity,
the model should be named after the REST endpoint that has traditionally returned the data: HostCreds
.
The models would match based on their primary keys: address
.
The Model
would add a subclass which would be used to house secrets. This new subclass would
be called SecretModel
and would always have it’s contents stored in the secrets store.
Model
|
+------+------+
| |
| SecretModel
| |
Host HostCreds
Example Code¶
These are examples and likely will not work without modification.
Model Updates
class SecretModel(Model):
"""
Parent class for all models which must be stored in the secrets store.
"""
pass
class Host(Model):
"""
Representation of a Host.
"""
_json_type = dict
_attribute_map = {
'address': {'type': str},
'status': {'type': str},
'os': {'type': str},
'cpus': {'type': int},
'memory': {'type': int},
'space': {'type': int},
'last_check': {'type': str},
'source': {'type': str},
}
_attribute_defaults = {
'address': '', 'status': '', 'os': '', 'cpus': 0,
'memory': 0, 'space': 0, 'last_check': '', 'source': ''}
_primary_key = 'address'
class HostCreds(SecretModel):
"""
Representation of Host credentials.
"""
_json_type = dict
_attribute_map = {
'address': {'type': str},
'ssh_priv_key': {'type': str},
'remote_user': {'type': str},
}
_attribute_defaults = {
'ssh_priv_key': '',
'remote_user': 'root',
}
_primary_key = 'address'
StorageHandlerManager Updates
def _get_handler(self, model):
"""
Looks up, and if necessary instantiates, a StoreHandler instance
for the given model. If the model stores secrets the secrets
handler is used. Raises KeyError if no handler is registered
for that type of model.
"""
if issubclass(model.__class__, models.SecretModel):
handler = self._handlers.get('secret') # Just an example
else:
handler = self._handlers.get(type(model))
if handler is None:
# Let this raise a KeyError if the registry lookup fails.
handler_type, config, model_types = self._registry[type(model)]
handler = handler_type(config)
self._handlers.update({mt: handler for mt in model_types})
return handler
Secrets Handler
def _register(router):
"""
Sets up routing for secrets.
:param router: Router instance to attach to.
:type router: commissaire_http.router.Router
:returns: The router.
:rtype: commissaire_http.router.Router
"""
from commissaire_http.constants import ROUTING_RX_PARAMS
router.connect(
R'/api/v0/secrets/',
controller=proxy_secrets,
conditions={'method': ['GET', 'PUT', 'POST', 'DELETE']})
@BasicHandler
def proxy_secrets(message, bus):
"""
Proxy secrets back to Custodia
:param message: jsonrpc message structure.
:type message: dict
:param bus: Bus instance.
:type bus: commissaire_http.bus.Bus
:returns: A jsonrpc structure.
:rtype: dict
"""
try:
# Use unix socket to proxy
except:
# ...
AuthenticationManager Update
def __init__(
self, app, authenticators=[], self_auths=['/api/v0/secrets']):
"""
Initializes a new AuthenticationManager instance.
:param app: A WSGI app to wrap.
:type app: instance
:param authenticators: Configured Authenticator instances to utilize.
:type authenticators: list
:param self_auths: List of endpoints which have their own authentication
:type self_auths: list
"""
self._app = app
self.authenticators = authenticators
self.self_auths = self_auths
def __call__(self, environ, start_response):
"""
...
"""
# If the endpoint self authenticates then pass directly
# to the handler
if environ['PATH'] in self.self_auths:
return self._app(environ, start_response)
# ...
Example Configuration¶
StorageService
{
"custodia_api_id": "storage_service",
"custodia_api_key": "$API_KEY",
"storage_handlers": [
{
"name": "etcd",
"server_url": "http://127.0.0.1:2379",
"models": ["*"]
}
],
"debug": false
}
Custodia
[DEFAULT]
libdir = /var/lib/commissaire/custodia/
logdir = /var/log/commissaire/
rundir = /var/run
[global]
debug = false
server_socket = ${rundir}/custodia.sock
auditlog = ${logdir}/custodia-audit.log
[store:etcd]
etcd_server = {{ etcd_server }}
etcd_port = {{ etcd_port }}
handler = EtcdStore
namespace = custodia_commissaire_data
[store:encrypted_etcd]
handler = EncryptedOverlay
backing_store = etcd
master_key = ${libdir}/secrets.key
master_enctype = A256CBC-HS512
autogen_master_key = true
[auth:creds]
handler = SimpleAuthKeys
id_header = CUSTODIA_AUTH_ID
key_header = CUSTODIA_KEY_ID
store = etcd
store_namespace = custodia_commissaire_api
[authz:paths]
handler = SimplePathAuthz
paths = /. /secrets
[/]
handler = Root
[/secrets]
handler = Secrets
store = encrypted_etcd
Documentation Updates¶
Documentation would need to be updated to clarify the following:
- Sensitive data is stored encrypted
- How to access the secrets store
- The bus component will need to be considered secure
- Some bus backends will need to use stunnel (and include an example)
- Information pointing to Custodia
Migration Tool¶
A migration tool to push secrets into the secrets store.
Future Considerations¶
- Commissaire could use Custodia for authentication/authorization
- Commissaire could provide a backend for Custodia to use it as authentication
Checklist¶
- breaks API backward compatibility
- breaks user interaction backward compatibility
- requires new or replaces current libraries
User Story¶
In order to increase security I would like encryption to be added to secrets storage so that those with access to the data do not get direct access to sensitive data.
Acceptance Criteria¶
- Verify a card for installing Custodia is created
- Verify a card is created for adding/updating models and updating model usage
- Verify a card is created for updating commissaire-service
- Verify a card is created for updating commissaire-http
- Verify a card is created for allowing commissaire-storage-service to query for the http endpoint