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> ./pegleg.sh site <options> upload <site_name> --context-marker=<uuid>
Site Secrets Group Site Secrets Group
================== ------------------
Subgroup of :ref:`site-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 PKI
------------ ^^^^^^^^^^^^
Generate certificates and keys according to all PKICatalog documents in the Generate certificates and keys according to all PKICatalog documents in the
site using the PKI module. Regenerating certificates can be 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. Name of site.
Examples Examples
^^^^^^^^ """"""""
:: ::
@ -472,31 +496,6 @@ Examples
.. _command-line-repository-overrides: .. _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 Encrypt
^^^^^^^ ^^^^^^^
@ -612,6 +611,58 @@ Example:
secrets decrypt site1 -f \ secrets decrypt site1 -f \
/opt/security-manifests/site/site1/passwords/password1.yaml /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 genesis_bundle
-------------- --------------

View File

@ -23,6 +23,7 @@ from pegleg import config
from pegleg import engine from pegleg import engine
from pegleg.engine import bundle from pegleg.engine import bundle
from pegleg.engine import catalog 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.pegleg_secret_management import PeglegSecretManagement
from pegleg.engine.util.shipyard_helper import ShipyardHelper 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)) 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( @site.command(
'genesis_bundle', 'genesis_bundle',
help='Construct the genesis deployment bundle.') help='Construct the genesis deployment bundle.')

View File

@ -14,11 +14,14 @@
import logging import logging
import os import os
import yaml
from pegleg.engine.generators.passphrase_generator import PassphraseGenerator from pegleg.engine.generators.passphrase_generator import PassphraseGenerator
from pegleg.engine.util.cryptostring import CryptoString from pegleg.engine.util.cryptostring import CryptoString
from pegleg.engine.util import definition from pegleg.engine.util import definition
from pegleg.engine.util import files 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 from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement
__all__ = ('encrypt', 'decrypt', 'generate_passphrases') __all__ = ('encrypt', 'decrypt', 'generate_passphrases')
@ -141,3 +144,45 @@ def generate_crypto_string(length):
""" """
return CryptoString().get_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", "repo_url": "https://github.com/openstack/airship-treasuremap.git",
} }
test_cert = """
-----BEGIN CERTIFICATE-----
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
-----END CERTIFICATE-----
"""
@pytest.mark.skipif( @pytest.mark.skipif(
not test_utils.is_connected(), not test_utils.is_connected(),
@ -552,6 +562,53 @@ class TestSiteSecretsActions(BaseCLIActionTest):
result = self.runner.invoke(cli.site, ['-r', repo_path] + secrets_opts) result = self.runner.invoke(cli.site, ['-r', repo_path] + secrets_opts)
assert result.exit_code == 0, result.output 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): class TestTypeCliActions(BaseCLIActionTest):
"""Tests type-level CLI actions.""" """Tests type-level CLI actions."""