Skip to content

Deploy Barbican

OpenStack Barbican is the dedicated security service within the OpenStack ecosystem, focused on the secure storage, management, and provisioning of sensitive data such as encryption keys, certificates, and passwords. Barbican plays a crucial role in enhancing the security posture of cloud environments by providing a centralized and controlled repository for cryptographic secrets, ensuring that sensitive information is protected and accessible only to authorized services and users. It integrates seamlessly with other OpenStack services to offer encryption and secure key management capabilities, which are essential for maintaining data confidentiality and integrity. In this document, we will explore the deployment of OpenStack Barbican using Genestack. With Genestack, the deployment of Barbican is optimized, ensuring that cloud infrastructures are equipped with strong and scalable security measures for managing critical secrets.

Create secrets

Information about the secretes used

Manual secret generation is only required if you haven't run the create-secrets.sh script located in /opt/genestack/bin.

Example secret generation
kubectl --namespace openstack \
        create secret generic barbican-rabbitmq-password \
        --type Opaque \
        --from-literal=username="barbican" \
        --from-literal=password="$(< /dev/urandom tr -dc _A-Za-z0-9 | head -c${1:-64};echo;)"
kubectl --namespace openstack \
        create secret generic barbican-db-password \
        --type Opaque \
        --from-literal=password="$(< /dev/urandom tr -dc _A-Za-z0-9 | head -c${1:-32};echo;)"
kubectl --namespace openstack \
        create secret generic barbican-admin \
        --type Opaque \
        --from-literal=password="$(< /dev/urandom tr -dc _A-Za-z0-9 | head -c${1:-32};echo;)"

Setup Barbican Overrides

When deploying barbican, it is important to provide the necessary configuration values to ensure that the service is properly configured and integrated with other OpenStack services. The /etc/genestack/helm-configs/barbican/barbican-helm-overrides.yaml file contains the necessary configuration values for Barbican, including database connection details, RabbitMQ credentials, and other service-specific settings. By providing these values, you can customize the deployment of Barbican to meet your specific requirements and ensure that the service operates correctly within your OpenStack environment.

Epoxy (2026.1) / OpenStack 2025.1

Barbican is validated here against the OpenStack 2025.1 stream. This update does not include direct changes to barbican-helm-overrides.yaml.

Set the host_href value

The host_href value should be set to the public endpoint of the Barbican service. This value is used by other OpenStack services and public consumers to communicate with Barbican and should be accessible from all OpenStack services.

conf:
  barbican:
    DEFAULT:
      host_href: "https://barbican.your.domain.tld"

Run the package deployment

Run the Barbican deployment Script /opt/genestack/bin/install-barbican.sh

#!/bin/bash
# Description: Fetches the version for SERVICE_NAME_DEFAULT from the specified
# YAML file and executes a helm upgrade/install command with dynamic values files.

# Disable SC2124 (unused array), SC2145 (array expansion issue), SC2294 (eval)
# shellcheck disable=SC2124,SC2145,SC2294

# Service
# The service name is used for both the release name and the chart name.
SERVICE_NAME_DEFAULT="barbican"
SERVICE_NAMESPACE="openstack"

# Helm
HELM_REPO_NAME_DEFAULT="openstack-helm"
HELM_REPO_URL_DEFAULT="https://tarballs.opendev.org/openstack/openstack-helm"

# Base directories provided by the environment
GENESTACK_BASE_DIR="${GENESTACK_BASE_DIR:-/opt/genestack}"
GENESTACK_OVERRIDES_DIR="${GENESTACK_OVERRIDES_DIR:-/etc/genestack}"

# Define service-specific override directories based on the framework
SERVICE_BASE_OVERRIDES="${GENESTACK_BASE_DIR}/base-helm-configs/${SERVICE_NAME_DEFAULT}"
SERVICE_CUSTOM_OVERRIDES="${GENESTACK_OVERRIDES_DIR}/helm-configs/${SERVICE_NAME_DEFAULT}"

# Define the Global Overrides directory used in the original script
GLOBAL_OVERRIDES_DIR="${GENESTACK_OVERRIDES_DIR}/helm-configs/global_overrides"

# Read the desired chart version from VERSION_FILE
VERSION_FILE="${GENESTACK_OVERRIDES_DIR}/helm-chart-versions.yaml"

if [ ! -f "$VERSION_FILE" ]; then
    echo "Error: helm-chart-versions.yaml not found at $VERSION_FILE" >&2
    exit 1
fi

# Extract version dynamically using the SERVICE_NAME_DEFAULT variable
SERVICE_VERSION=$(grep "^[[:space:]]*${SERVICE_NAME_DEFAULT}:" "$VERSION_FILE" | sed "s/.*${SERVICE_NAME_DEFAULT}: *//")

if [ -z "$SERVICE_VERSION" ]; then
    echo "Error: Could not extract version for '$SERVICE_NAME_DEFAULT' from $VERSION_FILE" >&2
    exit 1
fi

echo "Found version for $SERVICE_NAME_DEFAULT: $SERVICE_VERSION"

# Load chart metadata from custom override YAML if defined
for yaml_file in "${SERVICE_CUSTOM_OVERRIDES}"/*.yaml; do
    if [ -f "$yaml_file" ]; then
        HELM_REPO_URL=$(yq eval '.chart.repo_url // ""' "$yaml_file")
        HELM_REPO_NAME=$(yq eval '.chart.repo_name // ""' "$yaml_file")
        SERVICE_NAME=$(yq eval '.chart.service_name // ""' "$yaml_file")
        break  # use the first match and stop
    fi
done

# Fallback to defaults if variables not set
: "${HELM_REPO_URL:=$HELM_REPO_URL_DEFAULT}"
: "${HELM_REPO_NAME:=$HELM_REPO_NAME_DEFAULT}"
: "${SERVICE_NAME:=$SERVICE_NAME_DEFAULT}"


# Determine Helm chart path
if [[ "$HELM_REPO_URL" == oci://* ]]; then
    # OCI registry path
    HELM_CHART_PATH="$HELM_REPO_URL/$HELM_REPO_NAME/$SERVICE_NAME"
else
    # --- Helm Repository and Execution ---
    helm repo add "$HELM_REPO_NAME" "$HELM_REPO_URL"
    helm repo update
    HELM_CHART_PATH="$HELM_REPO_NAME/$SERVICE_NAME"
fi


# Debug output
echo "[DEBUG] HELM_REPO_URL=$HELM_REPO_URL"
echo "[DEBUG] HELM_REPO_NAME=$HELM_REPO_NAME"
echo "[DEBUG] SERVICE_NAME=$SERVICE_NAME"
echo "[DEBUG] HELM_CHART_PATH=$HELM_CHART_PATH"

# Prepare an array to collect -f arguments
overrides_args=()

# Include all YAML files from the BASE configuration directory
# NOTE: Files in this directory are included first.
if [[ -d "$SERVICE_BASE_OVERRIDES" ]]; then
    echo "Including base overrides from directory: $SERVICE_BASE_OVERRIDES"
    for file in "$SERVICE_BASE_OVERRIDES"/*.yaml; do
        # Check that there is at least one match
        if [[ -e "$file" ]]; then
            echo " - $file"
            overrides_args+=("-f" "$file")
        fi
    done
else
    echo "Warning: Base override directory not found: $SERVICE_BASE_OVERRIDES"
fi

# Include all YAML files from the GLOBAL configuration directory
# NOTE: Files here override base settings and are applied before service-specific ones.
if [[ -d "$GLOBAL_OVERRIDES_DIR" ]]; then
    echo "Including global overrides from directory: $GLOBAL_OVERRIDES_DIR"
    for file in "$GLOBAL_OVERRIDES_DIR"/*.yaml; do
        if [[ -e "$file" ]]; then
            echo " - $file"
            overrides_args+=("-f" "$file")
        fi
    done
else
    echo "Warning: Global override directory not found: $GLOBAL_OVERRIDES_DIR"
fi

# Include all YAML files from the custom SERVICE configuration directory
# NOTE: Files here have the highest precedence.
if [[ -d "$SERVICE_CUSTOM_OVERRIDES" ]]; then
    echo "Including overrides from service config directory:"
    for file in "$SERVICE_CUSTOM_OVERRIDES"/*.yaml; do
        if [[ -e "$file" ]]; then
            echo " - $file"
            overrides_args+=("-f" "$file")
        fi
    done
else
    echo "Warning: Service config directory not found: $SERVICE_CUSTOM_OVERRIDES"
fi

echo

# Collect all --set arguments, executing commands and quoting safely
set_args=(
    --set "endpoints.identity.auth.admin.password=$(kubectl --namespace openstack get secret keystone-admin -o jsonpath='{.data.password}' | base64 -d)"
    --set "endpoints.identity.auth.barbican.password=$(kubectl --namespace openstack get secret barbican-admin -o jsonpath='{.data.password}' | base64 -d)"
    --set "endpoints.oslo_db.auth.admin.password=$(kubectl --namespace openstack get secret mariadb -o jsonpath='{.data.root-password}' | base64 -d)"
    --set "endpoints.oslo_db.auth.barbican.password=$(kubectl --namespace openstack get secret barbican-db-password -o jsonpath='{.data.password}' | base64 -d)"
    --set "conf.barbican.database.connection=mysql+pymysql://barbican:$(kubectl --namespace openstack get secret barbican-db-password -o jsonpath='{.data.password}' | base64 -d)@mariadb-cluster-primary:3306/barbican?charset=utf8"
    --set "endpoints.oslo_messaging.auth.admin.password=$(kubectl --namespace openstack get secret rabbitmq-default-user -o jsonpath='{.data.password}' | base64 -d)"
    --set "endpoints.oslo_messaging.auth.barbican.password=$(kubectl --namespace openstack get secret barbican-rabbitmq-password -o jsonpath='{.data.password}' | base64 -d)"
    --set "endpoints.oslo_cache.auth.memcache_secret_key=$(kubectl --namespace openstack get secret os-memcached -o jsonpath='{.data.memcache_secret_key}' | base64 -d)"
    --set "conf.barbican.keystone_authtoken.memcache_secret_key=$(kubectl --namespace openstack get secret os-memcached -o jsonpath='{.data.memcache_secret_key}' | base64 -d)"
)

# SoftHSM p11 is the default in base helm. Treat it as enabled when any
# values file (base, global, or site) has both p11_crypto and libsofthsm2,
# or when a hyperconverged/lab flag is set.
override_file="${SERVICE_CUSTOM_OVERRIDES}/barbican-helm-overrides.yaml"
SOFTHSM_P11=false
for ((i = 0; i < ${#overrides_args[@]}; i++)); do
    if [[ "${overrides_args[$i]}" == "-f" ]]; then
        f="${overrides_args[$((i + 1))]}"
        if [[ -f "${f}" ]] \
            && grep -q "p11_crypto" "${f}" 2>/dev/null \
            && grep -q "libsofthsm2" "${f}" 2>/dev/null; then
            SOFTHSM_P11=true
            break
        fi
    fi
done
if [[ "${BARBICAN_HSM_ENABLED:-false}" == "true" ]] || [[ "${HYPERCONVERGED_BARBICAN_HSM:-false}" == "true" ]]; then
    SOFTHSM_P11=true
    # Generate a SoftHSM overlay only when no site file exists. Never rewrite
    # an existing environment override (region, images, policy would be lost).
    if [[ ! -f "${override_file}" ]]; then
        echo "HSM enabled and ${override_file} is missing. Generating SoftHSM overlay..."
        SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
        source "${SCRIPT_DIR}/../scripts/lib/hyperconverged-common.sh"
        writeServiceHelmOverrides "${GENESTACK_OVERRIDES_DIR}/helm-configs"
    fi
fi

# Ensure barbican-hsm-credentials exists. create-secrets.sh does this on
# greenfield; brownfield never re-runs that script, so the install path
# creates the secret once and leaves it alone on later upgrades.
if [[ "${SOFTHSM_P11}" == "true" ]]; then
    existing_pin="$(kubectl --namespace openstack get secret barbican-hsm-credentials \
        -o jsonpath='{.data.pin}' 2>/dev/null | base64 -d)" || true
    if [[ -n "${existing_pin}" ]]; then
        echo "barbican-hsm-credentials already present — leaving PIN unchanged"
    else
        echo "Creating barbican-hsm-credentials (missing on this brownfield cluster)"
        hsm_pin="$(python3 -c 'import secrets,string; a=string.ascii_letters+string.digits; print("".join(secrets.choice(a) for _ in range(32)))')"
        kubectl --namespace openstack create secret generic barbican-hsm-credentials \
            --from-literal=pin="${hsm_pin}" --dry-run=client -o yaml | \
            kubectl apply -f -
        unset hsm_pin
    fi
    unset existing_pin
fi

# PKCS#11 SoftHSM2 PIN Injection
hsm_pin="$(kubectl --namespace openstack get secret barbican-hsm-credentials \
    -o jsonpath='{.data.pin}' 2>/dev/null | base64 -d)" || true
if [[ -n "${hsm_pin}" ]]; then
    echo "HSM credentials found - injecting p11_crypto_plugin.login"
    set_args+=(
        --set "conf.barbican.p11_crypto_plugin.login=${hsm_pin}"
    )
fi
unset hsm_pin

# Programmatically Extract LEGACY_MASTER_KEK for simple_crypto Decryption
# Covers brownfield upgrades (Tier 1 & 2) and greenfield fresh install (Tier 3)
LEGACY_MASTER_KEK=""

# 1. Discover active primary MariaDB pod via Kubernetes labels
MARIADB_POD="$(kubectl --namespace "$SERVICE_NAMESPACE" get pod \
    -l app.kubernetes.io/name=mariadb,k8s.mariadb.com/role=primary \
    -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)"

# 2. Get Barbican DB password from Kubernetes secret
BARBICAN_DB_PASS="$(kubectl --namespace "$SERVICE_NAMESPACE" get secret barbican-db-password \
    -o jsonpath='{.data.password}' 2>/dev/null | base64 -d || true)"

# 3. DB Gate: count simple_crypto KEK records in MariaDB
SIMPLE_CRYPTO_SECRET_COUNT=0

if [[ -n "${MARIADB_POD}" && -n "${BARBICAN_DB_PASS}" ]]; then
    SIMPLE_CRYPTO_SECRET_COUNT="$(kubectl --namespace "$SERVICE_NAMESPACE" exec "${MARIADB_POD}" \
        -c mariadb -- mariadb -u barbican -p"${BARBICAN_DB_PASS}" barbican -N -e \
        "SELECT COUNT(*) FROM kek_data WHERE plugin_name LIKE '%SimpleCryptoPlugin%';" \
        2>/dev/null | tr -d '[:space:]' || true)"
else
    echo "WARNING: Could not connect to MariaDB (pod='${MARIADB_POD}'). Skipping KEK DB gate check."
fi

echo "Found ${SIMPLE_CRYPTO_SECRET_COUNT:-0} simple_crypto KEK record(s) in database."

# TIER 1: Read kek from active barbican.conf in 'barbican-etc' Kubernetes Secret
if [[ -z "${LEGACY_MASTER_KEK}" ]]; then
    # Use cut -f2- so Fernet padding ("=") is not stripped by the field split.
    LEGACY_MASTER_KEK="$(kubectl --namespace "$SERVICE_NAMESPACE" get secret barbican-etc \
        -o jsonpath='{.data.barbican\.conf}' 2>/dev/null | base64 -d | \
        grep -E "^\s*kek\s*=" | head -n1 | cut -d'=' -f2- | tr -d ' ' || true)"
    [[ -n "${LEGACY_MASTER_KEK}" ]] && echo "KEK found in barbican-etc secret."
fi

# Tier 2: Read simple_crypto_plugin.kek or simple_crypto_kek_rewrap.old_kek
#         from the deployed Helm release values
if [[ -z "${LEGACY_MASTER_KEK}" ]]; then
    LEGACY_MASTER_KEK="$(helm get values "$SERVICE_NAME_DEFAULT" --namespace "$SERVICE_NAMESPACE" --all 2>/dev/null | \
        python3 -c "
import sys, yaml
try:
    d = yaml.safe_load(sys.stdin) or {}
    conf = d.get('conf', {})
    kek = conf.get('barbican', {}).get('simple_crypto_plugin', {}).get('kek')
    if isinstance(kek, list) and len(kek) > 0 and kek[0]:
        print(kek[0])
    elif isinstance(kek, str) and kek:
        print(kek)
    else:
        old_kek = conf.get('simple_crypto_kek_rewrap', {}).get('old_kek')
        if old_kek:
            print(old_kek)
except Exception:
    pass
" 2>/dev/null || true)"
    [[ -n "${LEGACY_MASTER_KEK}" ]] && echo "KEK found in deployed Helm release values."
fi

# TIER 3: Greenfield / Fresh Lab - Read from barbican-simple-crypto-kek Secret
if [[ -z "${LEGACY_MASTER_KEK}" ]]; then
    LEGACY_MASTER_KEK="$(kubectl --namespace "$SERVICE_NAMESPACE" get secret barbican-simple-crypto-kek \
        -o jsonpath='{.data.kek}' 2>/dev/null | base64 -d || true)"
    [[ -n "${LEGACY_MASTER_KEK}" ]] && echo "KEK found in barbican-simple-crypto-kek secret (fresh install)."
fi

# Inject the resolved KEK or fail fast if brownfield has secrets in DB but no KEK found.
# Helm --set treats "=" as a delimiter and drops Fernet padding, so pass the KEK
# via a values file instead.
if [[ -n "${LEGACY_MASTER_KEK}" ]]; then
    # 32-byte Fernet keys always need a single trailing "="; restore it if a
    # previous --set/cut stripped it.
    if [[ "${LEGACY_MASTER_KEK}" != *= ]]; then
        LEGACY_MASTER_KEK="${LEGACY_MASTER_KEK}="
    fi
    echo "Injecting simple_crypto Master KEK into Barbican configuration..."
    KEK_VALUES_FILE="$(mktemp)"
    trap 'rm -f "${KEK_VALUES_FILE}"' EXIT
    python3 -c 'import json,sys; print("conf:\n  barbican:\n    simple_crypto_plugin:\n      kek: %s" % json.dumps(sys.argv[1]))' \
        "${LEGACY_MASTER_KEK}" > "${KEK_VALUES_FILE}"
    set_args+=(-f "${KEK_VALUES_FILE}")
elif [[ "${SIMPLE_CRYPTO_SECRET_COUNT:-0}" -gt 0 ]]; then
    echo "ERROR: Legacy simple_crypto secrets exist in DB but no KEK could be extracted!"
    echo "       Manual intervention required before upgrading Barbican."
    exit 1
fi

helm_command=(
    helm upgrade --install "$SERVICE_NAME_DEFAULT" "$HELM_CHART_PATH"
    --version "${SERVICE_VERSION}"
    --namespace="$SERVICE_NAMESPACE"
    --timeout 120m
    --create-namespace

    "${overrides_args[@]}"
    "${set_args[@]}"

    # Post-renderer configuration
    --post-renderer "$GENESTACK_OVERRIDES_DIR/kustomize/kustomize.sh"
    --post-renderer-args "$SERVICE_NAME_DEFAULT/overlay"

    "$@"
)

echo "Executing Helm command (arguments are quoted safely):"
printf '%q ' "${helm_command[@]}"
echo

# Execute the command directly from the array
"${helm_command[@]}"

# Post-Install SoftHSM2 Key Initialization (token, MKEK, HMAC).
# Runs when the site override already enables SoftHSM p11, or when the
# hyperconverged/lab HSM flags are set. No extra env var required.
if [[ "${SOFTHSM_P11}" == "true" ]]; then
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

    if ! declare -f initBarbicanHSMKeys >/dev/null 2>&1; then
        common_sh="${SCRIPT_DIR}/../scripts/lib/hyperconverged-common.sh"
        if [[ -f "${common_sh}" ]]; then
            source "${common_sh}" >/dev/null 2>&1 || true
        fi
    fi

    if declare -f initBarbicanHSMKeys >/dev/null 2>&1; then
        initBarbicanHSMKeys
    else
        echo "ERROR: initBarbicanHSMKeys not found; SoftHSM keys were not initialized"
        exit 1
    fi
fi

Note

For Epoxy validation, DB credentials are injected at install time from Kubernetes secrets in bin/install-barbican.sh (for example endpoints.oslo_db.auth.admin.password and endpoints.oslo_db.auth.barbican.password).

Tip

In other cases such as a multi-region deployment you may want to view the Multi-Region Support guide to for a workflow solution.