From 2b15178cbc0f8f2eadb42d0ba853c50fe19bdbe9 Mon Sep 17 00:00:00 2001 From: Ade Lee Date: Wed, 26 Aug 2026 17:32:59 -0400 Subject: [PATCH 1/2] [hooks] Add Barbican secret-store migrate live test hook Tempest cannot prove a secret changed backends. This hook creates a secret on the global default store, migrates it with OSC and barbican-secret-migrate, and checks MariaDB for the expected plugin. Related-Issue: #OSPRH-35280 Assisted-by: Cursor Grok 4.6 Signed-off-by: Ade Lee --- .../barbican-secret-store-migrate/admin.yml | 73 ++++ .../barbican-secret-store-migrate/assert.yml | 93 +++++ .../metaonly.yml | 86 +++++ .../barbican-secret-store-migrate/migrate.yml | 329 ++++++++++++++++++ .../barbican-secret-store-migrate/one.yml | 183 ++++++++++ .../barbican-secret-store-migrate/sql.yml | 26 ++ 6 files changed, 790 insertions(+) create mode 100644 hooks/playbooks/barbican-secret-store-migrate/admin.yml create mode 100644 hooks/playbooks/barbican-secret-store-migrate/assert.yml create mode 100644 hooks/playbooks/barbican-secret-store-migrate/metaonly.yml create mode 100644 hooks/playbooks/barbican-secret-store-migrate/migrate.yml create mode 100644 hooks/playbooks/barbican-secret-store-migrate/one.yml create mode 100644 hooks/playbooks/barbican-secret-store-migrate/sql.yml diff --git a/hooks/playbooks/barbican-secret-store-migrate/admin.yml b/hooks/playbooks/barbican-secret-store-migrate/admin.yml new file mode 100644 index 000000000..6da2e4c1d --- /dev/null +++ b/hooks/playbooks/barbican-secret-store-migrate/admin.yml @@ -0,0 +1,73 @@ +--- +# Run barbican-secret-migrate in the API pod. +# +# That image has the admin CLI; openstackclient does not. The API pod is +# not logged in as project admin, so pass a token issued from +# openstackclient plus Keystone and Key Manager URLs. + +- name: "Run barbican-secret-migrate ({{ cifmw_barbican_migrate_case }})" + when: not (cifmw_barbican_migrate_admin_dry_run | default(false) | bool) + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_api_pod }}" + - -c + - "{{ cifmw_barbican_migrate_api_container }}" + - -- + - barbican-secret-migrate + - --os-auth-type + - v3token + - --os-token + - "{{ _os_token }}" + - --os-auth-url + - "{{ cifmw_barbican_migrate_keystone }}" + - --os-endpoint-override + - "{{ cifmw_barbican_migrate_v1 }}" + - --os-identity-api-version + - "3" + - --secret-store-id + - "{{ cifmw_barbican_migrate_admin_store_id }}" + - --secret-id + - "{{ cifmw_barbican_migrate_admin_secret_id }}" + - --error-file + - /tmp/cifmw-barbican-secret-migrate-errors.jsonl + register: _migrate_admin + changed_when: false + no_log: true + +- name: "Dry-run barbican-secret-migrate ({{ cifmw_barbican_migrate_case }})" + when: cifmw_barbican_migrate_admin_dry_run | default(false) | bool + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_api_pod }}" + - -c + - "{{ cifmw_barbican_migrate_api_container }}" + - -- + - barbican-secret-migrate + - --os-auth-type + - v3token + - --os-token + - "{{ _os_token }}" + - --os-auth-url + - "{{ cifmw_barbican_migrate_keystone }}" + - --os-endpoint-override + - "{{ cifmw_barbican_migrate_v1 }}" + - --os-identity-api-version + - "3" + - --secret-store-id + - "{{ cifmw_barbican_migrate_admin_store_id }}" + - --secret-id + - "{{ cifmw_barbican_migrate_admin_secret_id }}" + - --error-file + - /tmp/cifmw-barbican-secret-migrate-errors.jsonl + - --dry-run + register: _migrate_admin_dry + changed_when: false + no_log: true diff --git a/hooks/playbooks/barbican-secret-store-migrate/assert.yml b/hooks/playbooks/barbican-secret-store-migrate/assert.yml new file mode 100644 index 000000000..2b9f013dc --- /dev/null +++ b/hooks/playbooks/barbican-secret-store-migrate/assert.yml @@ -0,0 +1,93 @@ +--- +# Assert a secret is on the expected Barbican backend. +# +# store_crypto (simple_crypto / p11_crypto): ciphertext is in encrypted_data; +# kek_data.plugin_name is the crypto plugin FQCN. +# +# KMIP and other secret-store plugins: payload is outside MariaDB; +# secret_store_metadata.plugin_name is the store plugin FQCN and there is +# no active encrypted_data row. +# +# Expects: +# cifmw_barbican_migrate_assert_label +# cifmw_barbican_migrate_assert_secret_id +# cifmw_barbican_migrate_assert_store (id, store_plugin, crypto_plugin) + +- name: "{{ cifmw_barbican_migrate_assert_label }}: resolve expected backend" + ansible.builtin.set_fact: + _assert_is_store_crypto: "{{ _store.store_plugin == 'store_crypto' or _store.crypto_plugin in ['simple_crypto', 'p11_crypto'] }}" + _assert_expected_fqcn: "{{ _crypto_fqcn if (_store.store_plugin == 'store_crypto' or _store.crypto_plugin in ['simple_crypto', 'p11_crypto']) else _store_fqcn }}" + vars: + _store: "{{ cifmw_barbican_migrate_assert_store }}" + _crypto_fqcn: "{{ cifmw_barbican_migrate_crypto_fqcns[_store.crypto_plugin] | default('') }}" + _store_fqcn: "{{ cifmw_barbican_migrate_store_fqcns[_store.store_plugin] | default('') }}" + +- name: "{{ cifmw_barbican_migrate_assert_label }}: require a known plugin FQCN" + ansible.builtin.assert: + that: + - _assert_expected_fqcn | length > 0 + fail_msg: >- + {{ cifmw_barbican_migrate_assert_label }}: no FQCN mapping for + store_plugin={{ cifmw_barbican_migrate_assert_store.store_plugin }} + crypto_plugin={{ cifmw_barbican_migrate_assert_store.crypto_plugin }}. + +- name: "{{ cifmw_barbican_migrate_assert_label }}: verify store_crypto backend" + when: _assert_is_store_crypto | bool + block: + - name: "{{ cifmw_barbican_migrate_assert_label }}: read kek_data.plugin_name" + ansible.builtin.include_tasks: sql.yml + vars: + cifmw_barbican_migrate_sql_desc: "{{ cifmw_barbican_migrate_assert_label }} kek plugin_name" + cifmw_barbican_migrate_sql: >- + SELECT k.plugin_name FROM encrypted_data e + JOIN kek_data k ON e.kek_id = k.id + WHERE e.secret_id='{{ cifmw_barbican_migrate_assert_secret_id }}' + AND e.deleted=0; + + - name: "{{ cifmw_barbican_migrate_assert_label }}: assert kek_data.plugin_name" + ansible.builtin.assert: + that: + - cifmw_barbican_migrate_sql_result.stdout | trim == _assert_expected_fqcn + fail_msg: >- + {{ cifmw_barbican_migrate_assert_label }}: expected kek_data.plugin_name + '{{ _assert_expected_fqcn }}' for crypto_plugin + '{{ cifmw_barbican_migrate_assert_store.crypto_plugin }}', got + '{{ cifmw_barbican_migrate_sql_result.stdout | trim }}'. + +- name: "{{ cifmw_barbican_migrate_assert_label }}: verify secret-store plugin backend" + when: not (_assert_is_store_crypto | bool) + block: + - name: "{{ cifmw_barbican_migrate_assert_label }}: read secret_store_metadata.plugin_name" + ansible.builtin.include_tasks: sql.yml + vars: + cifmw_barbican_migrate_sql_desc: "{{ cifmw_barbican_migrate_assert_label }} store plugin_name" + cifmw_barbican_migrate_sql: >- + SELECT value FROM secret_store_metadata + WHERE secret_id='{{ cifmw_barbican_migrate_assert_secret_id }}' + AND `key`='plugin_name' AND deleted=0; + + - name: "{{ cifmw_barbican_migrate_assert_label }}: save store plugin_name" + ansible.builtin.set_fact: + _assert_meta_plugin_name: "{{ cifmw_barbican_migrate_sql_result.stdout | trim }}" + + - name: "{{ cifmw_barbican_migrate_assert_label }}: count leftover ciphertext" + ansible.builtin.include_tasks: sql.yml + vars: + cifmw_barbican_migrate_sql_desc: "{{ cifmw_barbican_migrate_assert_label }} encrypted_data count" + cifmw_barbican_migrate_sql: >- + SELECT COUNT(*) FROM encrypted_data + WHERE secret_id='{{ cifmw_barbican_migrate_assert_secret_id }}' + AND deleted=0; + + - name: "{{ cifmw_barbican_migrate_assert_label }}: assert metadata plugin and no ciphertext" + ansible.builtin.assert: + that: + - _assert_meta_plugin_name == _assert_expected_fqcn + - cifmw_barbican_migrate_sql_result.stdout | trim == "0" + fail_msg: >- + {{ cifmw_barbican_migrate_assert_label }}: expected + secret_store_metadata.plugin_name '{{ _assert_expected_fqcn }}' + and no active encrypted_data for store_plugin + '{{ cifmw_barbican_migrate_assert_store.store_plugin }}'. + plugin_name='{{ _assert_meta_plugin_name }}' + encrypted_data_count='{{ cifmw_barbican_migrate_sql_result.stdout | trim }}'. diff --git a/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml b/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml new file mode 100644 index 000000000..d0bbab6d1 --- /dev/null +++ b/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml @@ -0,0 +1,86 @@ +--- +# Metadata-only secrets have no payload and must fail migrate (HTTP 409). + +- name: Metadata-only migrate must conflict + block: + - name: Create metadata-only secret + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - 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: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - --os-key-manager-api-version + - "1.3" + - secret + - migrate + - "{{ _meta_secret_href }}" + - --secret-store + - "{{ _store_other.id }}" + register: _meta_migrate + changed_when: false + failed_when: false + no_log: true + + - name: Assert metadata-only migrate failed + ansible.builtin.assert: + that: + - _meta_migrate.rc != 0 + - >- + ('409' in (_meta_migrate.stderr | default(''))) + or ('Conflict' in (_meta_migrate.stderr | default(''))) + or ('no stored payload' in (_meta_migrate.stderr | default(''))) + or ('409' in (_meta_migrate.stdout | default(''))) + or ('Conflict' in (_meta_migrate.stdout | default(''))) + or ('no stored payload' in (_meta_migrate.stdout | default(''))) + fail_msg: >- + Metadata-only migrate should fail with HTTP 409 / Conflict. + rc={{ _meta_migrate.rc }} + stderr={{ _meta_migrate.stderr | default('') }} + always: + - name: Delete metadata-only secret + when: _meta_secret_href is defined + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - secret + - delete + - "{{ _meta_secret_href }}" + changed_when: false + failed_when: false + no_log: true diff --git a/hooks/playbooks/barbican-secret-store-migrate/migrate.yml b/hooks/playbooks/barbican-secret-store-migrate/migrate.yml new file mode 100644 index 000000000..27dd49620 --- /dev/null +++ b/hooks/playbooks/barbican-secret-store-migrate/migrate.yml @@ -0,0 +1,329 @@ +--- +# Live-migrate Barbican secrets between SimpleCrypto and PKCS#11. +# +# Asserts movement in MariaDB. store_crypto backends (simple_crypto, p11_crypto) +# keep ciphertext in encrypted_data and are identified by kek_data.plugin_name +# (crypto plugin FQCN). KMIP keeps the payload in the KMIP server and is +# identified by secret_store_metadata.plugin_name (store plugin FQCN) plus +# no active encrypted_data row. A 204 plus a decryptable payload is not enough. +# +# Prerequisite: the control plane must enable both stores, for example: +# +# spec.barbican.template.enabledSecretStores: [simple_crypto, pkcs11] +# spec.barbican.template.globalDefaultSecretStore: pkcs11 +# +# The current Luna/Proteccio hooks only enable pkcs11. This playbook fails +# with a clear error if MariaDB secret_stores has fewer than those two backends. +# +# Secrets are created with OSC on the global-default store (OSC has no +# preferred-store command). Store discovery uses curl GET /v1/secret-stores +# because OSC has no list command. Coverage is OSC round-trip, +# barbican-secret-migrate round-trip, barbican-secret-migrate --dry-run, +# and a metadata-only conflict. +# +# Wire it as a pre_tests hook (fail before tempest) or post_tests: +# +# pre_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 with DB verification + 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_db_account: barbican + cifmw_barbican_migrate_db_name: barbican + cifmw_barbican_migrate_db_secret_key: DatabasePassword + cifmw_barbican_migrate_galera_pod_name: openstack-galera-0 + cifmw_barbican_migrate_galera_container: galera + cifmw_barbican_migrate_osc_pod_name: openstackclient + cifmw_barbican_migrate_api_label: component=barbican-api + cifmw_barbican_migrate_api_container: barbican-api + # kek_data.plugin_name for store_crypto backends (not the friendly config name). + cifmw_barbican_migrate_crypto_fqcns: + simple_crypto: barbican.plugin.crypto.simple_crypto.SimpleCryptoPlugin + p11_crypto: barbican.plugin.crypto.p11_crypto.P11CryptoPlugin + # secret_store_metadata.plugin_name for secret-store plugins. + cifmw_barbican_migrate_store_fqcns: + store_crypto: barbican.plugin.store_crypto.StoreCryptoAdapterPlugin + kmip_plugin: barbican.plugin.kmip_secret_store.KMIPSecretStore + _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 Galera 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_galera_pod_name }}" + register: _galera_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: Get Barbican MariaDBAccount + kubernetes.core.k8s_info: + kubeconfig: "{{ cifmw_openshift_kubeconfig }}" + api_key: "{{ cifmw_openshift_token | default(omit) }}" + context: "{{ cifmw_openshift_context | default(omit) }}" + api_version: mariadb.openstack.org/v1beta1 + kind: MariaDBAccount + namespace: "{{ cifmw_barbican_migrate_namespace }}" + name: "{{ cifmw_barbican_migrate_db_account }}" + register: _db_account_info + + - name: Fail if required pods or MariaDBAccount are missing + ansible.builtin.assert: + that: + - _osc_pod_info.resources | length > 0 + - _galera_pod_info.resources | length > 0 + - _barbican_api_pod_info.resources | length > 0 + - _db_account_info.resources | length > 0 + - _db_account_info.resources[0].spec.userName | default('') | length > 0 + - _db_account_info.resources[0].spec.secret | default('') | length > 0 + fail_msg: >- + Need Running openstackclient, {{ cifmw_barbican_migrate_galera_pod_name }}, + a pod labelled {{ cifmw_barbican_migrate_api_label }}, and + MariaDBAccount/{{ cifmw_barbican_migrate_db_account }} in + {{ cifmw_barbican_migrate_namespace }}. + + - name: Get Barbican DB secret from MariaDBAccount + 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 + namespace: "{{ cifmw_barbican_migrate_namespace }}" + name: "{{ _db_account_info.resources[0].spec.secret }}" + register: _db_secret_info + no_log: true + + - name: Fail if Barbican DB secret is missing the password key + ansible.builtin.assert: + that: + - _db_secret_info.resources | length > 0 + - cifmw_barbican_migrate_db_secret_key in (_db_secret_info.resources[0].data | default({})) + fail_msg: >- + Secret/{{ _db_account_info.resources[0].spec.secret }} must contain + {{ cifmw_barbican_migrate_db_secret_key }}. + + - name: Set pod names and Barbican DB credentials + ansible.builtin.set_fact: + cifmw_barbican_migrate_osc_pod: "{{ _osc_pod_info.resources[0].metadata.name }}" + cifmw_barbican_migrate_galera_pod: "{{ _galera_pod_info.resources[0].metadata.name }}" + cifmw_barbican_migrate_api_pod: "{{ _barbican_api_pod_info.resources[0].metadata.name }}" + cifmw_barbican_migrate_db_user: "{{ _db_account_info.resources[0].spec.userName }}" + _db_password: "{{ _db_secret_info.resources[0].data[cifmw_barbican_migrate_db_secret_key] | b64decode }}" + no_log: true + + - name: Get project-admin token from openstackclient + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - token + - issue + - -f + - value + - -c + - id + register: _os_token_cmd + changed_when: false + no_log: true + + - name: Save project-admin token + ansible.builtin.set_fact: + _os_token: "{{ _os_token_cmd.stdout | trim }}" + no_log: true + + - name: Get Keystone internal URL + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - endpoint + - list + - --service + - identity + - --interface + - internal + - -c + - URL + - -f + - value + register: _keystone_url_cmd + changed_when: false + + - name: Get Key Manager internal URL + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - endpoint + - list + - --service + - key-manager + - --interface + - internal + - -c + - URL + - -f + - value + register: _barbican_url_cmd + changed_when: false + + - name: Normalize Keystone and Barbican v1 URLs + ansible.builtin.set_fact: + cifmw_barbican_migrate_keystone: "{{ _keystone_url_cmd.stdout_lines | first | trim }}" + cifmw_barbican_migrate_v1: "{{ (_barbican_url | regex_replace('/$', '')) + ('' if _barbican_url.rstrip('/').endswith('/v1') else '/v1') }}" + vars: + _barbican_url: "{{ _barbican_url_cmd.stdout_lines | first | trim }}" + + # OSC has no secret-store list command. + - name: List secret stores from Barbican API + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - curl + - -sf + - -H + - "X-Auth-Token: {{ _os_token }}" + - -H + - "OpenStack-API-Version: key-manager 1.3" + - "{{ cifmw_barbican_migrate_v1 }}/secret-stores" + register: _stores_raw + changed_when: false + no_log: true + + - name: Parse secret stores + ansible.builtin.set_fact: + _secret_stores: "{{ _secret_stores | default([]) + [_store_row] }}" + vars: + _store_row: + id: "{{ item.secret_store_ref.split('/')[-1] }}" + store_plugin: "{{ item.secret_store_plugin | default('') }}" + crypto_plugin: "{{ item.crypto_plugin | default('') }}" + global_default: "{{ item.global_default | bool }}" + loop: "{{ (_stores_raw.stdout | from_json).secret_stores }}" + loop_control: + label: "{{ _store_row.store_plugin }}/{{ _store_row.crypto_plugin }} {{ _store_row.id }}" + + - name: Fail if secret-stores list was empty + ansible.builtin.assert: + that: + - _secret_stores | default([]) | length > 0 + fail_msg: >- + GET /v1/secret-stores returned no stores. Multiple secret stores + are not enabled on this control plane. + + - name: Map SimpleCrypto and PKCS#11 stores + ansible.builtin.set_fact: + _stores_simple: "{{ _secret_stores | selectattr('crypto_plugin', 'equalto', 'simple_crypto') | list }}" + _stores_pkcs11: "{{ _secret_stores | selectattr('crypto_plugin', 'equalto', 'p11_crypto') | list }}" + _stores_default: "{{ _secret_stores | selectattr('global_default', 'in', [true, True, 'True', 'true', 1, '1']) | list }}" + + - name: Require SimpleCrypto and PKCS#11 stores + ansible.builtin.assert: + that: + - _stores_simple | length == 1 + - _stores_pkcs11 | length == 1 + - _stores_default | length == 1 + fail_msg: >- + Expected one simple_crypto store, one p11_crypto store, and one + global_default from GET /v1/secret-stores. Enable both backends in + spec.barbican.template.enabledSecretStores before running this + playbook. Stores: {{ _secret_stores }} + + - name: Resolve source (global default) and destination stores + ansible.builtin.set_fact: + _store_src: "{{ _stores_default[0] }}" + _store_other: "{{ _stores_simple[0] if _stores_default[0].crypto_plugin == 'p11_crypto' else _stores_pkcs11[0] }}" + + - name: Run migrate scenarios + vars: + _payload: ci-migrate-payload-{{ 999999 | random }} + block: + - name: Round-trip migrate via OSC + ansible.builtin.include_tasks: one.yml + vars: + cifmw_barbican_migrate_case: osc_roundtrip + cifmw_barbican_migrate_via: osc + cifmw_barbican_migrate_roundtrip: true + cifmw_barbican_migrate_src_store: "{{ _store_src }}" + cifmw_barbican_migrate_dst_store: "{{ _store_other }}" + cifmw_barbican_migrate_payload: "{{ _payload }}-osc" + + - name: Round-trip migrate via barbican-secret-migrate + ansible.builtin.include_tasks: one.yml + vars: + cifmw_barbican_migrate_case: admin_roundtrip + cifmw_barbican_migrate_via: admin + cifmw_barbican_migrate_roundtrip: true + cifmw_barbican_migrate_src_store: "{{ _store_src }}" + cifmw_barbican_migrate_dst_store: "{{ _store_other }}" + cifmw_barbican_migrate_payload: "{{ _payload }}-admin" + + - name: Dry-run barbican-secret-migrate must not change the backend + ansible.builtin.include_tasks: one.yml + vars: + cifmw_barbican_migrate_case: admin_dry_run + cifmw_barbican_migrate_via: admin + cifmw_barbican_migrate_dry_run: true + cifmw_barbican_migrate_src_store: "{{ _store_src }}" + cifmw_barbican_migrate_dst_store: "{{ _store_other }}" + cifmw_barbican_migrate_payload: "{{ _payload }}-dry" + + - name: Metadata-only secret must fail migrate + ansible.builtin.include_tasks: metaonly.yml diff --git a/hooks/playbooks/barbican-secret-store-migrate/one.yml b/hooks/playbooks/barbican-secret-store-migrate/one.yml new file mode 100644 index 000000000..9b388946a --- /dev/null +++ b/hooks/playbooks/barbican-secret-store-migrate/one.yml @@ -0,0 +1,183 @@ +--- +# Create a secret on the global-default store, migrate it, assert the +# expected backend in MariaDB, decrypt via OSC, then delete. +# +# cifmw_barbican_migrate_via: osc | admin +# osc — openstack secret migrate in the openstackclient pod +# admin — barbican-secret-migrate in the barbican-api pod +# cifmw_barbican_migrate_dry_run: only valid with via=admin; skip PUT +# cifmw_barbican_migrate_roundtrip: migrate back to the source store +# +# Expects facts from migrate.yml, including +# cifmw_barbican_migrate_src_store / dst_store dicts with id, store_plugin, +# crypto_plugin. + +- name: "{{ cifmw_barbican_migrate_case }} migrate scenario" + block: + - name: "Create secret for {{ cifmw_barbican_migrate_case }}" + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - secret + - store + - --name + - "ci-migrate-{{ cifmw_barbican_migrate_case }}" + - --payload + - "{{ cifmw_barbican_migrate_payload }}" + - --payload-content-type + - text/plain + - -f + - value + - -c + - Secret href + register: _create_secret + changed_when: false + no_log: true + + - name: "Parse created secret for {{ cifmw_barbican_migrate_case }}" + ansible.builtin.set_fact: + _migrate_secret_href: "{{ _create_secret.stdout | trim }}" + _migrate_secret_id: "{{ _create_secret.stdout | trim | regex_replace('.*/', '') }}" + + - name: "Assert source backend before migrate ({{ cifmw_barbican_migrate_case }})" + ansible.builtin.include_tasks: assert.yml + vars: + cifmw_barbican_migrate_assert_label: "{{ cifmw_barbican_migrate_case }} before" + cifmw_barbican_migrate_assert_secret_id: "{{ _migrate_secret_id }}" + cifmw_barbican_migrate_assert_store: "{{ cifmw_barbican_migrate_src_store }}" + + - name: "Migrate secret with OSC ({{ cifmw_barbican_migrate_case }})" + when: cifmw_barbican_migrate_via | default('osc') == 'osc' + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - --os-key-manager-api-version + - "1.3" + - secret + - migrate + - "{{ _migrate_secret_href }}" + - --secret-store + - "{{ cifmw_barbican_migrate_dst_store.id }}" + register: _migrate_osc + changed_when: false + no_log: true + + - name: "Migrate secret with barbican-secret-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_dst_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 }}" + + - name: "Assert backend after migrate ({{ cifmw_barbican_migrate_case }})" + ansible.builtin.include_tasks: assert.yml + vars: + cifmw_barbican_migrate_assert_label: "{{ cifmw_barbican_migrate_case }} after" + cifmw_barbican_migrate_assert_secret_id: "{{ _migrate_secret_id }}" + cifmw_barbican_migrate_assert_store: "{{ cifmw_barbican_migrate_src_store if (cifmw_barbican_migrate_dry_run | default(false) | bool) else cifmw_barbican_migrate_dst_store }}" + + - name: "Migrate secret back with OSC ({{ cifmw_barbican_migrate_case }})" + when: + - cifmw_barbican_migrate_roundtrip | default(false) | bool + - not (cifmw_barbican_migrate_dry_run | default(false) | bool) + - cifmw_barbican_migrate_via | default('osc') == 'osc' + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - --os-key-manager-api-version + - "1.3" + - secret + - migrate + - "{{ _migrate_secret_href }}" + - --secret-store + - "{{ cifmw_barbican_migrate_src_store.id }}" + register: _migrate_osc_back + changed_when: false + no_log: true + + - name: "Migrate secret back with barbican-secret-migrate ({{ cifmw_barbican_migrate_case }})" + when: + - cifmw_barbican_migrate_roundtrip | default(false) | bool + - not (cifmw_barbican_migrate_dry_run | default(false) | bool) + - cifmw_barbican_migrate_via | default('osc') == 'admin' + ansible.builtin.include_tasks: admin.yml + vars: + cifmw_barbican_migrate_admin_store_id: "{{ cifmw_barbican_migrate_src_store.id }}" + cifmw_barbican_migrate_admin_secret_id: "{{ _migrate_secret_id }}" + cifmw_barbican_migrate_admin_dry_run: false + + - name: "Assert source backend after roundtrip ({{ cifmw_barbican_migrate_case }})" + when: + - cifmw_barbican_migrate_roundtrip | default(false) | bool + - not (cifmw_barbican_migrate_dry_run | default(false) | bool) + ansible.builtin.include_tasks: assert.yml + vars: + cifmw_barbican_migrate_assert_label: "{{ cifmw_barbican_migrate_case }} roundtrip" + cifmw_barbican_migrate_assert_secret_id: "{{ _migrate_secret_id }}" + cifmw_barbican_migrate_assert_store: "{{ cifmw_barbican_migrate_src_store }}" + + - name: "Decrypt payload after migrate ({{ cifmw_barbican_migrate_case }})" + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - secret + - get + - --payload + - "{{ _migrate_secret_href }}" + - -f + - value + - -c + - Payload + register: _payload_after + changed_when: false + no_log: true + + - name: "Assert payload unchanged ({{ cifmw_barbican_migrate_case }})" + ansible.builtin.assert: + that: + - _payload_after.stdout | trim == cifmw_barbican_migrate_payload + fail_msg: >- + {{ cifmw_barbican_migrate_case }}: payload mismatch after migrate. + always: + - name: "Delete secret for {{ cifmw_barbican_migrate_case }}" + when: _migrate_secret_href is defined + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_osc_pod }}" + - -- + - openstack + - secret + - delete + - "{{ _migrate_secret_href }}" + changed_when: false + failed_when: false + no_log: true diff --git a/hooks/playbooks/barbican-secret-store-migrate/sql.yml b/hooks/playbooks/barbican-secret-store-migrate/sql.yml new file mode 100644 index 000000000..f165e6140 --- /dev/null +++ b/hooks/playbooks/barbican-secret-store-migrate/sql.yml @@ -0,0 +1,26 @@ +--- +# Run a MariaDB statement inside the Galera pod. +# Set cifmw_barbican_migrate_sql to the SQL string. +# Result is cifmw_barbican_migrate_sql_result (no_log). + +- name: "{{ cifmw_barbican_migrate_sql_desc | default('Query Barbican MariaDB') }}" + ansible.builtin.command: + argv: + - oc + - -n + - "{{ cifmw_barbican_migrate_namespace }}" + - exec + - "{{ cifmw_barbican_migrate_galera_pod }}" + - -c + - "{{ cifmw_barbican_migrate_galera_container }}" + - -- + - mysql + - "--user={{ cifmw_barbican_migrate_db_user }}" + - "--password={{ _db_password }}" + - "--database={{ cifmw_barbican_migrate_db_name }}" + - --batch + - --skip-column-names + - "--execute={{ cifmw_barbican_migrate_sql }}" + register: cifmw_barbican_migrate_sql_result + changed_when: false + no_log: true From 64358066e0130ce5adc06d5986040ec153685bb1 Mon Sep 17 00:00:00 2001 From: Ade Lee Date: Thu, 27 Aug 2026 02:31:31 -0400 Subject: [PATCH 2/2] [hooks] Fix Barbican migrate OSC flags and admin token scope Drop --os-key-manager-api-version 1.3 (OSC only knows major version 1), pass --os-project-id for v3token, and pin the test payload with set_fact. Related-Issue: #OSPRH-35280 Assisted-by: Cursor Grok 4.6 Signed-off-by: Ade Lee --- .../barbican-secret-store-migrate/admin.yml | 10 ++++----- .../metaonly.yml | 3 --- .../barbican-secret-store-migrate/migrate.yml | 21 +++++++++++++------ .../barbican-secret-store-migrate/one.yml | 6 ------ 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/hooks/playbooks/barbican-secret-store-migrate/admin.yml b/hooks/playbooks/barbican-secret-store-migrate/admin.yml index 6da2e4c1d..d8a3c2a88 100644 --- a/hooks/playbooks/barbican-secret-store-migrate/admin.yml +++ b/hooks/playbooks/barbican-secret-store-migrate/admin.yml @@ -26,8 +26,8 @@ - "{{ cifmw_barbican_migrate_keystone }}" - --os-endpoint-override - "{{ cifmw_barbican_migrate_v1 }}" - - --os-identity-api-version - - "3" + - --os-project-id + - "{{ _os_project_id }}" - --secret-store-id - "{{ cifmw_barbican_migrate_admin_store_id }}" - --secret-id @@ -36,7 +36,6 @@ - /tmp/cifmw-barbican-secret-migrate-errors.jsonl register: _migrate_admin changed_when: false - no_log: true - name: "Dry-run barbican-secret-migrate ({{ cifmw_barbican_migrate_case }})" when: cifmw_barbican_migrate_admin_dry_run | default(false) | bool @@ -59,8 +58,8 @@ - "{{ cifmw_barbican_migrate_keystone }}" - --os-endpoint-override - "{{ cifmw_barbican_migrate_v1 }}" - - --os-identity-api-version - - "3" + - --os-project-id + - "{{ _os_project_id }}" - --secret-store-id - "{{ cifmw_barbican_migrate_admin_store_id }}" - --secret-id @@ -70,4 +69,3 @@ - --dry-run register: _migrate_admin_dry changed_when: false - no_log: true diff --git a/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml b/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml index d0bbab6d1..362a070a6 100644 --- a/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml +++ b/hooks/playbooks/barbican-secret-store-migrate/metaonly.yml @@ -39,8 +39,6 @@ - "{{ cifmw_barbican_migrate_osc_pod }}" - -- - openstack - - --os-key-manager-api-version - - "1.3" - secret - migrate - "{{ _meta_secret_href }}" @@ -49,7 +47,6 @@ register: _meta_migrate changed_when: false failed_when: false - no_log: true - name: Assert metadata-only migrate failed ansible.builtin.assert: diff --git a/hooks/playbooks/barbican-secret-store-migrate/migrate.yml b/hooks/playbooks/barbican-secret-store-migrate/migrate.yml index 27dd49620..6ac20121f 100644 --- a/hooks/playbooks/barbican-secret-store-migrate/migrate.yml +++ b/hooks/playbooks/barbican-secret-store-migrate/migrate.yml @@ -161,18 +161,25 @@ - token - issue - -f - - value - - -c - - id + - json register: _os_token_cmd changed_when: false no_log: true - name: Save project-admin token ansible.builtin.set_fact: - _os_token: "{{ _os_token_cmd.stdout | trim }}" + _os_token: "{{ (_os_token_cmd.stdout | from_json).id }}" + _os_project_id: "{{ (_os_token_cmd.stdout | from_json).project_id }}" no_log: true + - name: Fail if the token has no project id + ansible.builtin.assert: + that: + - _os_project_id | default('') | length > 0 + fail_msg: >- + openstack token issue did not return project_id. barbican-secret-migrate + with v3token needs a project-scoped token. + - name: Get Keystone internal URL ansible.builtin.command: argv: @@ -291,9 +298,11 @@ _store_src: "{{ _stores_default[0] }}" _store_other: "{{ _stores_simple[0] if _stores_default[0].crypto_plugin == 'p11_crypto' else _stores_pkcs11[0] }}" + - name: Choose a payload for this run + ansible.builtin.set_fact: + _payload: "ci-migrate-payload-{{ 999999 | random }}" + - name: Run migrate scenarios - vars: - _payload: ci-migrate-payload-{{ 999999 | random }} block: - name: Round-trip migrate via OSC ansible.builtin.include_tasks: one.yml diff --git a/hooks/playbooks/barbican-secret-store-migrate/one.yml b/hooks/playbooks/barbican-secret-store-migrate/one.yml index 9b388946a..7222204ba 100644 --- a/hooks/playbooks/barbican-secret-store-migrate/one.yml +++ b/hooks/playbooks/barbican-secret-store-migrate/one.yml @@ -63,8 +63,6 @@ - "{{ cifmw_barbican_migrate_osc_pod }}" - -- - openstack - - --os-key-manager-api-version - - "1.3" - secret - migrate - "{{ _migrate_secret_href }}" @@ -72,7 +70,6 @@ - "{{ cifmw_barbican_migrate_dst_store.id }}" register: _migrate_osc changed_when: false - no_log: true - name: "Migrate secret with barbican-secret-migrate ({{ cifmw_barbican_migrate_case }})" when: cifmw_barbican_migrate_via | default('osc') == 'admin' @@ -103,8 +100,6 @@ - "{{ cifmw_barbican_migrate_osc_pod }}" - -- - openstack - - --os-key-manager-api-version - - "1.3" - secret - migrate - "{{ _migrate_secret_href }}" @@ -112,7 +107,6 @@ - "{{ cifmw_barbican_migrate_src_store.id }}" register: _migrate_osc_back changed_when: false - no_log: true - name: "Migrate secret back with barbican-secret-migrate ({{ cifmw_barbican_migrate_case }})" when: