diff --git a/README.md b/README.md index a659527..f4dbea4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ mvn spring-boot:run 完整部署手册: - [docs/deployment/tms-deployment.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-deployment.md) +- [docs/deployment/tms-init-admin-permission-checklist.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-init-admin-permission-checklist.md) 1. 构建 jar 包: @@ -36,11 +37,17 @@ sudo mkdir -p /home/tms/bin/resource-restore sudo chown -R "$(whoami)":"$(whoami)" /home/tms cp scripts/tms.sh /home/tms/scripts/tms.sh cp scripts/standard-init/*.sh /home/tms/bin/ +sudo cp scripts/standard-init/root-sbin/tms-run-standard-vendor-step /usr/local/sbin/tms-run-standard-vendor-step +sudo cp scripts/standard-init/root-sbin/tms-prepare-standard-permissions /usr/local/sbin/tms-prepare-standard-permissions cp scripts/resource-restore/*.sh /home/tms/bin/resource-restore/ cp config/application.yml.example /home/tms/config/application.yml chmod +x /home/tms/bin/*.sh chmod +x /home/tms/bin/resource-restore/*.sh chmod +x /home/tms/scripts/tms.sh +sudo chown root:root /usr/local/sbin/tms-run-standard-vendor-step +sudo chmod 755 /usr/local/sbin/tms-run-standard-vendor-step +sudo chown root:root /usr/local/sbin/tms-prepare-standard-permissions +sudo chmod 755 /usr/local/sbin/tms-prepare-standard-permissions ``` 3. 启动 / 停止 / 状态: @@ -58,6 +65,8 @@ chmod +x /home/tms/scripts/tms.sh - 可通过 `JAR_PATH=/home/tms/tms-framework.jar` 覆盖 jar 路径。 - 如果部署路径不是 `/home/tms`,使用 `APP_HOME=/your/path /your/path/scripts/tms.sh start`。 - 标准 CISD 初始化辅助脚本位于 `scripts/standard-init/`,部署时需复制到 `/home/tms/bin/`。 +- `scripts/standard-init/root-sbin/tms-run-standard-vendor-step` 是标准版 vendor 客户化脚本的 root 受控入口,部署到 `/usr/local/sbin/` 后通过 sudoers 授权给 `tms` 使用。 +- `scripts/standard-init/root-sbin/tms-prepare-standard-permissions` 是标准版介质权限预处理脚本,由 `root` 在初始化前手工执行,用于修正 `/home/cmep4i/cpackage`、license、SQL load、CMSP/CMTP 目录权限。 - 资源恢复辅助脚本位于 `scripts/resource-restore/`,部署时需复制到 `/home/tms/bin/resource-restore/`。 - `apply_standard_db.sh` 依赖预置环境变量,例如 `DB_USER`、`DB_PASSWORD`,不要直接写入 `application.yml`。 - 运行目录结构、配置项说明、文件上传 `fileId` 流程和故障排查,请查看上面的完整部署手册。 diff --git a/config/application.yml.example b/config/application.yml.example index aa68ddc..e0a38ec 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -67,6 +67,7 @@ tms: standard-nginx-stop-command: /home/tms/bin/stop_standard_nginx.sh standard-rabbit-stop-command: /home/tms/bin/stop_standard_rabbitmq.sh standard-post-check-command: /home/tms/bin/check_standard_runtime.sh + standard-vendor-step-runner-command: /usr/local/sbin/tms-run-standard-vendor-step upload-base-dir: /home/tms/uploads staging-root-dir: /tmp/tms-init-staging direct-receiver-license-target-path: /home/cmep4d/cmep/license/cmep.license @@ -125,8 +126,6 @@ tms: - tms_role_ukey_binding - tms_auth_full_account - tms_auth_session - - tms_auth_challenge - - tms_auth_audit_log - tms_resource_backup_task - tms_resource_restore_task allowed-restore-roots: diff --git a/scripts/cisd_init_precheck.sh b/scripts/cisd_init_precheck.sh index 326b05e..481c2a8 100755 --- a/scripts/cisd_init_precheck.sh +++ b/scripts/cisd_init_precheck.sh @@ -12,6 +12,9 @@ PRODUCT_TYPE="${PRODUCT_TYPE:-${TMS_CISD_PRESET_PRODUCT_TYPE:-UNKNOWN}}" MODE="${TMS_INIT_EXECUTOR_MODE:-SIMULATE}" LOG_DIR="${TMS_INIT_EXECUTOR_LOG_DIR:-/tmp/tms-init-logs}" STANDARD_CPCONFIG_PATH="${TMS_INIT_EXECUTOR_STANDARD_CPCONFIG_PATH:-/home/cmep4i/cpconfig.cfg}" +STANDARD_HOME_DIR="${TMS_INIT_EXECUTOR_STANDARD_HOME_DIR:-/home/cmep4i}" +STANDARD_RUN_USER="${TMS_INIT_EXECUTOR_STANDARD_RUN_USER:-cmep4i}" +TMS_RUN_USER="${TMS_RUN_USER:-tms}" DIRECT_DB_SCRIPT_DIR="${TMS_INIT_EXECUTOR_DIRECT_DB_SCRIPT_DIR:-/opt/CAE_Install_Base_Resource/DB}" DIRECT_TLQ_INIT_CFG_DIR="${TMS_INIT_EXECUTOR_DIRECT_TLQ_INIT_CFG_DIR:-/opt/CAE_Install_App_Resource/CAE}" APP_PATTERN_1="${TMS_INIT_EXECUTOR_STANDARD_RABBIT_APP_CONFIG_PATTERN_1:-/home/cmep4i/cmsp/application-prd*.properties}" @@ -87,6 +90,68 @@ check_pattern_match() { fi } +run_test_as_user() { + local user="$1" + shift + + if [[ "$(id -un 2>/dev/null || true)" == "$user" ]]; then + test "$@" + return $? + fi + + if [[ "$(id -u)" == "0" ]] && command -v runuser >/dev/null 2>&1; then + runuser -u "$user" -- test "$@" 2>/dev/null + return $? + fi + + if command -v sudo >/dev/null 2>&1; then + sudo -n -u "$user" test "$@" 2>/dev/null + return $? + fi + + return 125 +} + +check_user_path_access() { + local user="$1" + local mode="$2" + local path="$3" + local flag="" + + case "$mode" in + r) flag="-r" ;; + w) flag="-w" ;; + x) flag="-x" ;; + rw) flag="-r" ;; + rx) flag="-r" ;; + *) fail "unknown access mode ${mode} for ${path}"; return ;; + esac + + if ! id "$user" >/dev/null 2>&1; then + fail "user missing: ${user}" + return + fi + + if run_test_as_user "$user" "$flag" "$path"; then + if [[ "$mode" == *w* ]] && ! run_test_as_user "$user" -w "$path"; then + fail "path not writable by ${user}: ${path}" + return + fi + if [[ "$mode" == *x* ]] && ! run_test_as_user "$user" -x "$path"; then + fail "path not executable/searchable by ${user}: ${path}" + return + fi + pass "path ${mode} by ${user}: ${path}" + else + local rc=$? + if [[ "$rc" == "125" ]]; then + warn "cannot verify access as ${user}: ${path} (run as root or configure sudo -n)" + else + fail "path not ${mode} by ${user}: ${path}" + fi + fi +} + check_product_type() { case "$PRODUCT_TYPE" in ENTERPRISE|INDIRECT|DIRECT) @@ -126,11 +191,22 @@ check_encryption_config() { } check_standard_assets() { + local standard_media="${STANDARD_HOME_DIR}/cpackage" check_file "$STANDARD_CPCONFIG_PATH" - check_file "/home/cmep4i/cpackage/toolsh/setfraq.sh" - check_file "/home/cmep4i/cpackage/toolsh/setftq.sh" - check_file "/home/cmep4i/cpackage/toolsh/setsql.sh" - check_file "/home/cmep4i/cpackage/toolsh/setsptp.sh" + check_dir "$standard_media" + check_file "${standard_media}/toolsh/setfraq.sh" + check_file "${standard_media}/toolsh/setftq.sh" + check_file "${standard_media}/toolsh/setsql.sh" + check_file "${standard_media}/toolsh/setsptp.sh" + check_user_path_access "$STANDARD_RUN_USER" rx "${standard_media}/toolsh/setfraq.sh" + check_user_path_access "$STANDARD_RUN_USER" rx "${standard_media}/toolsh/setftq.sh" + check_user_path_access "$STANDARD_RUN_USER" rx "${standard_media}/toolsh/setsptp.sh" + check_user_path_access "$STANDARD_RUN_USER" rw "${standard_media}/cmsp/01/application-prd-ci01.properties" + check_user_path_access "$STANDARD_RUN_USER" rw "${standard_media}/cmsp/02/application-prd-ci02.properties" + check_user_path_access "$STANDARD_RUN_USER" rw "${standard_media}/cmtp/01/application-prd-ci01.properties" + check_user_path_access "$STANDARD_RUN_USER" rw "${standard_media}/cmtp/02/application-prd-ci02.properties" + check_user_path_access "$TMS_RUN_USER" r "${standard_media}/software/nginx.conf" + check_user_path_access "$TMS_RUN_USER" r "${standard_media}/front/front.zip" check_pattern_match "$APP_PATTERN_1" check_pattern_match "$APP_PATTERN_2" check_cmd "rabbitmqctl" diff --git a/scripts/resource-restore/apply-resource-backup.sh b/scripts/resource-restore/apply-resource-backup.sh index b73479e..19e628c 100755 --- a/scripts/resource-restore/apply-resource-backup.sh +++ b/scripts/resource-restore/apply-resource-backup.sh @@ -87,7 +87,9 @@ restore_files() { local source_file="${PAYLOAD_DIR}/${entry_path}" [[ -f "${source_file}" ]] || fail "restore source file not found: ${entry_path}" "FILE" mkdir -p "$(dirname "${restore_path}")" - cp -f "${source_file}" "${restore_path}" + if ! cp -f "${source_file}" "${restore_path}" >> "${LOG_FILE}" 2>&1; then + fail "restore file failed: ${restore_path}" "FILE" + fi log "restored file: ${restore_path}" done < "${FILES_PLAN}" } diff --git a/scripts/standard-init/root-sbin/tms-prepare-standard-permissions b/scripts/standard-init/root-sbin/tms-prepare-standard-permissions new file mode 100755 index 0000000..365429f --- /dev/null +++ b/scripts/standard-init/root-sbin/tms-prepare-standard-permissions @@ -0,0 +1,106 @@ +#!/bin/bash +set -euo pipefail + +# Normalize permissions required by TMS standard CISD initialization. +# Run as root after the standard receiver media has been placed under /home/cmep4i. + +STANDARD_HOME_DIR="${STANDARD_HOME_DIR:-/home/cmep4i}" +STANDARD_RUN_USER="${STANDARD_RUN_USER:-cmep4i}" +TMS_RUN_USER="${TMS_RUN_USER:-tms}" + +if [ "$(id -u)" != "0" ]; then + echo "this script must be executed as root" >&2 + exit 77 +fi + +id "${STANDARD_RUN_USER}" >/dev/null 2>&1 || { + echo "standard run user not found: ${STANDARD_RUN_USER}" >&2 + exit 66 +} + +id "${TMS_RUN_USER}" >/dev/null 2>&1 || { + echo "tms run user not found: ${TMS_RUN_USER}" >&2 + exit 66 +} + +require_dir() { + local dir="$1" + if [ ! -d "${dir}" ]; then + echo "required directory not found: ${dir}" >&2 + exit 66 + fi +} + +apply_acl() { + local path="$1" + if command -v setfacl >/dev/null 2>&1; then + setfacl -R -m "u:${STANDARD_RUN_USER}:rwX,u:${TMS_RUN_USER}:rX" "${path}" + find "${path}" -type d -exec setfacl -m "d:u:${STANDARD_RUN_USER}:rwX,d:u:${TMS_RUN_USER}:rX" {} + + else + echo "setfacl not found; falling back to group-readable mode only" >&2 + chmod -R g+rX "${path}" + fi +} + +standard_media="${STANDARD_HOME_DIR}/cpackage" +require_dir "${standard_media}" + +chown -R "${STANDARD_RUN_USER}:${STANDARD_RUN_USER}" "${standard_media}" +chmod -R u+rwX,go-rwx "${standard_media}" +apply_acl "${standard_media}" + +if [ -d "${standard_media}/toolsh" ]; then + find "${standard_media}/toolsh" -type f -name "*.sh" -exec chmod 750 {} + +fi + +if [ -d "${standard_media}/rabq" ]; then + find "${standard_media}/rabq" -type f -name "*.sh" -exec chmod 750 {} + + find "${standard_media}/rabq" -type f -name "rabbitmqadmin" -exec chmod 755 {} + +fi + +for config in \ + "${standard_media}/cmsp/01/application-prd-ci01.properties" \ + "${standard_media}/cmsp/02/application-prd-ci02.properties" \ + "${standard_media}/cmtp/01/application-prd-ci01.properties" \ + "${standard_media}/cmtp/02/application-prd-ci02.properties" +do + if [ -f "${config}" ]; then + chown "${STANDARD_RUN_USER}:${STANDARD_RUN_USER}" "${config}" + chmod 640 "${config}" + if command -v setfacl >/dev/null 2>&1; then + setfacl -m "u:${STANDARD_RUN_USER}:rw,u:${TMS_RUN_USER}:r" "${config}" + fi + else + echo "warning: standard template config not found: ${config}" >&2 + fi +done + +for dir in \ + "${STANDARD_HOME_DIR}/cmsp" \ + "${STANDARD_HOME_DIR}/cmtp" \ + "${STANDARD_HOME_DIR}/cmep/license" \ + "${STANDARD_HOME_DIR}/mysql/loadfilepath" +do + mkdir -p "${dir}" + chown -R "${STANDARD_RUN_USER}:${STANDARD_RUN_USER}" "${dir}" + chmod 750 "${dir}" + if command -v setfacl >/dev/null 2>&1; then + setfacl -m "u:${STANDARD_RUN_USER}:rwx,u:${TMS_RUN_USER}:rwx" "${dir}" + setfacl -m "d:u:${STANDARD_RUN_USER}:rwX,d:u:${TMS_RUN_USER}:rwX" "${dir}" + fi +done + +if [ -f "${STANDARD_HOME_DIR}/cpconfig.cfg" ]; then + chown "${STANDARD_RUN_USER}:${STANDARD_RUN_USER}" "${STANDARD_HOME_DIR}/cpconfig.cfg" + chmod 640 "${STANDARD_HOME_DIR}/cpconfig.cfg" +else + touch "${STANDARD_HOME_DIR}/cpconfig.cfg" + chown "${STANDARD_RUN_USER}:${STANDARD_RUN_USER}" "${STANDARD_HOME_DIR}/cpconfig.cfg" + chmod 640 "${STANDARD_HOME_DIR}/cpconfig.cfg" +fi + +if command -v setfacl >/dev/null 2>&1; then + setfacl -m "u:${STANDARD_RUN_USER}:rw,u:${TMS_RUN_USER}:rw" "${STANDARD_HOME_DIR}/cpconfig.cfg" +fi + +echo "standard permissions prepared under ${STANDARD_HOME_DIR}" diff --git a/scripts/standard-init/root-sbin/tms-run-standard-vendor-step b/scripts/standard-init/root-sbin/tms-run-standard-vendor-step new file mode 100755 index 0000000..91692eb --- /dev/null +++ b/scripts/standard-init/root-sbin/tms-run-standard-vendor-step @@ -0,0 +1,57 @@ +#!/bin/bash +set -euo pipefail + +STANDARD_HOME_DIR="${STANDARD_HOME_DIR:-/home/cmep4i}" +STANDARD_RUN_USER="${STANDARD_RUN_USER:-cmep4i}" +STANDARD_JAVA_HOME="${STANDARD_JAVA_HOME:-${STANDARD_HOME_DIR}/jdk}" + +step="${1:-}" +case "${step}" in + setfraq) + script="${STANDARD_HOME_DIR}/cpackage/toolsh/setfraq.sh" + ;; + setftq) + script="${STANDARD_HOME_DIR}/cpackage/toolsh/setftq.sh" + ;; + settlq) + script="${STANDARD_HOME_DIR}/cpackage/toolsh/settlq.sh" + ;; + setsptp) + script="${STANDARD_HOME_DIR}/cpackage/toolsh/setsptp.sh" + ;; + *) + echo "unsupported standard vendor step: ${step}" >&2 + exit 64 + ;; +esac + +if [ "$(id -u)" != "0" ]; then + echo "this wrapper must be executed as root through sudo" >&2 + exit 77 +fi + +if [ ! -f "${script}" ]; then + echo "standard vendor script not found: ${script}" >&2 + exit 66 +fi + +run_payload=' +set -euo pipefail +standard_home="$1" +java_home="$2" +script="$3" + +cd "${standard_home}" +export JAVA_HOME="${java_home}" +export PATH="${JAVA_HOME}/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}" + +exec /bin/bash "${script}" +' + +if command -v runuser >/dev/null 2>&1; then + exec runuser -u "${STANDARD_RUN_USER}" -- /bin/bash -lc "${run_payload}" -- \ + "${STANDARD_HOME_DIR}" "${STANDARD_JAVA_HOME}" "${script}" +fi + +exec sudo -n -u "${STANDARD_RUN_USER}" /bin/bash -lc "${run_payload}" -- \ + "${STANDARD_HOME_DIR}" "${STANDARD_JAVA_HOME}" "${script}" diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java index 60049e9..3142b07 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java @@ -13,12 +13,18 @@ import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JnaPcieCryptoService implements PcieCryptoService { + private static final Logger log = LoggerFactory.getLogger(JnaPcieCryptoService.class); + private static final int ECC_KEY_BITS = 256; private static final int LMK_SEED_MAC_LENGTH = 8; + private static final long RECOVER_USER_KEY_ASYNC_DELAY_MILLIS = 10_000L; private static final int SDFE_INIT_STATUS_LOW_BYTE = 0x25; private static final int IK_KEY_TYPE_DEVICE = 1; private static final int IK_KEY_TYPE_AUTH = 2; @@ -31,10 +37,20 @@ public class JnaPcieCryptoService implements PcieCryptoService { private final PcieSessionTemplate sessionTemplate; private final boolean strictAlgIdValidation; + private final long recoverUserKeyAsyncDelayMillis; public JnaPcieCryptoService(PcieSessionTemplate sessionTemplate, CryptoCardProperties properties) { + this(sessionTemplate, properties, RECOVER_USER_KEY_ASYNC_DELAY_MILLIS); + } + + JnaPcieCryptoService( + PcieSessionTemplate sessionTemplate, + CryptoCardProperties properties, + long recoverUserKeyAsyncDelayMillis + ) { this.sessionTemplate = sessionTemplate; this.strictAlgIdValidation = properties.isStrictAlgIdValidation(); + this.recoverUserKeyAsyncDelayMillis = Math.max(0L, recoverUserKeyAsyncDelayMillis); } @Override @@ -1931,7 +1947,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { List userKeyRequests = requireUserKeyRequests(req.getUserKeyRequests()); try { - DeviceStatusResult deviceStatus = sessionTemplate.withSession("SDFE_RecoverMasterKeyMaterial", (lib, deviceHandle, sessionHandle) -> { + MasterKeyRecoveryResult result = sessionTemplate.withSession("SDFE_RecoverMasterKeyMaterial", (lib, deviceHandle, sessionHandle) -> { sessionTemplate.ensureSuccess( "SDFE_InitIdentify", lib.SDFE_InitIdentify(sessionHandle, oldPin, oldPin.length, newPin, newPin.length) @@ -1946,22 +1962,17 @@ public class JnaPcieCryptoService implements PcieCryptoService { "SDFE_RecoverIK_EX", lib.SDFE_RecoverIK_EX(sessionHandle, IK_KEY_TYPE_DEVICE, deviceIkComponent) ); - return readDeviceStatus(lib, sessionHandle); - }); - - byte[] seedMac = sessionTemplate.withSession("SDFE_RecoverUserKey", (lib, deviceHandle, sessionHandle) -> { - for (RecoverUserKeyRequest userKeyRequest : userKeyRequests) { - recoverUserKeyInCurrentSession(lib, sessionHandle, userKeyRequest); - } + DeviceStatusResult deviceStatus = readDeviceStatus(lib, sessionHandle); sessionTemplate.ensureSuccess("SDFE_CheckLMK", lib.SDFE_CheckLMK(sessionHandle)); byte[] mac = new byte[LMK_SEED_MAC_LENGTH]; sessionTemplate.ensureSuccess("SDFE_ExportLMKSeedMAC", lib.SDFE_ExportLMKSeedMAC(sessionHandle, mac)); - return Arrays.copyOf(mac, mac.length); + MasterKeyRecoveryResult recoveryResult = new MasterKeyRecoveryResult(); + recoveryResult.setDeviceStatus(deviceStatus); + recoveryResult.setLmkSeedMac(Arrays.copyOf(mac, mac.length)); + return recoveryResult; }); - MasterKeyRecoveryResult result = new MasterKeyRecoveryResult(); - result.setDeviceStatus(deviceStatus); - result.setLmkSeedMac(seedMac); + scheduleRecoverUserKeys(copyRecoverUserKeyRequests(userKeyRequests)); return result; } finally { wipe(oldPin); @@ -1975,6 +1986,35 @@ public class JnaPcieCryptoService implements PcieCryptoService { } } + private void scheduleRecoverUserKeys(List userKeyRequests) { + CompletableFuture.runAsync(() -> { + try { + sleepBeforeRecoverUserKeys(); + sessionTemplate.withSession("SDFE_RecoverUserKey", (lib, deviceHandle, sessionHandle) -> { + for (RecoverUserKeyRequest userKeyRequest : userKeyRequests) { + recoverUserKeyInCurrentSession(lib, sessionHandle, userKeyRequest); + } + return null; + }); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + log.warn("异步恢复用户密钥被中断"); + } catch (RuntimeException ex) { + log.error("异步恢复用户密钥失败", ex); + } finally { + for (RecoverUserKeyRequest userKeyRequest : userKeyRequests) { + wipe(userKeyRequest.getEncryptedKey()); + } + } + }); + } + + private void sleepBeforeRecoverUserKeys() throws InterruptedException { + if (recoverUserKeyAsyncDelayMillis > 0L) { + Thread.sleep(recoverUserKeyAsyncDelayMillis); + } + } + @Override public void generateIk(int keyType) { int safeKeyType = requireNonNegative("keyType", keyType); @@ -2134,6 +2174,21 @@ public class JnaPcieCryptoService implements PcieCryptoService { return List.copyOf(requests); } + private static List copyRecoverUserKeyRequests(List requests) { + return requests.stream() + .map(JnaPcieCryptoService::copyRecoverUserKeyRequest) + .toList(); + } + + private static RecoverUserKeyRequest copyRecoverUserKeyRequest(RecoverUserKeyRequest request) { + RecoverUserKeyRequest copy = new RecoverUserKeyRequest(); + copy.setKeyIndex(request.getKeyIndex()); + copy.setKeyType(request.getKeyType()); + copy.setStoreFlag(request.getStoreFlag()); + copy.setEncryptedKey(Arrays.copyOf(request.getEncryptedKey(), request.getEncryptedKey().length)); + return copy; + } + private void recoverUserKeyInCurrentSession( PcieNativeLibrary lib, Pointer sessionHandle, @@ -2624,4 +2679,4 @@ public class JnaPcieCryptoService implements PcieCryptoService { } return Arrays.copyOf(userId, userId.length); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java b/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java index 660b3d1..be0df3f 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java @@ -9,6 +9,7 @@ import java.util.Optional; public interface KeyEntityRepository { KeyEntity save(KeyEntity entity); void deleteById(Long id); + void deleteAll(); Optional findById(Long id); Optional findByKeyIdx(Integer keyIdx); List findAll(); diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java index 08536f5..a2aacde 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java @@ -38,6 +38,12 @@ public class KeyEntityRepositoryImpl implements KeyEntityRepository { mapper.deleteById(id); } + @Override + public void deleteAll() { + mapper.delete(new LambdaQueryWrapper() + .isNotNull(KeyEntity::getId)); + } + @Override public Optional findById(Long id) { return Optional.ofNullable(mapper.selectById(id)); diff --git a/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java b/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java index f9c98be..91397a6 100644 --- a/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java +++ b/src/main/java/com/cisd/tms/modules/init/config/InitCommandProfileService.java @@ -14,6 +14,7 @@ public class InitCommandProfileService { private static final String PROFILE_RESOURCE = "initplan/step-command-profiles.json"; private static final String STANDARD_HOME_DIR_PLACEHOLDER = "{{STANDARD_HOME_DIR}}"; + private static final String STANDARD_VENDOR_STEP_RUNNER_PLACEHOLDER = "{{STANDARD_VENDOR_STEP_RUNNER}}"; private final Map>> profileMap; private final InitExecutorProperties initExecutorProperties; @@ -65,7 +66,10 @@ public class InitCommandProfileService { private String interpolate(String rawCommand) { String standardHomeDir = normalize(initExecutorProperties.getStandardHomeDir()); - return rawCommand.replace(STANDARD_HOME_DIR_PLACEHOLDER, standardHomeDir); + String standardVendorStepRunner = normalize(initExecutorProperties.getStandardVendorStepRunnerCommand()); + return rawCommand + .replace(STANDARD_HOME_DIR_PLACEHOLDER, standardHomeDir) + .replace(STANDARD_VENDOR_STEP_RUNNER_PLACEHOLDER, standardVendorStepRunner); } private static String normalize(String value) { diff --git a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java index 80f56a0..e5f736f 100644 --- a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java +++ b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java @@ -11,8 +11,10 @@ public class InitExecutorProperties { LOCAL } - private static final String DEFAULT_STANDARD_HOME_DIR = "/home/cemp4i"; + private static final String DEFAULT_STANDARD_HOME_DIR = "/home/cmep4i"; private static final String DEFAULT_STANDARD_RUN_USER = "cmep4i"; + private static final String DEFAULT_STANDARD_VENDOR_STEP_RUNNER_COMMAND = + "/usr/local/sbin/tms-run-standard-vendor-step"; private Mode mode = Mode.SIMULATE; private int timeoutSec = 600; @@ -50,6 +52,8 @@ public class InitExecutorProperties { private String standardWebOrganizationJsonPath = "/usr/local/nginx/html/organization.json"; // 标准版 CMSP/CMTP 启动用户 private String standardRunUser = DEFAULT_STANDARD_RUN_USER; + // 标准版 vendor 客户化脚本受控执行入口;应部署为 root:root 且不允许 tms 写入 + private String standardVendorStepRunnerCommand = DEFAULT_STANDARD_VENDOR_STEP_RUNNER_COMMAND; // 标准版 CMSP/CMTP 启动命令 private String standardAppStartCommand = ""; // 标准版 CMSP/CMTP 停止命令 @@ -117,11 +121,7 @@ public class InitExecutorProperties { // 加密密钥,默认与官方 encrypt-decrypt.jar 保持一致 private String standardRabbitCredentialSecretKey = "ABCDEFGHIJKLMN1234567890"; // LOCAL 模式命令白名单前缀;仅命中前缀的命令允许执行 - private List whitelistPrefixes = List.of( - "bash /opt/CAE_Install_", - "systemctl ", - "echo " - ); + private List whitelistPrefixes = List.of(); public Mode getMode() { return mode; @@ -166,22 +166,13 @@ public class InitExecutorProperties { public List getWhitelistPrefixes() { if (whitelistPrefixes == null || whitelistPrefixes.isEmpty()) { return List.of( - "bash " + getStandardHomeDir() + "/cpackage/", + "sudo -n " + getStandardVendorStepRunnerCommand() + " ", "bash /opt/CAE_Install_", "systemctl ", "echo " ); } - boolean hasStandardPrefix = whitelistPrefixes.stream() - .filter(item -> item != null && !item.isBlank()) - .anyMatch(item -> item.startsWith("bash ") && item.contains("/cpackage/")); - if (hasStandardPrefix) { - return whitelistPrefixes; - } - java.util.ArrayList merged = new java.util.ArrayList<>(); - merged.add("bash " + getStandardHomeDir() + "/cpackage/"); - merged.addAll(whitelistPrefixes); - return merged; + return whitelistPrefixes; } public void setWhitelistPrefixes(List whitelistPrefixes) { @@ -308,6 +299,16 @@ public class InitExecutorProperties { this.standardRunUser = standardRunUser; } + public String getStandardVendorStepRunnerCommand() { + return isBlank(standardVendorStepRunnerCommand) + ? DEFAULT_STANDARD_VENDOR_STEP_RUNNER_COMMAND + : standardVendorStepRunnerCommand.trim(); + } + + public void setStandardVendorStepRunnerCommand(String standardVendorStepRunnerCommand) { + this.standardVendorStepRunnerCommand = standardVendorStepRunnerCommand; + } + public String getStandardAppStartCommand() { return standardAppStartCommand; } diff --git a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java index b78c508..f1e918e 100644 --- a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java +++ b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java @@ -262,26 +262,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { return InitStepExecutionResult.failure("mq_user_apply 执行失败:通道账号为空", -1, logPath); } - try { - Path logFile = prepareLogPath(task, step); - StandardRabbitSetupScriptContext scriptContext = prepareStandardRabbitSetupScriptContext(task, context); - appendLog(logFile, "rendered staged rabbit setup script with channel user: " + context.channelUsername()); - - int code = runCommand( - logFile, - Arrays.asList("bash", scriptContext.stagedSetupScript().toString()), - false, - scriptContext.stagingScriptDir() - ); - if (code != 0) { - return InitStepExecutionResult.failure("mq_user_apply 执行失败:setuprabq.sh执行失败", code, logFile.toString()); - } - return InitStepExecutionResult.success("MQ用户已通过setuprabq.sh应用", 0, logFile.toString()); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - String logPath = writeInternalLog(task, step, "mq_user_apply 被中断"); - return InitStepExecutionResult.failure("mq_user_apply 被中断", -1, logPath); - } + return executeDirectMqUserApply(task, step, context); } private InitStepExecutionResult executeMqQueueApply(InitTaskEntity task, InitTaskStepEntity step) throws IOException { @@ -348,16 +329,16 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { appendLog(logFile, "rabbitmqctl状态检查失败,继续执行应用流程"); } - runCommandAllowAlreadyExists(logFile, Arrays.asList(ctl, "add_vhost", vhost), "vhost"); - int addUserCode = runCommand(logFile, Arrays.asList(ctl, "add_user", user, pass), true); + runRabbitmqCommandAllowAlreadyExists(logFile, Arrays.asList(ctl, "add_vhost", vhost), "vhost"); + int addUserCode = runRabbitmqCommand(logFile, Arrays.asList(ctl, "add_user", user, pass), true); if (addUserCode != 0) { - int changeCode = runCommand(logFile, Arrays.asList(ctl, "change_password", user, pass), false); + int changeCode = runRabbitmqCommand(logFile, Arrays.asList(ctl, "change_password", user, pass), false); if (changeCode != 0) { return applyMqUserByRabbitmqAdminFallback(logFile, user, pass, vhost, addUserCode, changeCode); } } - int permissionCode = runCommand( + int permissionCode = runRabbitmqCommand( logFile, Arrays.asList(ctl, "set_permissions", "-p", vhost, user, ".*", ".*", ".*"), false @@ -592,7 +573,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private boolean waitRabbitmqReady(Path logFile, String ctl) throws IOException, InterruptedException { for (int i = 1; i <= 3; i++) { - int code = runCommand(logFile, Arrays.asList(ctl, "status"), true); + int code = runRabbitmqCommand(logFile, Arrays.asList(ctl, "status"), true); if (code == 0) { return true; } @@ -1110,13 +1091,13 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } for (String queue : buildStandardCustomerDefaultVhostQueues(normalize(context.orgCode()))) { - runCommand(logFile, Arrays.asList(ctl, "delete_queue", queue), true); + runRabbitmqCommand(logFile, Arrays.asList(ctl, "delete_queue", queue), true); } for (String queue : buildStandardRqVhostQueues(normalize(context.orgCode()))) { - runCommand(logFile, Arrays.asList(ctl, "delete_queue", "-p", vhost, queue), true); + runRabbitmqCommand(logFile, Arrays.asList(ctl, "delete_queue", "-p", vhost, queue), true); } - runCommand(logFile, Arrays.asList(ctl, "clear_permissions", "-p", vhost, user), true); - runCommand(logFile, Arrays.asList(ctl, "delete_user", user), true); + runRabbitmqCommand(logFile, Arrays.asList(ctl, "clear_permissions", "-p", vhost, user), true); + runRabbitmqCommand(logFile, Arrays.asList(ctl, "delete_user", user), true); return InitStepExecutionResult.success("mq cleanup 已应用", 0, logFile.toString()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); @@ -1931,6 +1912,17 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } } + private void runRabbitmqCommandAllowAlreadyExists(Path logFile, List command, String scene) throws IOException, InterruptedException { + int code = runRabbitmqCommand(logFile, command, true); + if (code != 0) { + appendLog(logFile, scene + " already exists or RabbitMQ 命令执行失败 with exit code=" + code + ", ignored"); + } + } + + private int runRabbitmqCommand(Path logFile, List command, boolean ignoreFailure) throws IOException, InterruptedException { + return runCommand(logFile, command, ignoreFailure, null, buildRabbitmqEnvironment()); + } + private int runCommand(Path logFile, List command, boolean ignoreFailure) throws IOException, InterruptedException { return runCommand(logFile, command, ignoreFailure, null); } @@ -1971,6 +1963,19 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { return exitCode; } + private Map buildRabbitmqEnvironment() { + Map environment = new LinkedHashMap<>(); + String existingPath = normalize(System.getenv("PATH")); + String path = "/usr/local/erlang/bin:/usr/local/rabbitmq/sbin:/usr/local/rabbitmq-server/sbin:" + + "/usr/lib/rabbitmq/bin:/usr/sbin:/usr/bin:/bin"; + if (!isBlank(existingPath)) { + path = path + ":" + existingPath; + } + environment.put("ERLANG_HOME", "/usr/local/erlang"); + environment.put("PATH", path); + return environment; + } + private Map buildStandardDbApplyEnvironment(Path loadDir, PlanContext context) { Map environment = new LinkedHashMap<>(); JdbcEndpoint endpoint = resolveStandardDbEndpoint(); @@ -2244,12 +2249,26 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private void verifyProcessAbsent(Path logFile, String pattern, String label, List failures) throws IOException { try { - int code = runCommand( - logFile, - Arrays.asList("/bin/bash", "-lc", "pgrep -f " + quoteShellValue(pattern) + " >/dev/null"), - true - ); - if (code == 0) { + ProcessBuilder processBuilder = new ProcessBuilder("ps", "-eo", "args="); + processBuilder.redirectErrorStream(true); + Process process = processBuilder.start(); + boolean finished = process.waitFor(properties.getTimeoutSec(), TimeUnit.SECONDS); + if (!finished) { + process.destroyForcibly(); + failures.add(label + " process verification 超时"); + return; + } + String output = new String(process.getInputStream().readAllBytes()); + if (process.exitValue() != 0) { + appendLog(logFile, "process verification command failed for " + label + ", exitCode=" + process.exitValue()); + failures.add(label + " process verification failed"); + return; + } + boolean running = output.lines() + .map(ConfigurableInitStepExecutor::normalize) + .anyMatch(line -> !isBlank(line) && line.contains(pattern)); + appendLog(logFile, "process verification " + label + ": pattern=" + pattern + ", running=" + running); + if (running) { failures.add(label + " process still running"); } } catch (InterruptedException ex) { @@ -2301,7 +2320,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private void verifyRabbitObjectAbsent(Path logFile, String shellCommand, String message, List failures) throws IOException { try { - int code = runCommand(logFile, Arrays.asList("/bin/bash", "-lc", shellCommand), true); + int code = runCommand(logFile, Arrays.asList("/bin/bash", "-lc", shellCommand), true, null, buildRabbitmqEnvironment()); if (code == 0) { failures.add(message); } diff --git a/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java b/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java index 1a6ebc8..7e17b4e 100644 --- a/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java @@ -8,6 +8,7 @@ import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey; import com.cisd.tms.integration.crypto.pcie.model.*; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.modules.auth.service.AuthSecurityResetService; +import com.cisd.tms.modules.cert.repository.KeyEntityRepository; import com.cisd.tms.modules.mk.common.LMKConstant; import com.cisd.tms.modules.mk.config.MasterKeyInitProperties; import com.cisd.tms.modules.mk.dto.*; @@ -23,6 +24,8 @@ import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * 主密钥生命周期服务实现。 @@ -58,22 +61,54 @@ public class LmkServiceImpl implements LmkService { private final Object backupRoundLock = new Object(); private CachedBackupRound cachedBackupRound; private final MasterKeyActivateRepository masterKeyActivateRepository; + private final KeyEntityRepository keyEntityRepository; + private final Executor masterKeyPostInitExecutor; @Autowired public LmkServiceImpl( PcieCryptoService pcieCryptoService, MasterKeyInitProperties masterKeyInitProperties, AuthSecurityResetService authSecurityResetService, - MasterKeyActivateRepository masterKeyActivateRepository + MasterKeyActivateRepository masterKeyActivateRepository, + KeyEntityRepository keyEntityRepository + ) { + this( + pcieCryptoService, + masterKeyInitProperties, + authSecurityResetService, + masterKeyActivateRepository, + keyEntityRepository, + newMasterKeyPostInitExecutor() + ); + } + + public LmkServiceImpl( + PcieCryptoService pcieCryptoService, + MasterKeyInitProperties masterKeyInitProperties, + AuthSecurityResetService authSecurityResetService, + MasterKeyActivateRepository masterKeyActivateRepository, + KeyEntityRepository keyEntityRepository, + Executor masterKeyPostInitExecutor ) { this.pcieCryptoService = pcieCryptoService; this.masterKeyInitProperties = masterKeyInitProperties; this.authSecurityResetService = authSecurityResetService; this.masterKeyActivateRepository = masterKeyActivateRepository; + this.keyEntityRepository = keyEntityRepository; + this.masterKeyPostInitExecutor = masterKeyPostInitExecutor == null ? Runnable::run : masterKeyPostInitExecutor; + } + + public LmkServiceImpl( + PcieCryptoService pcieCryptoService, + MasterKeyInitProperties masterKeyInitProperties, + AuthSecurityResetService authSecurityResetService, + MasterKeyActivateRepository masterKeyActivateRepository + ) { + this(pcieCryptoService, masterKeyInitProperties, authSecurityResetService, masterKeyActivateRepository, null, Runnable::run); } public LmkServiceImpl(PcieCryptoService pcieCryptoService, MasterKeyInitProperties masterKeyInitProperties, MasterKeyActivateRepository masterKeyActivateRepository) { - this(pcieCryptoService, masterKeyInitProperties, () -> { }, masterKeyActivateRepository); + this(pcieCryptoService, masterKeyInitProperties, () -> { }, masterKeyActivateRepository, null, Runnable::run); } @Override @@ -100,11 +135,7 @@ public class LmkServiceImpl implements LmkService { MasterKeyStateResult result = new MasterKeyStateResult(); result.setStatus(true); result.setSeedMac(Hex.toHexString(pcieCryptoService.initializeMasterKey(oldPin, newPin))); - authSecurityResetService.resetAfterMasterKeyInitialized(); - - masterKeyActivateEntity.setActivationStatus(false); - masterKeyActivateEntity.setEverInitialized(false); - masterKeyActivateRepository.update(masterKeyActivateEntity); + schedulePostMasterKeyInitMaintenance(masterKeyActivateEntity); return result; } @@ -118,6 +149,7 @@ public class LmkServiceImpl implements LmkService { pcieCryptoService.destroyLmk(); pcieCryptoService.destroyIk(IKEnums.KEY_TYPE_AUTH.getCode()); pcieCryptoService.destroyIk(IKEnums.KEY_TYPE_DEVICE.getCode()); + clearKeyEntityRegistry(); } @Override @@ -221,8 +253,9 @@ public class LmkServiceImpl implements LmkService { MasterKeyRecoveryResult recoveryResult = pcieCryptoService.recoverMasterKeyMaterial(recoveryRequest); DeviceStatusResult deviceStatus = recoveryResult.getDeviceStatus(); if (deviceStatus != null) { - log.info("device status:{}", deviceStatus.getFsmState()); + log.info("device status:{}/lmk seed mac:{}", deviceStatus.getFsmState(), recoveryResult.getLmkSeedMac()); } + clearKeyEntityRegistry(); MasterKeyStateResult result = new MasterKeyStateResult(); result.setStatus(true); result.setSeedMac(Hex.toHexString(recoveryResult.getLmkSeedMac())); @@ -239,6 +272,7 @@ public class LmkServiceImpl implements LmkService { pcieCryptoService.initIdentify(oldPin, newPin); pcieCryptoService.recoverLmkEx(Hex.decode(fullLmkHex)); pcieCryptoService.loadLmk(); + clearKeyEntityRegistry(); } @Override @@ -585,6 +619,48 @@ public class LmkServiceImpl implements LmkService { } } + private void schedulePostMasterKeyInitMaintenance(MasterKeyActivateEntity masterKeyActivateEntity) { + try { + masterKeyPostInitExecutor.execute(() -> runPostMasterKeyInitMaintenance(masterKeyActivateEntity)); + } catch (RuntimeException ex) { + log.error("提交主密钥初始化后置维护任务失败", ex); + } + } + + private void runPostMasterKeyInitMaintenance(MasterKeyActivateEntity masterKeyActivateEntity) { + try { + clearKeyEntityRegistry(); + } catch (RuntimeException ex) { + log.error("主密钥初始化后清理实体密钥登记表失败", ex); + } + try { + authSecurityResetService.resetAfterMasterKeyInitialized(); + } catch (RuntimeException ex) { + log.error("主密钥初始化后重置认证状态失败", ex); + } + try { + masterKeyActivateEntity.setActivationStatus(false); + masterKeyActivateEntity.setEverInitialized(false); + masterKeyActivateRepository.update(masterKeyActivateEntity); + } catch (RuntimeException ex) { + log.error("主密钥初始化后更新激活记录失败", ex); + } + } + + private void clearKeyEntityRegistry() { + if (keyEntityRepository != null) { + keyEntityRepository.deleteAll(); + } + } + + private static Executor newMasterKeyPostInitExecutor() { + return Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable, "tms-master-key-post-init"); + thread.setDaemon(true); + return thread; + }); + } + private record ParsedMasterKeyBackup(String lmkMacHex, Map componentMap) { } diff --git a/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java b/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java index 8cad805..34d92b2 100644 --- a/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java @@ -17,8 +17,8 @@ import com.cisd.tms.modules.mk.repository.MasterKeyActivateRepository; import com.cisd.tms.modules.mk.service.MasterKeyActivateService; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.ObjectMapper; -import com.sunyard.cisd.DeviceFingerprint; -import com.sunyard.cisd.DeviceFingerprintService; +import com.sunyard.cisd.device.tool.DeviceFingerprint; +import com.sunyard.cisd.device.tool.DeviceFingerprintService; import lombok.Data; import lombok.RequiredArgsConstructor; import org.bouncycastle.crypto.CipherParameters; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 11fa96f..aec6974 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -102,9 +102,9 @@ tms: # 标准版 CMSP/CMTP 停止命令。 standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:/home/tms/bin/stop_standard_apps.sh} # 标准版 nginx 启动命令。 - standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:/home/tms/bin/start_standard_nginx.sh} + standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-nginx} # 标准版 nginx 停止命令。 - standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:/home/tms/bin/stop_standard_nginx.sh} + standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:sudo -n /usr/local/sbin/tms-stop-standard-nginx} # 标准版 RabbitMQ 停止命令。 standard-rabbit-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_STOP_COMMAND:/home/tms/bin/stop_standard_rabbitmq.sh} # 标准版启动后检查命令。 @@ -114,7 +114,7 @@ tms: # 上传文件根目录;所有 fileId 均从该目录解析。 upload-base-dir: ${TMS_INIT_EXECUTOR_UPLOAD_BASE_DIR:/home/tms/uploads} # 初始化过程临时暂存目录(用于复制上传文件后再处理)。 - staging-root-dir: ${TMS_INIT_EXECUTOR_STAGING_ROOT_DIR:/home/tmp/tms-init-staging} + staging-root-dir: ${TMS_INIT_EXECUTOR_STAGING_ROOT_DIR:/home/tms/tmp/tms-init-staging} # 标准版收发器 license 固定落位路径。 standard-receiver-license-target-path: ${TMS_INIT_EXECUTOR_STANDARD_RECEIVER_LICENSE_TARGET_PATH:${tms.init.executor.standard-home-dir}/cmep/license/cmep.license} # 直参版收发器 license 固定落位路径。 @@ -137,10 +137,10 @@ tms: rabbit-admin-password: ${TMS_INIT_EXECUTOR_RABBIT_ADMIN_PASSWORD:admin} # RabbitMQ 控制命令(用于新增用户、授权等)。 # 建议线上配置为绝对路径,例如 /usr/local/rabbitmq/sbin/rabbitmqctl。 - rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:rabbitmqctl} + rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:/usr/local/rabbitmq/sbin/rabbitmqctl} # RabbitMQ Admin CLI 命令路径(用于创建队列等)。 rabbitmq-admin-command: ${TMS_INIT_EXECUTOR_RABBITMQ_ADMIN_COMMAND:/usr/sbin/rabbitmqadmin} - # 标准版 RabbitMQ 初始化脚本(会按前端输入替换 rConn/rConn 后执行)。 + # 标准版 RabbitMQ 初始化脚本路径(保留兼容;MQ_USER_APPLY 当前优先直接调用 rabbitmqctl/rabbitmqadmin)。 standard-rabbit-setup-script-path: ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_SETUP_SCRIPT_PATH:${tms.init.executor.standard-home-dir}/cpackage/rabq/setuprabq.sh} # 标准版 RabbitMQ 队列初始化脚本(默认执行 01 节点脚本)。 standard-rabbit-queue-script-path: ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_QUEUE_SCRIPT_PATH:${tms.init.executor.standard-home-dir}/cpackage/rabq/addrabq_R_01.sh} @@ -152,13 +152,15 @@ tms: standard-rabbit-app-config-patterns: - ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_APP_CONFIG_PATTERN_1:${tms.init.executor.standard-home-dir}/cmsp/application-prd*.properties} - ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_APP_CONFIG_PATTERN_2:${tms.init.executor.standard-home-dir}/cmtp/application-prd*.properties} + # 标准版 vendor 客户化脚本受控执行入口;该文件应由 root 部署到 /usr/local/sbin 且不可由 tms 写入。 + standard-vendor-step-runner-command: ${TMS_INIT_EXECUTOR_STANDARD_VENDOR_STEP_RUNNER_COMMAND:/usr/local/sbin/tms-run-standard-vendor-step} # 回写应用配置时,是否对 RabbitMQ 账号密码进行 3DES 加密。 standard-rabbit-credential-encrypt-enabled: ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_CREDENTIAL_ENCRYPT_ENABLED:true} # 3DES 加密密钥(需与收发器解密逻辑保持一致)。 standard-rabbit-credential-secret-key: ${TMS_INIT_EXECUTOR_STANDARD_RABBIT_CREDENTIAL_SECRET_KEY:ABCDEFGHIJKLMN1234567890} # LOCAL 模式命令白名单前缀;非白名单命令会被拒绝执行。 whitelist-prefixes: - - ${TMS_INIT_EXECUTOR_WHITELIST_PREFIX_1:bash ${tms.init.executor.standard-home-dir}/cpackage/} + - ${TMS_INIT_EXECUTOR_WHITELIST_PREFIX_1:sudo -n ${tms.init.executor.standard-vendor-step-runner-command} } - ${TMS_INIT_EXECUTOR_WHITELIST_PREFIX_2:bash /opt/CAE_Install_} - ${TMS_INIT_EXECUTOR_WHITELIST_PREFIX_3:systemctl } - ${TMS_INIT_EXECUTOR_WHITELIST_PREFIX_4:echo } @@ -191,7 +193,7 @@ tms: # 升级任务统一日志目录。 log-dir: ${TMS_UPGRADE_LOG_DIR:/home/tms/tmp/tms-upgrade-logs} # 升级包验签使用的公钥 PEM 文件路径;为空时升级预检会拒绝通过。 - signature-public-key-pem-path: ${TMS_UPGRADE_SIGNATURE_PUBLIC_KEY_PEM_PATH:/home/tms/cert/public_key.pem} + signature-public-key-pem-path: ${TMS_UPGRADE_SIGNATURE_PUBLIC_KEY_PEM_PATH:/home/tms/device/dev.mac.pub} backup: # 资源备份包输出目录;第一版先在本机生成可见的 .tmsbak 文件。 output-dir: ${TMS_BACKUP_OUTPUT_DIR:/home/tms/tmp/resource-backup-packages} @@ -315,8 +317,6 @@ tms: - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_ROLE_UKEY:tms_role_ukey_binding} - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_FULL:tms_auth_full_account} - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_SESSION:tms_auth_session} - - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_CHALLENGE:tms_auth_challenge} - - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_AUDIT:tms_auth_audit_log} - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_RESOURCE_BACKUP_TASK:tms_resource_backup_task} - ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_RESOURCE_RESTORE_TASK:tms_resource_restore_task} # 备份/恢复标准收发器业务库名;第一版默认使用 CMEP。 @@ -340,7 +340,7 @@ tms: internal-token: ${TMS_INTERNAL_TOKEN:change-me-internal-token} internal-auth: # 是否启用 /api/** 的内部登录鉴权;开发联调时可临时关闭。 - enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:true} + enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:false} # 关闭内部鉴权后,注入请求上下文的调试角色。 debug-role-code: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_ROLE_CODE:OPS_ADMIN} # 关闭内部鉴权后,注入请求上下文的调试认证等级。 @@ -349,7 +349,7 @@ tms: debug-session-token: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_SESSION_TOKEN:DEBUG-BYPASS} replay: # 是否启用防重放校验;本地 Postman/联调可临时设为 false,生产环境应保持 true。 - enabled: ${TMS_SECURITY_REPLAY_ENABLED:true} + enabled: ${TMS_SECURITY_REPLAY_ENABLED:false} openapi: # 外部签名服务接口允许的时间戳偏差(秒),防重放。 timestamp-skew-seconds: 300 diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index 8947ec6..a9c6890 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS tms_init_task ( create_time DATETIME(3) NOT NULL, update_time DATETIME(3) NOT NULL, UNIQUE KEY uk_tms_init_task_task_id (task_id) -); +) COMMENT='初始化任务表'; CREATE TABLE IF NOT EXISTS tms_init_task_step ( id BIGINT PRIMARY KEY, @@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS tms_init_task_step ( update_time DATETIME(3) NOT NULL, UNIQUE KEY uk_tms_init_task_step_task_no (task_id, step_no), KEY idx_tms_init_task_step_task_id (task_id) -); +) COMMENT='初始化任务步骤表'; -- ----------------------------------------------------------------------------- -- 资源备份与恢复 @@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS tms_resource_backup_task ( UNIQUE KEY uk_tms_resource_backup_task_backup_id (backup_id), KEY idx_tms_resource_backup_task_status (status), KEY idx_tms_resource_backup_task_create_time (create_time) -); +) COMMENT='资源备份任务表'; CREATE TABLE IF NOT EXISTS tms_resource_restore_task ( id BIGINT PRIMARY KEY, @@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS tms_resource_restore_task ( UNIQUE KEY uk_tms_resource_restore_task_precheck_id (precheck_id), KEY idx_tms_resource_restore_task_status (status), KEY idx_tms_resource_restore_task_create_time (create_time) -); +) COMMENT='资源恢复任务表'; -- ----------------------------------------------------------------------------- -- 认证与设备基础表 -- ----------------------------------------------------------------------------- @@ -127,7 +127,7 @@ CREATE TABLE IF NOT EXISTS tms_device_node ( create_time DATETIME(3) NOT NULL, update_time DATETIME(3) NOT NULL, UNIQUE KEY uk_tms_device_node_node_id (node_id) -); +) COMMENT='设备节点表'; CREATE TABLE IF NOT EXISTS tms_file_record ( id BIGINT PRIMARY KEY, @@ -138,7 +138,7 @@ CREATE TABLE IF NOT EXISTS tms_file_record ( create_time DATETIME(3) NOT NULL, update_time DATETIME(3) NOT NULL, UNIQUE KEY uk_tms_file_record_file_id (file_id) -); +) COMMENT='文件记录表'; -- ----------------------------------------------------------------------------- -- 设备软件版本 @@ -155,7 +155,7 @@ CREATE TABLE IF NOT EXISTS tms_device_software_version ( create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_tms_device_software_version_component_code (component_code) -); +) COMMENT='设备软件版本表'; INSERT INTO tms_device_software_version (id, component_code, component_name, current_version, source_type, detected_at, remarks) @@ -204,7 +204,7 @@ CREATE TABLE IF NOT EXISTS tms_upgrade_task ( KEY idx_tms_upgrade_task_status (status), KEY idx_tms_upgrade_task_task_type (task_type), KEY idx_tms_upgrade_task_create_time (create_time) -); +) COMMENT='升级任务表'; -- ----------------------------------------------------------------------------- -- 新认证体系 @@ -219,7 +219,7 @@ CREATE TABLE IF NOT EXISTS tms_role_account ( create_time DATETIME(3) NOT NULL, update_time DATETIME(3) NOT NULL, UNIQUE KEY uk_tms_role_account_role_code (role_code) -); +) COMMENT='角色账号表'; CREATE TABLE IF NOT EXISTS tms_role_ukey_binding ( id BIGINT PRIMARY KEY, @@ -236,7 +236,7 @@ CREATE TABLE IF NOT EXISTS tms_role_ukey_binding ( UNIQUE KEY uk_tms_role_ukey_binding_role_uid_serial (role_code, uid, ukey_serial), KEY idx_tms_role_ukey_binding_role_uid_status (role_code, uid, status), KEY idx_tms_role_ukey_binding_role_status (role_code, status) -); +) COMMENT='角色UKey绑定表'; CREATE TABLE IF NOT EXISTS tms_auth_full_account ( id BIGINT PRIMARY KEY, @@ -258,7 +258,7 @@ CREATE TABLE IF NOT EXISTS tms_auth_full_account ( UNIQUE KEY uk_tms_auth_full_account_role_uid (role_code, uid), UNIQUE KEY uk_tms_auth_full_account_name (account_name), KEY idx_tms_auth_full_account_role_code (role_code) -); +) COMMENT='完整认证账号表'; CREATE TABLE IF NOT EXISTS tms_auth_session ( id BIGINT PRIMARY KEY, @@ -275,7 +275,7 @@ CREATE TABLE IF NOT EXISTS tms_auth_session ( update_time DATETIME(3) NOT NULL, UNIQUE KEY uk_tms_auth_session_token (session_token), KEY idx_tms_auth_session_role_code (role_code) -); +) COMMENT='认证会话表'; -- ----------------------------------------------------------------------------- -- 防重放与安全事件 @@ -298,7 +298,7 @@ CREATE TABLE IF NOT EXISTS tms_replay_nonce ( UNIQUE KEY uk_tms_replay_nonce_scope_principal_nonce (scope, principal_id, nonce), KEY idx_tms_replay_nonce_expires_at (expires_at), KEY idx_tms_replay_nonce_principal_time (principal_id, create_time) -); +) COMMENT='防重放nonce表'; CREATE TABLE IF NOT EXISTS tms_security_event ( id BIGINT PRIMARY KEY, @@ -314,7 +314,7 @@ CREATE TABLE IF NOT EXISTS tms_security_event ( update_time DATETIME(3) NOT NULL, KEY idx_tms_security_event_event_type (event_type), KEY idx_tms_security_event_scope_principal (scope, principal_id) -); +) COMMENT='安全事件表'; INSERT IGNORE INTO tms_role_account ( id, @@ -483,7 +483,7 @@ CREATE TABLE IF NOT EXISTS tms_access_whitelist ( mask VARCHAR(10) NOT NULL, create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) - ); +) COMMENT='访问白名单表'; -- ----------------------------------------------------------------------------- @@ -512,7 +512,7 @@ CREATE TABLE IF NOT EXISTS tms_key_entity ( KEY idx_tms_key_entity_algo (algo_type), KEY idx_tms_key_entity_status (status), KEY idx_tms_key_entity_create_time (create_time) -); +) COMMENT='密钥实体表'; CREATE TABLE IF NOT EXISTS tms_certificate ( id BIGINT PRIMARY KEY, @@ -537,7 +537,7 @@ CREATE TABLE IF NOT EXISTS tms_certificate ( KEY idx_tms_certificate_issuer_dn (issuer_dn(512)), KEY idx_tms_certificate_valid_to (valid_to), KEY idx_tms_certificate_import_time (import_time) -); +) COMMENT='证书表'; CREATE TABLE IF NOT EXISTS tms_trusted_cert ( id BIGINT PRIMARY KEY, @@ -560,7 +560,7 @@ CREATE TABLE IF NOT EXISTS tms_trusted_cert ( KEY idx_tms_trusted_cert_subject_dn (subject_dn(512)), KEY idx_tms_trusted_cert_issuer_dn (issuer_dn(512)), KEY idx_tms_trusted_cert_valid_to (valid_to) -); +) COMMENT='可信证书表'; CREATE TABLE IF NOT EXISTS tms_certificate_crl ( id BIGINT PRIMARY KEY, @@ -580,7 +580,7 @@ CREATE TABLE IF NOT EXISTS tms_certificate_crl ( KEY idx_tms_certificate_crl_issuer_dn (issuer_dn(512)), KEY idx_tms_certificate_crl_this_update (this_update), KEY idx_tms_certificate_crl_next_update (next_update) -); +) COMMENT='证书CRL表'; CREATE TABLE IF NOT EXISTS tms_certificate_crl_revoked ( id BIGINT PRIMARY KEY, @@ -598,7 +598,7 @@ CREATE TABLE IF NOT EXISTS tms_certificate_crl_revoked ( KEY idx_tms_certificate_crl_revoked_issuer_serial (issuer_dn(512), serial_number), KEY idx_tms_certificate_crl_revoked_subject_dn (subject_dn(512)), KEY idx_tms_certificate_crl_revoked_revocation_time (revocation_time) -); +) COMMENT='证书CRL吊销明细表'; CREATE TABLE IF NOT EXISTS tms_certificate_crl_import_task ( id BIGINT PRIMARY KEY, @@ -617,7 +617,7 @@ CREATE TABLE IF NOT EXISTS tms_certificate_crl_import_task ( UNIQUE KEY uk_tms_certificate_crl_import_task_task_id (task_id), KEY idx_tms_certificate_crl_import_task_status (status), KEY idx_tms_certificate_crl_import_task_create_time (create_time) -); +) COMMENT='证书CRL导入任务表'; -- ----------------------------------------------------------------------------- @@ -650,7 +650,7 @@ CREATE TABLE IF NOT EXISTS tms_operation_audit_log ( KEY idx_tms_operation_audit_log_result (operation_result), KEY idx_tms_operation_audit_log_audit_status (audit_status), KEY idx_tms_operation_audit_log_occurred_at (occurred_at) -); +) COMMENT='操作审计日志表'; @@ -672,7 +672,7 @@ CREATE TABLE IF NOT EXISTS tms_log_backup_record ( UNIQUE KEY `uk_record_id` (`record_id`), KEY `idx_create_time` (`create_time`), KEY `idx_backup_type` (`backup_type`) -); +) COMMENT='日志备份记录表'; @@ -687,7 +687,7 @@ CREATE TABLE IF NOT EXISTS tms_backup_config ( last_backup_time datetime DEFAULT NULL COMMENT '上次执行时间', sign_data varchar(255) NOT NULL COMMENT '其他字段的签名值', PRIMARY KEY (`id`) -); +) COMMENT='日志备份配置表'; CREATE TABLE IF NOT EXISTS tms_master_key_activate ( @@ -699,7 +699,7 @@ CREATE TABLE IF NOT EXISTS tms_master_key_activate ( create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) -); +) COMMENT='主密钥激活状态表'; INSERT IGNORE INTO tms_master_key_activate ( id, diff --git a/src/main/resources/initplan/step-command-profiles.json b/src/main/resources/initplan/step-command-profiles.json index da31fae..dac8a32 100644 --- a/src/main/resources/initplan/step-command-profiles.json +++ b/src/main/resources/initplan/step-command-profiles.json @@ -3,12 +3,12 @@ "RABBITMQ": { "RENDER_CONFIG": "internal::render_config", "FILE_PREPARE": "internal::file_prepare", - "SET_RABBITMQ": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setfraq.sh", + "SET_RABBITMQ": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setfraq", "MQ_USER_APPLY": "internal::mq_user_apply", "MQ_QUEUE_APPLY": "internal::mq_queue_apply", "DB_RENDER": "internal::db_render", "DB_APPLY": "internal::db_apply", - "DEPLOY_RECEIVER": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setsptp.sh", + "DEPLOY_RECEIVER": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setsptp", "APP_MQ_CREDENTIALS_APPLY": "internal::app_mq_credentials_apply", "NGINX_CONFIG_APPLY": "internal::nginx_config_apply", "WEB_CONFIG_APPLY": "internal::web_config_apply", @@ -20,11 +20,11 @@ "TLQ": { "RENDER_CONFIG": "internal::render_config", "FILE_PREPARE": "internal::file_prepare", - "SET_TLQ": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setftq.sh", - "TLQ_CONF_APPLY": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/settlq.sh", + "SET_TLQ": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setftq", + "TLQ_CONF_APPLY": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} settlq", "DB_RENDER": "internal::db_render", "DB_APPLY": "internal::db_apply", - "DEPLOY_RECEIVER": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setsptp.sh", + "DEPLOY_RECEIVER": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setsptp", "NGINX_CONFIG_APPLY": "internal::nginx_config_apply", "WEB_CONFIG_APPLY": "internal::web_config_apply", "START_CMSP_CMTP": "internal::start_cmsp_cmtp", @@ -37,12 +37,12 @@ "RABBITMQ": { "RENDER_CONFIG": "internal::render_config", "FILE_PREPARE": "internal::file_prepare", - "SET_RABBITMQ": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setfraq.sh", + "SET_RABBITMQ": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setfraq", "MQ_USER_APPLY": "internal::mq_user_apply", "MQ_QUEUE_APPLY": "internal::mq_queue_apply", "DB_RENDER": "internal::db_render", "DB_APPLY": "internal::db_apply", - "DEPLOY_RECEIVER": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setsptp.sh", + "DEPLOY_RECEIVER": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setsptp", "APP_MQ_CREDENTIALS_APPLY": "internal::app_mq_credentials_apply", "NGINX_CONFIG_APPLY": "internal::nginx_config_apply", "WEB_CONFIG_APPLY": "internal::web_config_apply", @@ -54,13 +54,13 @@ "RABBITMQ_TLQ": { "RENDER_CONFIG": "internal::render_config", "FILE_PREPARE": "internal::file_prepare", - "SET_TLQ": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setftq.sh", - "TLQ_CONF_APPLY": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/settlq.sh", + "SET_TLQ": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setftq", + "TLQ_CONF_APPLY": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} settlq", "MQ_USER_APPLY": "internal::mq_user_apply", "MQ_QUEUE_APPLY": "internal::mq_queue_apply", "DB_RENDER": "internal::db_render", "DB_APPLY": "internal::db_apply", - "DEPLOY_RECEIVER": "bash {{STANDARD_HOME_DIR}}/cpackage/toolsh/setsptp.sh", + "DEPLOY_RECEIVER": "sudo -n {{STANDARD_VENDOR_STEP_RUNNER}} setsptp", "APP_MQ_CREDENTIALS_APPLY": "internal::app_mq_credentials_apply", "NGINX_CONFIG_APPLY": "internal::nginx_config_apply", "WEB_CONFIG_APPLY": "internal::web_config_apply", diff --git a/src/test/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoServiceTest.java b/src/test/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoServiceTest.java index fe7d29c..540ced3 100644 --- a/src/test/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoServiceTest.java +++ b/src/test/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoServiceTest.java @@ -11,7 +11,6 @@ import com.cisd.tms.integration.crypto.pcie.jna.SdfeDeviceStatus; import com.cisd.tms.integration.crypto.pcie.jna.SdfeIkComponent; import com.cisd.tms.integration.crypto.pcie.jna.SdfeLmkComponent; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; -import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult; import com.cisd.tms.integration.crypto.pcie.model.DigestRequest; import com.cisd.tms.integration.crypto.pcie.model.EccInternalDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.EccInternalEncryptRequest; @@ -40,6 +39,8 @@ import com.sun.jna.ptr.IntByReference; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -142,10 +143,10 @@ class JnaPcieCryptoServiceTest { } @Test - void shouldRecoverMasterKeyMaterialWithUserKeysInSeparateSessionAndReturnSeedMac() { + void shouldRecoverMasterKeyMaterialWithUserKeysInSeparateSessionAndReturnSeedMac() throws InterruptedException { PcieSessionTemplate sessionTemplate = Mockito.mock(PcieSessionTemplate.class); CryptoCardProperties properties = new CryptoCardProperties(); - JnaPcieCryptoService service = new JnaPcieCryptoService(sessionTemplate, properties); + JnaPcieCryptoService service = new JnaPcieCryptoService(sessionTemplate, properties, 0L); Pointer restoreSession = Pointer.createConstant(2); Pointer userKeySession = Pointer.createConstant(3); byte[] seedMac = new byte[] {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; @@ -168,11 +169,12 @@ class JnaPcieCryptoServiceTest { AtomicReference restoreLibRef = new AtomicReference<>(); AtomicReference userKeyLibRef = new AtomicReference<>(); + CountDownLatch recoverUserKeyStarted = new CountDownLatch(1); Mockito.when(sessionTemplate.withSession(Mockito.eq("SDFE_RecoverMasterKeyMaterial"), Mockito.any())) .thenAnswer(invocation -> { @SuppressWarnings("unchecked") - PcieSessionTemplate.SessionCallback callback = - (PcieSessionTemplate.SessionCallback) invocation.getArgument(1); + PcieSessionTemplate.SessionCallback callback = + (PcieSessionTemplate.SessionCallback) invocation.getArgument(1); PcieNativeLibrary lib = Mockito.mock(PcieNativeLibrary.class); restoreLibRef.set(lib); Mockito.when(lib.SDFE_InitIdentify(Mockito.eq(restoreSession), Mockito.any(), Mockito.anyInt(), Mockito.any(), Mockito.anyInt())).thenReturn(0); @@ -181,29 +183,34 @@ class JnaPcieCryptoServiceTest { Mockito.when(lib.SDFE_RecoverIK_EX(Mockito.eq(restoreSession), Mockito.eq(2), Mockito.any(SdfeIkComponent.class))).thenReturn(0); Mockito.when(lib.SDFE_RecoverIK_EX(Mockito.eq(restoreSession), Mockito.eq(1), Mockito.any(SdfeIkComponent.class))).thenReturn(0); Mockito.when(lib.SDFE_DeviceStatusGet(Mockito.eq(restoreSession), Mockito.any(SdfeDeviceStatus.class))).thenReturn(0); + Mockito.when(lib.SDFE_CheckLMK(Mockito.eq(restoreSession))).thenReturn(0); + Mockito.when(lib.SDFE_ExportLMKSeedMAC(Mockito.eq(restoreSession), Mockito.any())).thenAnswer(exportInvocation -> { + byte[] out = exportInvocation.getArgument(1); + System.arraycopy(seedMac, 0, out, 0, seedMac.length); + return 0; + }); return callback.apply(lib, Pointer.createConstant(1), restoreSession); }); Mockito.when(sessionTemplate.withSession(Mockito.eq("SDFE_RecoverUserKey"), Mockito.any())) .thenAnswer(invocation -> { @SuppressWarnings("unchecked") - PcieSessionTemplate.SessionCallback callback = - (PcieSessionTemplate.SessionCallback) invocation.getArgument(1); + PcieSessionTemplate.SessionCallback callback = + (PcieSessionTemplate.SessionCallback) invocation.getArgument(1); PcieNativeLibrary lib = Mockito.mock(PcieNativeLibrary.class); userKeyLibRef.set(lib); Mockito.when(lib.SDFE_RecoverUserKey(Mockito.eq(userKeySession), Mockito.eq(1), Mockito.anyInt(), Mockito.any(), Mockito.anyInt(), Mockito.anyByte())).thenReturn(0); - Mockito.when(lib.SDFE_CheckLMK(Mockito.eq(userKeySession))).thenReturn(0); - Mockito.when(lib.SDFE_ExportLMKSeedMAC(Mockito.eq(userKeySession), Mockito.any())).thenAnswer(exportInvocation -> { - byte[] out = exportInvocation.getArgument(1); - System.arraycopy(seedMac, 0, out, 0, seedMac.length); - return 0; - }); - return callback.apply(lib, Pointer.createConstant(1), userKeySession); + try { + return callback.apply(lib, Pointer.createConstant(1), userKeySession); + } finally { + recoverUserKeyStarted.countDown(); + } }); MasterKeyRecoveryResult result = service.recoverMasterKeyMaterial(request); Assertions.assertArrayEquals(seedMac, result.getLmkSeedMac()); Assertions.assertNotNull(result.getDeviceStatus()); + Assertions.assertTrue(recoverUserKeyStarted.await(1, TimeUnit.SECONDS)); InOrder sessionOrder = Mockito.inOrder(sessionTemplate); sessionOrder.verify(sessionTemplate).withSession(Mockito.eq("SDFE_RecoverMasterKeyMaterial"), Mockito.any()); sessionOrder.verify(sessionTemplate).withSession(Mockito.eq("SDFE_RecoverUserKey"), Mockito.any()); @@ -216,13 +223,16 @@ class JnaPcieCryptoServiceTest { restoreOrder.verify(restoreLib).SDFE_RecoverIK_EX(Mockito.eq(restoreSession), Mockito.eq(2), Mockito.any(SdfeIkComponent.class)); restoreOrder.verify(restoreLib).SDFE_RecoverIK_EX(Mockito.eq(restoreSession), Mockito.eq(1), Mockito.any(SdfeIkComponent.class)); restoreOrder.verify(restoreLib).SDFE_DeviceStatusGet(Mockito.eq(restoreSession), Mockito.any(SdfeDeviceStatus.class)); + restoreOrder.verify(restoreLib).SDFE_CheckLMK(Mockito.eq(restoreSession)); + restoreOrder.verify(restoreLib).SDFE_ExportLMKSeedMAC(Mockito.eq(restoreSession), Mockito.any()); Mockito.verify(restoreLib, Mockito.never()).SDFE_RecoverUserKey(Mockito.any(), Mockito.anyInt(), Mockito.anyInt(), Mockito.any(), Mockito.anyInt(), Mockito.anyByte()); PcieNativeLibrary userKeyLib = userKeyLibRef.get(); InOrder userKeyOrder = Mockito.inOrder(userKeyLib); userKeyOrder.verify(userKeyLib, Mockito.times(2)).SDFE_RecoverUserKey(Mockito.eq(userKeySession), Mockito.eq(1), Mockito.anyInt(), Mockito.any(), Mockito.anyInt(), Mockito.anyByte()); - userKeyOrder.verify(userKeyLib).SDFE_CheckLMK(Mockito.eq(userKeySession)); - userKeyOrder.verify(userKeyLib).SDFE_ExportLMKSeedMAC(Mockito.eq(userKeySession), Mockito.any()); + Mockito.verify(userKeyLib, Mockito.never()).SDFE_LoadLMK(Mockito.any()); + Mockito.verify(userKeyLib, Mockito.never()).SDFE_CheckLMK(Mockito.any()); + Mockito.verify(userKeyLib, Mockito.never()).SDFE_ExportLMKSeedMAC(Mockito.any(), Mockito.any()); } @Test diff --git a/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java b/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java index 5981815..6efb660 100644 --- a/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java @@ -219,6 +219,46 @@ class ApplyResourceBackupScriptContractTest { Assertions.assertTrue(status.contains("\"phase\":\"FILE\"")); } + @Test + void shouldFailAndAttemptServiceStartWhenFileCopyCommandFails() throws Exception { + Path workDir = tempDir.resolve("work"); + Path payloadDir = workDir.resolve("payload"); + Files.createDirectories(payloadDir.resolve("resources")); + Files.writeString(payloadDir.resolve("payload-manifest.json"), "{\"resources\":[]}"); + Files.writeString(payloadDir.resolve("resources/cpconfig.cfg"), "content"); + Files.writeString(workDir.resolve("restore-files.tsv"), + "resources/cpconfig.cfg\t" + tempDir.resolve("target/cpconfig.cfg") + System.lineSeparator()); + Path commandLog = tempDir.resolve("file-copy-recovery.log"); + Path stopScript = fakeScript("stop-tms.sh", commandLog); + Path startScript = fakeScript("start-tms.sh", commandLog); + Files.writeString(workDir.resolve("restore-stop-commands.tsv"), + "TMS\ttrue\t" + stopScript + System.lineSeparator()); + Files.writeString(workDir.resolve("restore-start-commands.tsv"), + "TMS\ttrue\t" + startScript + System.lineSeparator()); + Path binDir = tempDir.resolve("bin"); + Files.createDirectories(binDir); + Path cp = binDir.resolve("cp"); + Files.writeString(cp, """ + #!/usr/bin/env bash + printf 'cp failed %%s\\n' "$*" >> "%s" + exit 1 + """.formatted(commandLog)); + cp.toFile().setExecutable(true); + + int exitCode = runScriptForExit(workDir, Map.of( + "PATH", binDir + ":" + System.getenv("PATH") + )); + + Assertions.assertEquals(1, exitCode); + String log = Files.readString(commandLog); + Assertions.assertTrue(log.indexOf("stop-tms.sh") < log.indexOf("cp failed")); + Assertions.assertTrue(log.indexOf("cp failed") < log.indexOf("start-tms.sh")); + String status = Files.readString(workDir.resolve("status.json")); + Assertions.assertTrue(status.contains("\"status\":\"FAILED\"")); + Assertions.assertTrue(status.contains("\"phase\":\"FILE\"")); + Assertions.assertTrue(status.contains("restore file failed")); + } + @Test void shouldFailAndAttemptServiceStartWhenDatabaseRestoreTimesOut() throws Exception { Path workDir = tempDir.resolve("work"); diff --git a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java index 57061a4..ffeb133 100644 --- a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java +++ b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java @@ -1030,6 +1030,7 @@ class ConfigurableInitStepExecutorTest { Path stopNginxScript = scriptDir.resolve("stop_standard_nginx.sh"); Path stopRabbitScript = scriptDir.resolve("stop_standard_rabbitmq.sh"); Path checkScript = scriptDir.resolve("check_standard_runtime.sh"); + Path vendorStepRunner = scriptDir.resolve("root-sbin/tms-run-standard-vendor-step"); Assertions.assertTrue(Files.exists(dbScript)); Assertions.assertTrue(Files.exists(appScript)); @@ -1038,6 +1039,7 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(Files.exists(stopNginxScript)); Assertions.assertTrue(Files.exists(stopRabbitScript)); Assertions.assertTrue(Files.exists(checkScript)); + Assertions.assertTrue(Files.exists(vendorStepRunner)); String dbContent = Files.readString(dbScript); Assertions.assertTrue(dbContent.contains("SCHEMA-DDL.sql")); @@ -1049,7 +1051,7 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(dbContent.contains("CIPS_USER_PARAM_INFO")); Assertions.assertTrue(dbContent.contains("ORG_CHINESE_NAME")); Assertions.assertTrue(dbContent.contains("--force")); - Assertions.assertFalse(dbContent.contains("/home/cemp4i/mysql/loadfilepath")); + Assertions.assertFalse(dbContent.contains("/home/cmep4i/mysql/loadfilepath")); Assertions.assertTrue(dbContent.indexOf("CMEP.sql") < dbContent.indexOf("UP-ORG-INFO.sql")); Assertions.assertTrue(dbContent.indexOf("UP-ORG-INFO.sql") < dbContent.indexOf("SCHEMA-DDL.sql")); @@ -1059,7 +1061,7 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(appContent.contains("stop_standard_apps.sh")); Assertions.assertTrue(appContent.contains("start_cmsp.sh")); Assertions.assertTrue(appContent.contains("start_cmtp.sh")); - Assertions.assertFalse(appContent.contains("/home/cemp4i/cmsp/start_cmsp.sh")); + Assertions.assertFalse(appContent.contains("/home/cmep4i/cmsp/start_cmsp.sh")); Assertions.assertFalse(appContent.contains("su - cmep4i")); String stopAppContent = Files.readString(stopAppScript); @@ -1071,6 +1073,14 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(stopAppContent.contains("CMEP-CMTP")); Assertions.assertTrue(stopAppContent.contains("wait_until_stopped")); + String vendorRunnerContent = Files.readString(vendorStepRunner); + Assertions.assertTrue(vendorRunnerContent.contains("STANDARD_RUN_USER")); + Assertions.assertTrue(vendorRunnerContent.contains("setfraq")); + Assertions.assertTrue(vendorRunnerContent.contains("setftq")); + Assertions.assertTrue(vendorRunnerContent.contains("settlq")); + Assertions.assertTrue(vendorRunnerContent.contains("setsptp")); + Assertions.assertTrue(vendorRunnerContent.contains("runuser -u")); + String stopRabbitContent = Files.readString(stopRabbitScript); Assertions.assertTrue(stopRabbitContent.contains("systemctl stop")); Assertions.assertTrue(stopRabbitContent.contains("rabbitmq-server")); @@ -1162,12 +1172,13 @@ class ConfigurableInitStepExecutorTest { } @Test - void shouldRewriteAndExecuteStandardRabbitSetupScript() throws IOException { + void shouldApplyStandardRabbitUserByRabbitmqCtl() throws IOException { Path tempRoot = Files.createTempDirectory("init-executor-rabbit-setup-script-test"); Path setupScript = tempRoot.resolve("setuprabq.sh"); Path queueScript = tempRoot.resolve("addrabq_R_01.sh"); Path cpconfig = tempRoot.resolve("cpconfig.cfg"); Path calls = tempRoot.resolve("calls.log"); + Path rabbitmqctl = tempRoot.resolve("rabbitmqctl"); Files.writeString(cpconfig, "MY_CIPSID_OR_BIC=AAAABBBBXXX\n"); Files.writeString( setupScript, @@ -1181,6 +1192,13 @@ class ConfigurableInitStepExecutorTest { + "rabbitmqctl set_permissions -p /RQ rConn '.*' '.*' '.*'\n" ); Files.writeString(queueScript, "#!/bin/bash\necho AAAABBBBXXX > /dev/null\n"); + Files.writeString( + rabbitmqctl, + "#!/bin/bash\n" + + "echo \"$*\" >> \"" + calls + "\"\n" + + "exit 0\n" + ); + Assertions.assertTrue(rabbitmqctl.toFile().setExecutable(true)); InitExecutorProperties properties = new InitExecutorProperties(); properties.setMode(InitExecutorProperties.Mode.LOCAL); @@ -1189,6 +1207,7 @@ class ConfigurableInitStepExecutorTest { properties.setStandardCpconfigPath(cpconfig.toString()); properties.setStandardRabbitSetupScriptPath(setupScript.toString()); properties.setStandardRabbitQueueScriptPath(queueScript.toString()); + properties.setRabbitmqCtlCommand(rabbitmqctl.toString()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); @@ -1214,30 +1233,28 @@ class ConfigurableInitStepExecutorTest { InitStepExecutionResult result = executor.execute(task, step); Assertions.assertTrue(result.isSuccess()); + Assertions.assertEquals("MQ用户已应用", result.getMessage()); // 源脚本保持不变(不污染基线介质) String sourceScriptContent = Files.readString(setupScript); Assertions.assertTrue(sourceScriptContent.contains("rabbitmqctl add_user rConn rConn")); Assertions.assertTrue(sourceScriptContent.contains("rabbitmqctl set_permissions -p /RQ rConn '.*' '.*' '.*'")); - // staging 脚本被按本次任务参数渲染 - Path stagedSetupScript = tempRoot.resolve("staging") - .resolve("TASK-RABBIT-SETUP-1") - .resolve("scripts") - .resolve("rabq") - .resolve("setuprabq.sh"); - String stagedScriptContent = Files.readString(stagedSetupScript); - Assertions.assertTrue(stagedScriptContent.contains("rabbitmqctl add_user 'bank_user' 'bank_pass' || rabbitmqctl change_password 'bank_user' 'bank_pass'")); - Assertions.assertTrue(stagedScriptContent.contains("rabbitmqctl set_permissions -p /RQ 'bank_user' '.*' '.*' '.*'")); + String callsContent = Files.readString(calls); + Assertions.assertTrue(callsContent.contains("status")); + Assertions.assertTrue(callsContent.contains("add_vhost /RQ")); + Assertions.assertTrue(callsContent.contains("add_user bank_user bank_pass")); + Assertions.assertTrue(callsContent.contains("set_permissions -p /RQ bank_user .* .* .*")); } @Test - void shouldExecuteStandardRabbitSetupScriptForIndirectRabbitmqTlq() throws IOException { + void shouldApplyStandardRabbitUserForIndirectRabbitmqTlq() throws IOException { Path tempRoot = Files.createTempDirectory("init-executor-rabbit-setup-indirect-tlq-test"); Path setupScript = tempRoot.resolve("setuprabq.sh"); Path queueScript = tempRoot.resolve("addrabq_R_01.sh"); Path cpconfig = tempRoot.resolve("cpconfig.cfg"); Path calls = tempRoot.resolve("calls.log"); + Path rabbitmqctl = tempRoot.resolve("rabbitmqctl"); Files.writeString(cpconfig, "MY_CIPSID_OR_BIC=AAAABBBBXXX\n"); Files.writeString( setupScript, @@ -1249,6 +1266,13 @@ class ConfigurableInitStepExecutorTest { + "rabbitmqctl set_permissions -p /RQ rConn '.*' '.*' '.*'\n" ); Files.writeString(queueScript, "#!/bin/bash\necho queue > /dev/null\n"); + Files.writeString( + rabbitmqctl, + "#!/bin/bash\n" + + "echo \"$*\" >> \"" + calls + "\"\n" + + "exit 0\n" + ); + Assertions.assertTrue(rabbitmqctl.toFile().setExecutable(true)); InitExecutorProperties properties = new InitExecutorProperties(); properties.setMode(InitExecutorProperties.Mode.LOCAL); @@ -1257,6 +1281,7 @@ class ConfigurableInitStepExecutorTest { properties.setStandardCpconfigPath(cpconfig.toString()); properties.setStandardRabbitSetupScriptPath(setupScript.toString()); properties.setStandardRabbitQueueScriptPath(queueScript.toString()); + properties.setRabbitmqCtlCommand(rabbitmqctl.toString()); ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); InitTaskEntity task = new InitTaskEntity(); @@ -1283,11 +1308,10 @@ class ConfigurableInitStepExecutorTest { InitStepExecutionResult result = executor.execute(task, step); Assertions.assertTrue(result.isSuccess()); Assertions.assertFalse(result.getMessage().contains("已跳过")); - Assertions.assertTrue(Files.exists(tempRoot.resolve("staging") - .resolve("TASK-RABBIT-SETUP-INDIRECT-TLQ-1") - .resolve("scripts") - .resolve("rabq") - .resolve("setuprabq.sh"))); + String callsContent = Files.readString(calls); + Assertions.assertTrue(callsContent.contains("add_vhost /RQ")); + Assertions.assertTrue(callsContent.contains("add_user bank_user bank_pass")); + Assertions.assertTrue(callsContent.contains("set_permissions -p /RQ bank_user .* .* .*")); } @Test diff --git a/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java b/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java index 6b443a4..e5a479a 100644 --- a/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java @@ -573,7 +573,7 @@ class InitServiceTest { Assertions.assertTrue(steps.stream().allMatch(s -> s.getCommandLine() != null && !s.getCommandLine().isBlank())); Assertions.assertTrue( steps.stream().anyMatch(s -> "SET_RABBITMQ".equals(s.getStepCode()) - && s.getCommandLine().contains("/home/cemp4i/cpackage/toolsh/setfraq.sh")) + && s.getCommandLine().equals("sudo -n /usr/local/sbin/tms-run-standard-vendor-step setfraq")) ); Assertions.assertEquals("INIT", detail.getTaskType()); } diff --git a/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java b/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java index 500c6cb..9b26425 100644 --- a/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java @@ -8,8 +8,10 @@ import com.cisd.tms.integration.crypto.pcie.jna.SdfeIkComponent; import com.cisd.tms.integration.crypto.pcie.model.*; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.modules.auth.service.AuthSecurityResetService; +import com.cisd.tms.modules.cert.repository.KeyEntityRepository; import com.cisd.tms.modules.mk.config.MasterKeyInitProperties; import com.cisd.tms.modules.mk.dto.*; +import com.cisd.tms.modules.mk.entity.MasterKeyActivateEntity; import com.cisd.tms.modules.mk.repository.MasterKeyActivateRepository; import com.cisd.tms.modules.mk.service.impl.LmkServiceImpl; import com.fasterxml.jackson.databind.JsonNode; @@ -22,9 +24,13 @@ import org.mockito.Mockito; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.ArrayDeque; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.Executor; class LmkServiceTest { @@ -33,6 +39,7 @@ class LmkServiceTest { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + allowMasterKeyInit(masterKeyActivateRepository); Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())).thenReturn(new byte[] {0x01, 0x23, 0x45, 0x67}); LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository); @@ -54,6 +61,7 @@ class LmkServiceTest { MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class); AuthSecurityResetService authSecurityResetService = Mockito.mock(AuthSecurityResetService.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + allowMasterKeyInit(masterKeyActivateRepository); Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())).thenReturn(new byte[] {0x01, 0x23}); LmkService service = new LmkServiceImpl(pcieCryptoService, properties, authSecurityResetService, masterKeyActivateRepository); @@ -70,6 +78,7 @@ class LmkServiceTest { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + allowMasterKeyInit(masterKeyActivateRepository); Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())) .thenThrow(new IllegalStateException("主密钥初始化失败")); @@ -80,6 +89,39 @@ class LmkServiceTest { Assertions.assertEquals("主密钥初始化失败", exception.getMessage()); } + @Test + void shouldRunMasterKeyInitializationMaintenanceAsynchronously() { + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class); + AuthSecurityResetService authSecurityResetService = Mockito.mock(AuthSecurityResetService.class); + KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); + CapturingExecutor postInitExecutor = new CapturingExecutor(); + MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + allowMasterKeyInit(masterKeyActivateRepository); + Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())).thenReturn(new byte[] {0x01, 0x23}); + LmkService service = new LmkServiceImpl( + pcieCryptoService, + properties, + authSecurityResetService, + masterKeyActivateRepository, + keyEntityRepository, + postInitExecutor + ); + + service.initMasterKey(); + + Mockito.verify(keyEntityRepository, Mockito.never()).deleteAll(); + Mockito.verify(authSecurityResetService, Mockito.never()).resetAfterMasterKeyInitialized(); + Mockito.verify(masterKeyActivateRepository, Mockito.never()).update(Mockito.any()); + + postInitExecutor.runNext(); + + InOrder inOrder = Mockito.inOrder(keyEntityRepository, authSecurityResetService, masterKeyActivateRepository); + inOrder.verify(keyEntityRepository).deleteAll(); + inOrder.verify(authSecurityResetService).resetAfterMasterKeyInitialized(); + inOrder.verify(masterKeyActivateRepository).update(Mockito.any()); + } + @Test void shouldRejectInitMasterKeyWhenMasterKeyAlreadyExists() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); @@ -100,16 +142,18 @@ class LmkServiceTest { void shouldDestroyMasterKeyAfterDeletingUserKey() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class); + KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); - LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository); + LmkService service = new LmkServiceImpl(pcieCryptoService, properties, () -> { }, masterKeyActivateRepository, keyEntityRepository); service.destroyMasterKey(); - InOrder inOrder = Mockito.inOrder(pcieCryptoService); + InOrder inOrder = Mockito.inOrder(pcieCryptoService, keyEntityRepository); inOrder.verify(pcieCryptoService).deleteUserKey(1); inOrder.verify(pcieCryptoService).destroyLmk(); inOrder.verify(pcieCryptoService).destroyIk(2); inOrder.verify(pcieCryptoService).destroyIk(1); + inOrder.verify(keyEntityRepository).deleteAll(); } @Test @@ -369,6 +413,24 @@ class LmkServiceTest { Mockito.verify(pcieCryptoService, Mockito.never()).recoverUserKey(Mockito.any()); } + @Test + void shouldClearKeyEntityRegistryAfterMasterKeyRecoverySucceeds() { + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class); + KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); + MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + MasterKeyRecoveryResult recoveryResult = new MasterKeyRecoveryResult(); + recoveryResult.setLmkSeedMac(hex("1112131415161718")); + Mockito.when(pcieCryptoService.recoverMasterKeyMaterial(Mockito.any())).thenReturn(recoveryResult); + LmkService service = new LmkServiceImpl(pcieCryptoService, properties, () -> { }, masterKeyActivateRepository, keyEntityRepository); + + service.recoverKeyPackets(List.of(recoverPacketFixture(1), recoverPacketFixture(2))); + + InOrder inOrder = Mockito.inOrder(pcieCryptoService, keyEntityRepository); + inOrder.verify(pcieCryptoService).recoverMasterKeyMaterial(Mockito.any()); + inOrder.verify(keyEntityRepository).deleteAll(); + } + @Test void shouldHashRawDataBeforeInternalSign() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); @@ -442,6 +504,29 @@ class LmkServiceTest { return properties; } + private static void allowMasterKeyInit(MasterKeyActivateRepository masterKeyActivateRepository) { + MasterKeyActivateEntity entity = new MasterKeyActivateEntity(); + entity.setEverInitialized(true); + entity.setActivationStatus(false); + Mockito.when(masterKeyActivateRepository.find()).thenReturn(Optional.of(entity)); + } + + private static final class CapturingExecutor implements Executor { + + private final Queue tasks = new ArrayDeque<>(); + + @Override + public void execute(Runnable command) { + tasks.add(command); + } + + private void runNext() { + Runnable task = tasks.poll(); + Assertions.assertNotNull(task); + task.run(); + } + } + private static byte[] sequentialBytes(int length, int start) { byte[] result = new byte[length]; for (int i = 0; i < length; i++) {