Skip to content
Draft
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
30 changes: 30 additions & 0 deletions hooks/playbooks/barbican-secret-store-migrate/admin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
# Run barbican-manage secret migrate in the API pod.
#
# Operator CLI: reads barbican.conf / DB in-process and calls
# rewrap_secret. No Keystone token or HTTP to Barbican.
#
# Expects: cifmw_barbican_migrate_api_exec, *_admin_store_id,
# *_admin_secret_id, optional *_admin_dry_run.

- name: "barbican-manage secret migrate ({{ cifmw_barbican_migrate_case }})"
ansible.builtin.command:
argv: "{{ _manage_argv }}"
vars:
_manage_argv: "{{
cifmw_barbican_migrate_api_exec
+ [
'barbican-manage', 'secret', 'migrate',
'--dest-store-id', cifmw_barbican_migrate_admin_store_id,
'--secret-id', cifmw_barbican_migrate_admin_secret_id,
'--error-file',
'/tmp/cifmw-barbican-manage-secret-migrate-errors.jsonl',
]
+ (
['--dry-run']
if (cifmw_barbican_migrate_admin_dry_run | default(false) | bool)
else []
)
}}"
register: _migrate_admin
changed_when: false
35 changes: 35 additions & 0 deletions hooks/playbooks/barbican-secret-store-migrate/assert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
# Assert a secret is on the expected Barbican secret-store catalogue entry.
#
# Microversion 1.3 secret metadata always includes secret_store_id and
# secret_store_ref (UUID/href, or null when there is no live payload).
# Use OSC — the patched client negotiates key-manager 1.3.
#
# Expects:
# cifmw_barbican_migrate_assert_label
# cifmw_barbican_migrate_assert_secret_id
# cifmw_barbican_migrate_assert_store (id, store_plugin, crypto_plugin)
# cifmw_barbican_migrate_osc_exec

- name: "{{ cifmw_barbican_migrate_assert_label }}: get secret metadata"
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'openstack', 'secret', 'get',
cifmw_barbican_migrate_assert_secret_id,
'-f', 'json',
] }}"
register: _assert_secret_get
changed_when: false
no_log: true

- name: "{{ cifmw_barbican_migrate_assert_label }}: assert secret_store_id"
ansible.builtin.assert:
that:
- "'Secret store id' in (_assert_secret_get.stdout | from_json)"
- "'Secret store href' in (_assert_secret_get.stdout | from_json)"
- (_assert_secret_get.stdout | from_json)['Secret store id'] == cifmw_barbican_migrate_assert_store.id
- cifmw_barbican_migrate_assert_store.id in ((_assert_secret_get.stdout | from_json)['Secret store href'] | default(''))
fail_msg: >-
{{ cifmw_barbican_migrate_assert_label }}: expected secret_store_id
'{{ cifmw_barbican_migrate_assert_store.id }}', got
'{{ (_assert_secret_get.stdout | from_json)['Secret store id'] | default('') }}'.
79 changes: 79 additions & 0 deletions hooks/playbooks/barbican-secret-store-migrate/metaonly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
# Metadata-only secrets have no payload and must fail migrate (HTTP 400).

- name: Metadata-only migrate must conflict
block:
- name: Create metadata-only secret
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'openstack', 'secret', 'store',
'--name', 'ci-migrate-metaonly',
'-f', 'value', '-c', 'Secret href',
] }}"
register: _meta_create
changed_when: false
no_log: true

- name: Parse metadata-only secret
ansible.builtin.set_fact:
_meta_secret_href: "{{ _meta_create.stdout | trim }}"

- name: Migrate metadata-only secret (expect conflict)
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'openstack', 'secret', 'migrate',
_meta_secret_href,
'--secret-store', cifmw_barbican_migrate_dst_store.id,
] }}"
register: _meta_migrate
changed_when: false
failed_when: false

- name: Assert metadata-only migrate failed
ansible.builtin.assert:
that:
- _meta_migrate.rc != 0
- >-
('400' in (_meta_migrate.stderr | default('')))
or ('Bad Request' in (_meta_migrate.stderr | default('')))
or ('no stored payload' in (_meta_migrate.stderr | default('')))
or ('400' in (_meta_migrate.stdout | default('')))
or ('Bad Request' in (_meta_migrate.stdout | default('')))
or ('no stored payload' in (_meta_migrate.stdout | default('')))
fail_msg: >-
Metadata-only migrate should fail with HTTP 400 / Bad Request.
rc={{ _meta_migrate.rc }}
stderr={{ _meta_migrate.stderr | default('') }}

- name: Get metadata-only secret (expect null secret_store_id)
when: _meta_secret_href is defined
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'openstack', 'secret', 'get',
_meta_secret_href, '-f', 'json',
] }}"
register: _meta_get
changed_when: false
no_log: true

- name: Assert metadata-only secret has null store fields
when: _meta_get is defined and _meta_get is not skipped
ansible.builtin.assert:
that:
- "'Secret store id' in (_meta_get.stdout | from_json)"
- "'Secret store href' in (_meta_get.stdout | from_json)"
- (_meta_get.stdout | from_json)['Secret store id'] is none
- (_meta_get.stdout | from_json)['Secret store href'] is none
fail_msg: >-
Metadata-only secret should include Secret store id/href as null.
body={{ _meta_get.stdout | default('') }}
always:
- name: Delete metadata-only secret
when: _meta_secret_href is defined
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'openstack', 'secret', 'delete', _meta_secret_href,
] }}"
changed_when: false
failed_when: false
no_log: true
152 changes: 152 additions & 0 deletions hooks/playbooks/barbican-secret-store-migrate/migrate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
# Live-migrate Barbican secrets across every enabled secret store.
#
# Store set and catalogue UUIDs come from GET /v1/secret-stores
# (openstacksdk in the openstackclient pod) — the same ids the migrate
# API and microversion 1.3 secret_store_id assert require. Creates on
# the global-default store (OSC has no preferred-store command), then
# for each other store runs: OSC round-trip, barbican-manage secret
# migrate round-trip, dry-run, and (once) a metadata-only conflict
# against the first destination.
#
# Optional: cifmw_barbican_migrate_secret_type (e.g. symmetric) is
# passed through to openstack secret store when set.
#
# Wire as post_tests (after tempest on the default store):
#
# post_tests:
# - name: 90 Barbican secret-store migrate
# type: playbook
# source: "{{ ci_framework_base_src_dir }}/hooks/playbooks/barbican-secret-store-migrate/migrate.yml"
#
# Related: OSPRH-35280 / OSPRH-35261 / RHOSSTRAT-1435

- name: Test Barbican secret-store migrate via GET secret_store_id
hosts: "{{ cifmw_target_hook_host | default('localhost') }}"
gather_facts: false
vars:
cifmw_barbican_migrate_namespace: "{{ namespace | default(cifmw_openstack_namespace) | default('openstack') }}"
cifmw_barbican_migrate_osc_pod_name: openstackclient
cifmw_barbican_migrate_api_label: component=barbican-api
cifmw_barbican_migrate_api_container: barbican-api
_oc_env:
KUBECONFIG: "{{ cifmw_openshift_kubeconfig }}"
PATH: "{{ cifmw_path }}"
environment: "{{ _oc_env }}"
tasks:
- name: Get openstackclient pod
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: Pod
namespace: "{{ cifmw_barbican_migrate_namespace }}"
name: "{{ cifmw_barbican_migrate_osc_pod_name }}"
register: _osc_pod_info

- name: Get Barbican API pods
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: Pod
namespace: "{{ cifmw_barbican_migrate_namespace }}"
label_selectors:
- "{{ cifmw_barbican_migrate_api_label }}"
field_selectors:
- status.phase=Running
register: _barbican_api_pod_info

- name: Fail if required pods are missing
ansible.builtin.assert:
that:
- _osc_pod_info.resources | length > 0
- _barbican_api_pod_info.resources | length > 0
fail_msg: >-
Need Running openstackclient and a pod labelled
{{ cifmw_barbican_migrate_api_label }} in
{{ cifmw_barbican_migrate_namespace }}.

- name: Set pod names and shared oc-exec argv prefixes
ansible.builtin.set_fact:
cifmw_barbican_migrate_osc_pod: "{{ _osc_pod_info.resources[0].metadata.name }}"
cifmw_barbican_migrate_api_pod: "{{ _barbican_api_pod_info.resources[0].metadata.name }}"
cifmw_barbican_migrate_osc_exec: "{{ [
'oc', '-n', cifmw_barbican_migrate_namespace,
'exec', _osc_pod_info.resources[0].metadata.name, '--',
] }}"
cifmw_barbican_migrate_api_exec: "{{ [
'oc', '-n', cifmw_barbican_migrate_namespace,
'exec', _barbican_api_pod_info.resources[0].metadata.name,
'-c', cifmw_barbican_migrate_api_container, '--',
] }}"

# OSC has no secret-store list command; use openstacksdk in the OSC pod.
- name: List secret stores via openstacksdk
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'python3', '-c', _list_stores_py,
] }}"
vars:
_list_stores_py: |
import json
import openstack
conn = openstack.connect()
stores = []
for s in conn.key_manager.secret_stores():
stores.append({
'id': s.id,
'store_plugin': s.secret_store_plugin or '',
'crypto_plugin': s.crypto_plugin or '',
'global_default': bool(s.global_default),
})
print(json.dumps(stores))
register: _stores_raw
changed_when: false
no_log: true

- name: Parse secret stores
ansible.builtin.set_fact:
_secret_stores: "{{ _stores_raw.stdout | from_json }}"

- name: Require at least two stores and one global default
vars:
_defaults: "{{ _secret_stores | selectattr('global_default', 'in', [true, True, 'True', 'true', 1, '1']) | list }}"
ansible.builtin.assert:
that:
- _secret_stores | length >= 2
- _defaults | length == 1
fail_msg: >-
Need >=2 secret stores and exactly one global_default from
GET /v1/secret-stores (got {{ _secret_stores | length }} stores,
{{ _defaults | length }} defaults). Stores: {{ _secret_stores }}

- name: Set global-default source and destinations
vars:
_defaults: "{{ _secret_stores | selectattr('global_default', 'in', [true, True, 'True', 'true', 1, '1']) | list }}"
ansible.builtin.set_fact:
_store_src: "{{ _defaults[0] }}"
_store_destinations: "{{ _secret_stores | rejectattr('id', 'equalto', _defaults[0].id) | list }}"

- name: Choose a payload for this run
ansible.builtin.set_fact:
_payload: "ci-migrate-payload-{{ 999999 | random }}"

- name: Migrate from global default to each other store
ansible.builtin.include_tasks: pair.yml
loop: "{{ _store_destinations }}"
loop_control:
loop_var: _store_dst
extended: true
label: "{{ _store_src.store_plugin }}/{{ _store_src.crypto_plugin }} → {{ _store_dst.store_plugin }}/{{ _store_dst.crypto_plugin }}"
vars:
cifmw_barbican_migrate_src_store: "{{ _store_src }}"
cifmw_barbican_migrate_dst_store: "{{ _store_dst }}"
# Pass-through; pair/scenario use | default('') — do not self-assign
# (ansible-lint jinja[invalid] recursive loop).
cifmw_barbican_migrate_pair_label: >-
{{ (_store_src.crypto_plugin or _store_src.store_plugin) ~ '_to_'
~ (_store_dst.crypto_plugin or _store_dst.store_plugin) }}
cifmw_barbican_migrate_run_metaonly: "{{ ansible_loop.first }}"
21 changes: 21 additions & 0 deletions hooks/playbooks/barbican-secret-store-migrate/move.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
# Move _migrate_secret_href/_id onto cifmw_barbican_migrate_target_store
# via OSC or barbican-manage, per cifmw_barbican_migrate_via.

- name: "OSC migrate ({{ cifmw_barbican_migrate_case }})"
when: cifmw_barbican_migrate_via | default('osc') == 'osc'
ansible.builtin.command:
argv: "{{ cifmw_barbican_migrate_osc_exec + [
'openstack', 'secret', 'migrate',
_migrate_secret_href,
'--secret-store', cifmw_barbican_migrate_target_store.id,
] }}"
changed_when: false

- name: "barbican-manage migrate ({{ cifmw_barbican_migrate_case }})"
when: cifmw_barbican_migrate_via | default('osc') == 'admin'
ansible.builtin.include_tasks: admin.yml
vars:
cifmw_barbican_migrate_admin_store_id: "{{ cifmw_barbican_migrate_target_store.id }}"
cifmw_barbican_migrate_admin_secret_id: "{{ _migrate_secret_id }}"
cifmw_barbican_migrate_admin_dry_run: "{{ cifmw_barbican_migrate_dry_run | default(false) | bool }}"
36 changes: 36 additions & 0 deletions hooks/playbooks/barbican-secret-store-migrate/pair.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# Run migrate scenarios for one (src → dst) pair.
# Expects cifmw_barbican_migrate_src_store / dst_store, _payload,
# cifmw_barbican_migrate_pair_label, cifmw_barbican_migrate_secret_type,
# and cifmw_barbican_migrate_run_metaonly (metaonly once across all pairs).

- name: "Round-trip migrate via OSC ({{ cifmw_barbican_migrate_pair_label }})"
ansible.builtin.include_tasks: scenario.yml
vars:
cifmw_barbican_migrate_case: "osc_roundtrip_{{ cifmw_barbican_migrate_pair_label }}"
cifmw_barbican_migrate_via: osc
cifmw_barbican_migrate_roundtrip: true
cifmw_barbican_migrate_dry_run: false
cifmw_barbican_migrate_payload: "{{ _payload }}-osc-{{ cifmw_barbican_migrate_pair_label }}"

- name: "Round-trip migrate via barbican-manage ({{ cifmw_barbican_migrate_pair_label }})"
ansible.builtin.include_tasks: scenario.yml
vars:
cifmw_barbican_migrate_case: "admin_roundtrip_{{ cifmw_barbican_migrate_pair_label }}"
cifmw_barbican_migrate_via: admin
cifmw_barbican_migrate_roundtrip: true
cifmw_barbican_migrate_dry_run: false
cifmw_barbican_migrate_payload: "{{ _payload }}-admin-{{ cifmw_barbican_migrate_pair_label }}"

- name: "Dry-run barbican-manage secret migrate ({{ cifmw_barbican_migrate_pair_label }})"
ansible.builtin.include_tasks: scenario.yml
vars:
cifmw_barbican_migrate_case: "admin_dry_run_{{ cifmw_barbican_migrate_pair_label }}"
cifmw_barbican_migrate_via: admin
cifmw_barbican_migrate_roundtrip: false
cifmw_barbican_migrate_dry_run: true
cifmw_barbican_migrate_payload: "{{ _payload }}-dry-{{ cifmw_barbican_migrate_pair_label }}"

- name: Metadata-only secret must fail migrate
when: cifmw_barbican_migrate_run_metaonly | default(false) | bool
ansible.builtin.include_tasks: metaonly.yml
Loading
Loading