Enable rendering without decrypting
This patchset aims to address least privileged concerns, namely that Pegleg's current behavior is to require decryption of all site documents prior to rendering. Failure to do so leads to a duplicate document error. Operators of Pegleg may not have a valid reason to access secrets that are not being modified during their current workflow, their work may be limited to non-secrets but need to test their changes by rendering the site manifests. To enable this, the get_rendered_documents function has been updated such that if a document is encrypted, the secret value will be converted to a string to pass schema validation, and then used for rendering. This will allow operators of Pegleg to render documents without decrypting secrets. Instead the encrypted string value of the secret will be used. Change-Id: I8656b5496e2225e6eb59727c4f79326a1406147c
This commit is contained in:
parent
417975b596
commit
e4ff07c793
@ -133,7 +133,27 @@ def get_rendered_docs(site_name, validate=True):
|
||||
'', lambda loader, suffix, node: None)
|
||||
for filename in util.definition.site_files(site_name):
|
||||
with open(filename, 'r') as f:
|
||||
documents.extend(list(yaml.safe_load_all(f)))
|
||||
docs = yaml.safe_load_all(f)
|
||||
|
||||
for doc in docs:
|
||||
|
||||
# Managed documents may be encrypted, and require slight
|
||||
# alteration for rendering without decrypting.
|
||||
if doc['schema'] == 'pegleg/PeglegManagedDocument/v1':
|
||||
|
||||
# Do not decrypt secret, but convert it from bytes to
|
||||
# string to pass schema validation.
|
||||
if 'encrypted' in doc['data'].keys():
|
||||
doc['data']['managedDocument']['data'] = doc['data'][
|
||||
'managedDocument']['data'].decode()
|
||||
|
||||
# Append the document if it was encrypted using the
|
||||
# encrypted string. If not, using original value.
|
||||
documents.append(doc['data']['managedDocument'])
|
||||
|
||||
# File was not Pegleg managed, so it can be added directly.
|
||||
else:
|
||||
documents.append(doc)
|
||||
|
||||
rendered_documents, errors = util.deckhand.deckhand_render(
|
||||
documents=documents, validate=validate)
|
||||
|
225
tests/unit/engine/test_site_render.py
Normal file
225
tests/unit/engine/test_site_render.py
Normal file
@ -0,0 +1,225 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import os
|
||||
import shutil
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from pegleg import config
|
||||
from pegleg.engine import site
|
||||
from pegleg.engine.util import files
|
||||
|
||||
_SITE_TEST_STRUCTURE = {
|
||||
'directories': {
|
||||
'secrets': {
|
||||
'directories': {
|
||||
'passphrases': {
|
||||
'files': {}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'files': {}
|
||||
}
|
||||
|
||||
_SITE_DEFINITION = textwrap.dedent(
|
||||
"""
|
||||
---
|
||||
schema: pegleg/SiteDefinition/v1
|
||||
metadata:
|
||||
layeringDefinition: {abstract: false, layer: site}
|
||||
name: %(sitename)s
|
||||
schema: metadata/Document/v1
|
||||
storagePolicy: cleartext
|
||||
data:
|
||||
repositories:
|
||||
global:
|
||||
revision: v1.0
|
||||
url: http://nowhere.com
|
||||
site_type: %(sitename)s
|
||||
...
|
||||
""")
|
||||
|
||||
_LAYERING_DEFINITION = textwrap.dedent(
|
||||
"""
|
||||
---
|
||||
schema: deckhand/LayeringPolicy/v1
|
||||
metadata:
|
||||
schema: metadata/Control/v1
|
||||
name: layering-policy
|
||||
data:
|
||||
layerOrder:
|
||||
- site
|
||||
""")
|
||||
|
||||
_PLAINTEXT_SECRET = textwrap.dedent(
|
||||
"""
|
||||
---
|
||||
schema: deckhand/Passphrase/v1
|
||||
metadata:
|
||||
schema: metadata/Document/v1
|
||||
name: plaintext-secret
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
storagePolicy: cleartext
|
||||
data: dde25e24d263e476cdcd
|
||||
...
|
||||
""")
|
||||
|
||||
_MANAGED_SECRET = textwrap.dedent(
|
||||
"""
|
||||
---
|
||||
schema: pegleg/PeglegManagedDocument/v1
|
||||
metadata:
|
||||
name: managed-secret
|
||||
schema: metadata/Document/v1
|
||||
labels: {}
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
storagePolicy: cleartext
|
||||
data:
|
||||
managedDocument:
|
||||
schema: deckhand/Certificate/v1
|
||||
metadata:
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
name: managed-secret
|
||||
schema: metadata/Document/v1
|
||||
storagePolicy: cleartext
|
||||
data: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSDCCAjCgAwIBAgIUaAjhb47nDilYQacmkdtprW42gHowDQYJKoZIhvcNAQEL
|
||||
BQAwKjETMBEGA1UEChMKS3ViZXJuZXRlczETMBEGA1UEAxMKa3ViZXJuZXRlczAe
|
||||
Fw0xOTA3MTEyMjQ4MDBaFw0yNDA3MDkyMjQ4MDBaMCoxEzARBgNVBAoTCkt1YmVy
|
||||
bmV0ZXMxEzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDVi4YbTvjC+txSiclIJpJGE7YQe9t2nOfEyBykIwbi70GgcVyR
|
||||
vNVN4bXQglG5EOVOv/A6DPQ3VIB4OsidPigwR7p8CCNl9yzVDSnhFtdcDv/Xw0z2
|
||||
aBjvOMS1cBj9QzJIE04vct1sH1BQQ2l3PyOXtOalj1URFm+RLm2Lj+JiCnaxIV3g
|
||||
Rp+CtiyYWwwfW+3GbDJGuXjIlch6zHa3BynoqvZBbWvMQ1hUn/iBKUtxtfHNDtoz
|
||||
Xn5S6Cxzz2l7XaHtotKtlHwkH+U701nvj8vLev0EgDcESbl6yGqgHJIL6UieQlXL
|
||||
4uKm8r9ThIhUuGBnDieydZNuVNpIPRVFeb0jAgMBAAGjZjBkMA4GA1UdDwEB/wQE
|
||||
AwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1UdDgQWBBS7TMynvzvifS00ysY9
|
||||
TGwjdejl3DAfBgNVHSMEGDAWgBS7TMynvzvifS00ysY9TGwjdejl3DANBgkqhkiG
|
||||
9w0BAQsFAAOCAQEAglQGmrNz+BDq2CKq68JSGXhi5PCZ1NwmJmQekI+8jdV8Hd7g
|
||||
urnoZGoMk1i7ZiL8YiOkiZNNWolKSF5whH/COBVBtTkYaPhCKfMDOi2sIVftv0q8
|
||||
jkCIajudTCdf2ZcxB6/T+5wVUipjHtYzylTEaBhg171jc9P9vinSK6WSI6Q8wPCA
|
||||
oPNHlBNg/YAErDuKsfeoBudpRakbHuucDEL9BLwOAoC1bBBQgOP6/j1A+5hVZ9bl
|
||||
d1YXxkDR6odHEndfMTYHAtdiuYY6D2F3c6tESgnuksuAIuHRLnptIKrbC4HzBZG7
|
||||
A8glSdSPBaCjMV8jnl2ge0XnIWbKYWXrWBaLIQ==
|
||||
-----END CERTIFICATE-----
|
||||
...
|
||||
""")
|
||||
|
||||
_ENCRYPTED_SECRET = textwrap.dedent(
|
||||
"""
|
||||
---
|
||||
schema: pegleg/PeglegManagedDocument/v1
|
||||
metadata:
|
||||
name: encrypted-secret
|
||||
schema: metadata/Document/v1
|
||||
labels: {}
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
storagePolicy: cleartext
|
||||
data:
|
||||
managedDocument:
|
||||
schema: deckhand/Passphrase/v1
|
||||
metadata:
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
storagePolicy: encrypted
|
||||
name: encrypted-secret
|
||||
schema: metadata/Document/v1
|
||||
data: !!binary |
|
||||
Z0FBQUFBQmVxeHkwQ2JCYy1lMmFIU0ZCcGJTdUp4OFlyM2t4TmYwRXJndTRVTFE5SFozYVd0eFVJ
|
||||
SkhPRTdCRGppb3NhVjFQRkN0WXhaSmZWdjRHZkZTUzFBU0xGSS1vdWVVYUUxaEVfN1d5RmdUNkFw
|
||||
RXM2NjA9
|
||||
encrypted:
|
||||
by: alexanderhughes
|
||||
at: '2020-04-30T18:45:08.794873'
|
||||
...
|
||||
""")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def create_tmp_site_structure(tmpdir):
|
||||
"""Fixture that creates a temporary site directory structure
|
||||
|
||||
:returns: Function pointer, which, when called, creates a temporary file
|
||||
structure.
|
||||
|
||||
"""
|
||||
def _create_tmp_folder_system(sitename):
|
||||
"""Creates a temporary site folder system.
|
||||
|
||||
:param str sitename: Name of the site.
|
||||
"""
|
||||
# Create site directories and files.
|
||||
p = tmpdir.mkdir("deployment_files")
|
||||
config.set_site_repo(p.strpath)
|
||||
|
||||
site_definition = copy.deepcopy(_SITE_DEFINITION)
|
||||
site_definition = site_definition % {'sitename': sitename}
|
||||
|
||||
test_structure = copy.deepcopy(_SITE_TEST_STRUCTURE)
|
||||
test_structure['files']['site-definition.yaml'] = yaml.safe_load(
|
||||
site_definition)
|
||||
test_structure['files']['layering-definition.yaml'] = yaml.safe_load(
|
||||
_LAYERING_DEFINITION)
|
||||
test_structure['directories']['secrets']['directories']['passphrases'][
|
||||
'files']['plaintext.yaml'] = yaml.safe_load(_PLAINTEXT_SECRET)
|
||||
test_structure['directories']['secrets']['directories']['passphrases'][
|
||||
'files']['managed.yaml'] = yaml.safe_load(_MANAGED_SECRET)
|
||||
test_structure['directories']['secrets']['directories']['passphrases'][
|
||||
'files']['encrypted.yaml'] = yaml.safe_load(_ENCRYPTED_SECRET)
|
||||
|
||||
test_path = os.path.join(p.strpath, files._site_path(sitename))
|
||||
files._create_tree(test_path, tree=test_structure)
|
||||
|
||||
return p.strpath
|
||||
|
||||
try:
|
||||
yield _create_tmp_folder_system
|
||||
finally:
|
||||
temp_path = config.get_site_repo()
|
||||
if temp_path != './' and os.path.exists(temp_path):
|
||||
shutil.rmtree(temp_path, ignore_errors=True)
|
||||
|
||||
|
||||
def test_site_render(create_tmp_site_structure):
|
||||
sitename = "test"
|
||||
rootpath = create_tmp_site_structure(sitename)
|
||||
docs = site.get_rendered_docs(sitename)
|
||||
|
||||
assert len(
|
||||
docs) == 5 # Site-definition, layering definition, 3 secrets documents
|
||||
for doc in docs:
|
||||
if doc['metadata']['name'] == 'plaintext-secret':
|
||||
doc2 = yaml.safe_load(_PLAINTEXT_SECRET)
|
||||
assert doc2 == doc
|
||||
elif doc['metadata']['name'] == 'managed-secret':
|
||||
doc2 = yaml.safe_load(_MANAGED_SECRET)
|
||||
assert doc2['data']['managedDocument'] == doc
|
||||
elif doc['metadata']['name'] == 'encrypted-secret':
|
||||
doc2 = yaml.safe_load(_ENCRYPTED_SECRET)
|
||||
doc2['data']['managedDocument']['data'] = doc2['data'][
|
||||
'managedDocument']['data'].decode()
|
||||
assert doc2['data']['managedDocument'] == doc
|
Loading…
Reference in New Issue
Block a user