diff --git a/compose/kael.yml b/compose/kael.yml index e90c67b..1b224d9 100644 --- a/compose/kael.yml +++ b/compose/kael.yml @@ -8,6 +8,9 @@ services: restart: always env_file: - ${CONFIG_SAFE_FILE} + environment: + PLATFORM_GATEWAY_ENABLED: "true" + PLATFORM_DELEGATION_KEY: ${CHAT_AI_DELEGATION_SECRET} volumes: - ${VOLUME_DIR}/kael/data:/opt/kael/data healthcheck: diff --git a/config-example.txt b/config-example.txt index 02c1137..f0dfc75 100644 --- a/config-example.txt +++ b/config-example.txt @@ -57,6 +57,13 @@ SECRET_KEY= # BOOTSTRAP_TOKEN= +# Secret used to sign Kael user delegation requests to Core. The installer +# generates this value once and preserves it during upgrades. +# (*) Warning: Keep this value secret. +# (*) Do not disclose CHAT_AI_DELEGATION_SECRET to anyone +# +CHAT_AI_DELEGATION_SECRET= + # Log level INFO, WARN, ERROR # LOG_LEVEL=ERROR diff --git a/scripts/1_config_jumpserver.sh b/scripts/1_config_jumpserver.sh index 1416528..fab4d04 100644 --- a/scripts/1_config_jumpserver.sh +++ b/scripts/1_config_jumpserver.sh @@ -18,6 +18,7 @@ function set_secret_key() { set_config BOOTSTRAP_TOKEN "${bootstrap_key}" echo_check "BOOTSTRAP_TOKEN generated" fi + ensure_config_secret CHAT_AI_DELEGATION_SECRET 32 || return 1 if command -v hostname&>/dev/null; then SERVER_HOSTNAME=$(hostname) set_config SERVER_HOSTNAME "${SERVER_HOSTNAME}" diff --git a/scripts/7_upgrade.sh b/scripts/7_upgrade.sh index e1d94b9..0ee32b7 100644 --- a/scripts/7_upgrade.sh +++ b/scripts/7_upgrade.sh @@ -96,6 +96,7 @@ function upgrade_config() { check_and_set_config "JUMPSERVER_ENABLE_FONT_SMOOTHING" "true" check_and_set_config "USE_LB" "1" check_and_set_config "VERIFY_EXTERNAL_SSL" "false" + ensure_config_secret CHAT_AI_DELEGATION_SECRET 32 || return 1 # XPACK use_xpack=$(get_config_or_env USE_XPACK) if [[ "${use_xpack}" == "1" ]]; then diff --git a/scripts/gists/common.sh b/scripts/gists/common.sh index aa290e8..3a837b2 100644 --- a/scripts/gists/common.sh +++ b/scripts/gists/common.sh @@ -22,6 +22,20 @@ function random_str() { fi } +function random_secret() { + local byte_length=${1:-32} + + if ! [[ "${byte_length}" =~ ^[1-9][0-9]*$ ]]; then + printf 'Secret byte length must be a positive integer\n' >&2 + return 1 + fi + + # Hex keeps generated values safe for env files while /dev/urandom provides + # installation-specific entropy even when the installer runs as root. + od -An -N "${byte_length}" -tx1 /dev/urandom | tr -d '[:space:]' + printf '\n' +} + function read_from_input() { var=$1 diff --git a/scripts/gists/conf.sh b/scripts/gists/conf.sh index bc1ef42..f3acfb4 100644 --- a/scripts/gists/conf.sh +++ b/scripts/gists/conf.sh @@ -148,6 +148,21 @@ function set_config() { mv -f "${tmp_file}" "${CONFIG_FILE}" } +function ensure_config_secret() { + local key=$1 + local byte_length=${2:-32} + local value + + value=$(get_config "${key}") + if [[ -n "${value}" ]]; then + return 0 + fi + + value=$(random_secret "${byte_length}") || return 1 + set_config "${key}" "${value}" || return 1 + echo_check "${key} generated" +} + function remove_config() { key=$1 diff --git a/tests/test_compose.sh b/tests/test_compose.sh index 54aac2b..8e9e258 100755 --- a/tests/test_compose.sh +++ b/tests/test_compose.sh @@ -11,6 +11,7 @@ fi test_dir="${TEST_TMP_ROOT}/compose" export HOSTNAME=test-host +export CHAT_AI_DELEGATION_SECRET=test-only-delegation-secret-00000000000000000000000000000000 mkdir -p "${test_dir}" cp "${TEST_ROOT}/config-example.txt" "${test_dir}/config.txt" cp "${TEST_ROOT}/config-example.txt" "${test_dir}/config_safe.txt" @@ -27,6 +28,7 @@ default_config=$( ${compose_cmd} --env-file "${CONFIG_FILE}" config ) assert_contains "${default_config}" 'jms_kael' 'rendered Compose config must contain Kael' +assert_contains "${default_config}" "PLATFORM_DELEGATION_KEY: ${CHAT_AI_DELEGATION_SECRET}" 'Kael must receive the Core delegation secret' if [[ "${default_config}" == *'jms_ai'* ]]; then fail 'rendered Compose config must not contain the removed AI service' fi diff --git a/tests/test_conf.sh b/tests/test_conf.sh index 86898e9..85bc49a 100755 --- a/tests/test_conf.sh +++ b/tests/test_conf.sh @@ -17,6 +17,7 @@ printf '%s\n' \ 'LABEL=hello world' \ 'REDIS_SENTINEL_HOSTS=old' >"${CONFIG_FILE}" +. "${TEST_ROOT}/scripts/gists/common.sh" . "${TEST_ROOT}/scripts/gists/conf.sh" assert_eq 'abc=def' "$(get_config PASSWORD)" 'get_config must preserve equals signs' @@ -34,3 +35,12 @@ set_config EMPTY_VALUE '' assert_eq '1' "$(has_config EMPTY_VALUE)" 'set_config must create explicitly empty values' printf 'PASS: config round trips complex values\n' + +ensure_config_secret CHAT_AI_DELEGATION_SECRET 32 +delegation_secret=$(get_config CHAT_AI_DELEGATION_SECRET) +[[ "${delegation_secret}" =~ ^[0-9a-f]{64}$ ]] || + fail 'delegation secret must contain 32 bytes encoded as lowercase hex' +ensure_config_secret CHAT_AI_DELEGATION_SECRET 32 +assert_eq "${delegation_secret}" "$(get_config CHAT_AI_DELEGATION_SECRET)" 'existing delegation secret must be preserved' + +printf 'PASS: config secrets are generated once and preserved\n'