Merge "Added document wrapping command"
This commit is contained in:
commit
6348b83e3c
@ -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
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -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.')
|
||||||
|
@ -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)
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user