Merge "Added document wrapping command"

This commit is contained in:
Zuul 2019-04-05 18:18:50 +00:00 committed by Gerrit Code Review
commit 6348b83e3c
4 changed files with 238 additions and 28 deletions

View File

@ -421,12 +421,36 @@ Usage:
./pegleg.sh site <options> upload <site_name> --context-marker=<uuid>
Site Secrets Group
==================
------------------
Subgroup of :ref:`site-group`.
A sub-group of site command group, which allows you to perform secrets
level operations for secrets documents of a site.
.. note::
For the CLI commands ``encrypt``, ``decrypt``, ``generate-pki``, and ``wrap``
in the ``secrets`` command
group, which encrypt or decrypt site secrets, two environment variables,
``PEGLEG_PASSPHRASE``, and ``PEGLEG_SALT``, are used to capture the
master passphrase, and the salt needed for encryption and decryption of the
site secrets. The contents of ``PEGLEG_PASSPHRASE``, and ``PEGLEG_SALT``
are not generated by Pegleg, but are created externally, and set by
deployment engineers or tooling.
A minimum length of 24 for master passphrases will be checked by all CLI
commands, which use the ``PEGLEG_PASSPHRASE`` and ``PEGLEG_SALT``.
All other criteria around master passphrase strength are assumed to be
enforced elsewhere.
::
./pegleg.sh site -r <site_repo> -e <extra_repo> secrets <command> <options>
Generate PKI
------------
^^^^^^^^^^^^
Generate certificates and keys according to all PKICatalog documents in the
site using the PKI module. Regenerating certificates can be
@ -454,7 +478,7 @@ Dashes in the document names will be converted to underscores for consistency.
Name of site.
Examples
^^^^^^^^
""""""""
::
@ -472,31 +496,6 @@ Examples
.. _command-line-repository-overrides:
Secrets
-------
A sub-group of site command group, which allows you to perform secrets
level operations for secrets documents of a site.
.. note::
For the CLI commands ``encrypt`` and ``decrypt`` in the ``secrets`` command
group, which encrypt or decrypt site secrets, two environment variables,
``PEGLEG_PASSPHRASE``, and ``PEGLEG_SALT``, are used to capture the
master passphrase, and the salt needed for encryption and decryption of the
site secrets. The contents of ``PEGLEG_PASSPHRASE``, and ``PEGLEG_SALT``
are not generated by Pegleg, but are created externally, and set by a
deployment engineers or tooling.
A minimum length of 24 for master passphrases will be checked by all CLI
commands, which use the ``PEGLEG_PASSPHRASE``. All other criteria around
master passphrase strength are assumed to be enforced elsewhere.
::
./pegleg.sh site -r <site_repo> -e <extra_repo> secrets <command> <options>
Encrypt
^^^^^^^
@ -612,6 +611,58 @@ Example:
secrets decrypt site1 -f \
/opt/security-manifests/site/site1/passwords/password1.yaml
Wrap
^^^^
Wrap bare files (e.g. pem or crt) in a PeglegManagedDocument and optionally encrypt them.
**site_name** (Required).
Name of site.
**-a / --author**
Identifying name of the author generating new certificates. Used
for tracking provenance information in the PeglegManagedDocuments.
An attempt is made to automatically determine this value,
but should be provided.
**-f / --filename**
The relative path to the file to be wrapped.
**-o / --output-path**
The output path for the wrapped file. (default: input path with the extension
replaced with .yaml)
**-s / --schema**
The schema for the document to be wrapped, e.g. deckhand/Certificate/v1
**-n / --name**
The name for the document to be wrapped, e.g. new-cert.
**-l / --layer**
The layer for the document to be wrapped, e.g. site.
**--encrypt / --no-encrypt**
A flag specifying whether to encrypt the output file. (default: True)
Examples
""""""""
::
./pegleg.sh site -r /home/myuser/myrepo \
secrets wrap -a myuser -f secrets/certificates/new_cert.crt \
-o secrets/certificates/new_cert.yaml -s "deckhand/Certificate/v1" \
-n "new-cert" -l site mysite
genesis_bundle
--------------

View File

@ -23,6 +23,7 @@ from pegleg import config
from pegleg import engine
from pegleg.engine import bundle
from pegleg.engine import catalog
from pegleg.engine.secrets import wrap_secret
from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement
from pegleg.engine.util.shipyard_helper import ShipyardHelper
@ -415,6 +416,62 @@ def generate_pki(site_name, author):
click.echo("Generated PKI files written to:\n%s" % '\n'.join(output_paths))
@secrets.command(
'wrap',
help='Wrap bare files (e.g. pem or crt) in a PeglegManagedDocument '
'and encrypt them (by default).')
@click.option(
'-a',
'--author',
'author',
help='Author for the new wrapped file.')
@click.option(
'-f',
'--filename',
'file_name',
help='The relative file path for the file to be wrapped.')
@click.option(
'-o',
'--output-path',
'output_path',
required=False,
help='The output path for the wrapped file. (default: input path with '
'.yaml)')
@click.option(
'-s',
'--schema',
'schema',
help='The schema for the document to be wrapped, e.g. '
'deckhand/Certificate/v1')
@click.option(
'-n',
'--name',
'name',
help='The name for the document to be wrapped, e.g. new-cert')
@click.option(
'-l',
'--layer',
'layer',
help='The layer for the document to be wrapped., e.g. site.')
@click.option(
'--encrypt/--no-encrypt',
'encrypt',
is_flag=True,
default=True,
help='Whether to encrypt the wrapped file (default: True).')
@click.argument('site_name')
def wrap_secret_cli(*, site_name, author, file_name, output_path, schema,
name, layer, encrypt):
"""Wrap a bare secrets file in a YAML and ManagedDocument.
"""
engine.repository.process_repositories(site_name,
overwrite_existing=True)
wrap_secret(author, file_name, output_path, schema,
name, layer, encrypt)
@site.command(
'genesis_bundle',
help='Construct the genesis deployment bundle.')

View File

@ -14,11 +14,14 @@
import logging
import os
import yaml
from pegleg.engine.generators.passphrase_generator import PassphraseGenerator
from pegleg.engine.util.cryptostring import CryptoString
from pegleg.engine.util import definition
from pegleg.engine.util import files
from pegleg.engine.util.pegleg_managed_document import \
PeglegManagedSecretsDocument as PeglegManagedSecret
from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement
__all__ = ('encrypt', 'decrypt', 'generate_passphrases')
@ -141,3 +144,45 @@ def generate_crypto_string(length):
"""
return CryptoString().get_crypto_string(length)
def wrap_secret(author, file_name, output_path, schema,
name, layer, encrypt):
"""Wrap a bare secrets file in a YAML and ManagedDocument.
:param author: author for ManagedDocument
:param file_name: file path for input file
:param output_path: file path for output file
:param schema: schema for wrapped document
:param name: name for wrapped document
:param layer: layer for wrapped document
:param encrypt: whether to encrypt the output doc
"""
if not output_path:
output_path = os.path.splitext(file_name)[0] + ".yaml"
with open(file_name, "r") as in_fi:
data = in_fi.read()
inner_doc = {
"schema": schema,
"data": data,
"metadata": {
"layeringDefinition": {
"abstract": False,
"layer": layer
},
"name": name,
"schema": "metadata/Document/v1",
"storagePolicy": "encrypted" if encrypt else "cleartext"
}
}
managed_secret = PeglegManagedSecret(inner_doc, author=author)
if encrypt:
psm = PeglegSecretManagement(docs=[inner_doc], author=author)
output_doc = psm.get_encrypted_secrets()[0][0]
else:
output_doc = managed_secret.pegleg_document
with open(output_path, "w") as output_fi:
yaml.safe_dump(output_doc, output_fi)

View File

@ -36,6 +36,16 @@ TEST_PARAMS = {
"repo_url": "https://github.com/openstack/airship-treasuremap.git",
}
test_cert = """
-----BEGIN CERTIFICATE-----
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
-----END CERTIFICATE-----
"""
@pytest.mark.skipif(
not test_utils.is_connected(),
@ -552,6 +562,53 @@ class TestSiteSecretsActions(BaseCLIActionTest):
result = self.runner.invoke(cli.site, ['-r', repo_path] + secrets_opts)
assert result.exit_code == 0, result.output
@mock.patch.dict(os.environ, {
"PEGLEG_PASSPHRASE": "123456789012345678901234567890",
"PEGLEG_SALT": "123456"
})
def test_site_secrets_wrap(self):
"""Validates ``generate-pki`` action using local repo path."""
# Scenario:
#
# 1) Encrypt a file in a local repo
repo_path = self.treasuremap_path
file_dir = os.path.join(repo_path, "site", "airship-seaworthy",
"secrets", "certificates")
file_path = os.path.join(file_dir, "test.crt")
output_path = os.path.join(file_dir, "test.yaml")
with open(file_path, "w") as test_crt_fi:
test_crt_fi.write(test_cert)
secrets_opts = ['secrets', 'wrap', "-a", "lm734y", "-f", file_path,
"-s", "deckhand/Certificate/v1",
"-n", "test-certificate", "-l", "site", "--no-encrypt",
self.site_name]
result = self.runner.invoke(cli.site, ["-r", repo_path] + secrets_opts)
assert result.exit_code == 0
with open(output_path, "r") as output_fi:
doc = yaml.safe_load(output_fi)
assert doc["data"]["managedDocument"]["data"] == test_cert
assert doc["data"]["managedDocument"]["schema"] == "deckhand/Certificate/v1"
assert doc["data"]["managedDocument"]["metadata"]["name"] == "test-certificate"
assert doc["data"]["managedDocument"]["metadata"]["layeringDefinition"]["layer"] == "site"
assert doc["data"]["managedDocument"]["metadata"]["storagePolicy"] == "cleartext"
os.remove(output_path)
secrets_opts = ['secrets', 'wrap', "-a", "lm734y", "-f", file_path,
"-o", output_path, "-s", "deckhand/Certificate/v1",
"-n", "test-certificate", "-l", "site",
self.site_name]
result = self.runner.invoke(cli.site, ["-r", repo_path] + secrets_opts)
assert result.exit_code == 0
with open(output_path, "r") as output_fi:
doc = yaml.safe_load(output_fi)
assert "encrypted" in doc["data"]
assert "managedDocument" in doc["data"]
class TestTypeCliActions(BaseCLIActionTest):
"""Tests type-level CLI actions."""