Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/dictionary/en-custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ fci
fdp
fedoraproject
fil
Fernet
filesystem
fips
firewalld
Expand Down
7 changes: 7 additions & 0 deletions roles/kustomize_deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ $ ansible-playbook deploy-edpm.yml \
```
This would skip the first stage described in the automation file.

During control-plane stages, `kustomize_deploy` ensures `BarbicanSimpleCryptoKEK`
is present in generated `osp-secret` manifests before `oc apply`. The role
looks up the live secret in the namespace declared on the manifest (for example
`openstack2` in multi-namespace scenarios), reuses an existing cluster key when
present, otherwise keeps keys from the kustomize output, otherwise generates a
Fernet key at deploy time.

### Break point

You can also stop the automated deploy by setting `cifmw_deploy_architecture_stopper`
Expand Down
112 changes: 112 additions & 0 deletions roles/kustomize_deploy/files/osp_secret_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""Helpers for osp-secret keys in kustomize-built manifests."""
import base64
import json
import sys

import yaml

OSP_SECRET_NAME = "osp-secret"


def load_docs(path):
with open(path) as handle:
return [doc for doc in yaml.safe_load_all(handle) if doc is not None]


def find_osp_secret(docs):
for doc in docs:
if doc.get("kind") != "Secret":
continue
if doc.get("metadata", {}).get("name") != OSP_SECRET_NAME:
continue
return doc
return None


def get_secret_namespace(docs):
secret = find_osp_secret(docs)
if secret is None:
return None
return secret.get("metadata", {}).get("namespace")


def get_secret_key(docs, key):
secret = find_osp_secret(docs)
if secret is None:
return None
data = secret.get("data", {})
if key not in data or not data[key]:
return None
return base64.b64decode(data[key]).decode()


def apply_secret_keys(docs, keys):
secret = find_osp_secret(docs)
if secret is None:
return False, []
data = secret.setdefault("data", {})
changed_keys = []
for key, value in keys.items():
if not value:
continue
encoded = base64.b64encode(value.encode()).decode()
if data.get(key) != encoded:
data[key] = encoded
changed_keys.append(key)
return bool(changed_keys), changed_keys


def cmd_has(path):
secret = find_osp_secret(load_docs(path))
sys.exit(0 if secret else 1)


def cmd_get(path, key):
value = get_secret_key(load_docs(path), key)
if value is None:
sys.exit(2)
sys.stdout.write(value)


def cmd_get_namespace(path):
namespace = get_secret_namespace(load_docs(path))
if not namespace:
sys.exit(2)
sys.stdout.write(namespace)


def cmd_set(path, keys_json):
docs = load_docs(path)
keys = json.loads(keys_json)
changed, changed_keys = apply_secret_keys(docs, keys)
if changed:
with open(path, "w") as handle:
yaml.dump_all(docs, handle, default_flow_style=False)
for key in changed_keys:
print("Set: {}".format(key))


def main():
if len(sys.argv) < 3:
sys.exit("usage: osp_secret_manifest.py <has|get|get-namespace|set> <path> [args]")
command = sys.argv[1]
path = sys.argv[2]
if command == "has":
cmd_has(path)
elif command == "get":
if len(sys.argv) != 4:
sys.exit("usage: osp_secret_manifest.py get <path> <key>")
cmd_get(path, sys.argv[3])
elif command == "get-namespace":
cmd_get_namespace(path)
elif command == "set":
if len(sys.argv) != 4:
sys.exit("usage: osp_secret_manifest.py set <path> <json>")
cmd_set(path, sys.argv[3])
else:
sys.exit("unknown command: {}".format(command))


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions roles/kustomize_deploy/tasks/execute_step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@
loop_control:
label: "{{ item }}"

- name: Ensure confidentiality-critical osp-secret keys in kustomize output
ansible.builtin.include_tasks: inject_osp_secret_keys.yml
vars:
cifmw_kustomize_deploy_manifest_path: "{{ _output }}"

- name: "Store kustomized content in artifacts for {{ stage.path }}"
ansible.builtin.copy:
remote_src: true
Expand Down
150 changes: 150 additions & 0 deletions roles/kustomize_deploy/tasks/inject_osp_secret_keys.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
# Copyright Red Hat, Inc.
# All Rights Reserved.
#
# 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.

# Ensure confidentiality-critical osp-secret keys exist in kustomize output
# before oc apply. Prefers the live cluster secret over manifest content.

- name: Check whether kustomize output contains osp-secret
ansible.builtin.command:
argv:
- python3
- "{{ role_path }}/files/osp_secret_manifest.py"
- has
- "{{ cifmw_kustomize_deploy_manifest_path }}"
register: _osp_secret_manifest_check
changed_when: false
failed_when: false

- name: Ensure BarbicanSimpleCryptoKEK in osp-secret manifest
when: _osp_secret_manifest_check.rc == 0
vars:
_barbican_kek_key: BarbicanSimpleCryptoKEK
_nolog: "{{ cifmw_nolog | default(true) | bool }}"
block:
- name: Reset BarbicanSimpleCryptoKEK selection for this manifest
ansible.builtin.set_fact:
_barbican_simple_crypto_kek: ""
_decoded_cluster_barbican_kek: ""

- name: Read osp-secret namespace from kustomize manifest
ansible.builtin.command:
argv:
- python3
- "{{ role_path }}/files/osp_secret_manifest.py"
- get-namespace
- "{{ cifmw_kustomize_deploy_manifest_path }}"
register: _manifest_osp_secret_namespace
changed_when: false
failed_when: false

- name: Set osp-secret namespace for cluster lookup
ansible.builtin.set_fact:
_osp_secret_namespace: >-
{{
_manifest_osp_secret_namespace.stdout
if _manifest_osp_secret_namespace.rc == 0 and
(_manifest_osp_secret_namespace.stdout | length > 0)
else cifmw_openstack_namespace
}}

- name: Get existing osp-secret from cluster
kubernetes.core.k8s_info:
kubeconfig: "{{ cifmw_openshift_kubeconfig }}"
api_key: "{{ cifmw_openshift_token | default(omit) }}"
context: "{{ cifmw_openshift_context | default(omit) }}"
api_version: v1
kind: Secret
name: osp-secret
namespace: "{{ _osp_secret_namespace }}"
register: _existing_osp_secret
no_log: "{{ _nolog }}"

- name: Decode BarbicanSimpleCryptoKEK from cluster osp-secret
when:
- _existing_osp_secret.resources is defined
- _existing_osp_secret.resources | length > 0
- _barbican_kek_key in (_existing_osp_secret.resources[0].data | default({}))
ansible.builtin.set_fact:
_decoded_cluster_barbican_kek: >-
{{ _existing_osp_secret.resources[0].data[_barbican_kek_key] | b64decode }}
no_log: "{{ _nolog }}"

- name: Use BarbicanSimpleCryptoKEK from cluster osp-secret
when:
- _decoded_cluster_barbican_kek is defined
- _decoded_cluster_barbican_kek | length > 0
ansible.builtin.set_fact:
_barbican_simple_crypto_kek: "{{ _decoded_cluster_barbican_kek }}"
no_log: "{{ _nolog }}"

- name: Read BarbicanSimpleCryptoKEK from kustomize manifest
when: _barbican_simple_crypto_kek | length == 0
ansible.builtin.command:
argv:
- python3
- "{{ role_path }}/files/osp_secret_manifest.py"
- get
- "{{ cifmw_kustomize_deploy_manifest_path }}"
- "{{ _barbican_kek_key }}"
register: _manifest_barbican_kek
changed_when: false
failed_when: false
no_log: "{{ _nolog }}"

- name: Use BarbicanSimpleCryptoKEK from kustomize manifest
when:
- _barbican_simple_crypto_kek | length == 0
- _manifest_barbican_kek.rc == 0
- _manifest_barbican_kek.stdout | length > 0
ansible.builtin.set_fact:
_barbican_simple_crypto_kek: "{{ _manifest_barbican_kek.stdout }}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
no_log: "{{ _nolog }}"

- name: Generate BarbicanSimpleCryptoKEK
when: _barbican_simple_crypto_kek | length == 0
ansible.builtin.command:
argv:
- python3
- -c
- >-
from cryptography.fernet import Fernet;
print(Fernet.generate_key().decode())
register: _generated_barbican_kek
changed_when: false
no_log: "{{ _nolog }}"

- name: Set generated BarbicanSimpleCryptoKEK
when: _barbican_simple_crypto_kek | length == 0
ansible.builtin.set_fact:
_barbican_simple_crypto_kek: "{{ _generated_barbican_kek.stdout }}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
no_log: "{{ _nolog }}"

- name: Inject BarbicanSimpleCryptoKEK into kustomize manifest
ansible.builtin.command:
argv:
- python3
- "{{ role_path }}/files/osp_secret_manifest.py"
- set
- "{{ cifmw_kustomize_deploy_manifest_path }}"
- >-
{{
{
_barbican_kek_key: _barbican_simple_crypto_kek
} | to_json
}}
register: _inject_barbican_kek
changed_when: "'Set:' in _inject_barbican_kek.stdout"
no_log: "{{ _nolog }}"
Loading
Loading