This commit is contained in:
waner 2026-05-18 15:10:40 +08:00
parent 60002f6c31
commit 5891a510c7
22 changed files with 737 additions and 167 deletions

View File

@ -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` 流程和故障排查,请查看上面的完整部署手册。

View File

@ -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:

View File

@ -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"

View File

@ -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}"
}

View File

@ -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}"

View File

@ -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}"

View File

@ -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<RecoverUserKeyRequest> 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<RecoverUserKeyRequest> 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<RecoverUserKeyRequest> copyRecoverUserKeyRequests(List<RecoverUserKeyRequest> 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);
}
}
}

View File

@ -9,6 +9,7 @@ import java.util.Optional;
public interface KeyEntityRepository {
KeyEntity save(KeyEntity entity);
void deleteById(Long id);
void deleteAll();
Optional<KeyEntity> findById(Long id);
Optional<KeyEntity> findByKeyIdx(Integer keyIdx);
List<KeyEntity> findAll();

View File

@ -38,6 +38,12 @@ public class KeyEntityRepositoryImpl implements KeyEntityRepository {
mapper.deleteById(id);
}
@Override
public void deleteAll() {
mapper.delete(new LambdaQueryWrapper<KeyEntity>()
.isNotNull(KeyEntity::getId));
}
@Override
public Optional<KeyEntity> findById(Long id) {
return Optional.ofNullable(mapper.selectById(id));

View File

@ -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<String, Map<String, Map<String, String>>> 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) {

View File

@ -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<String> whitelistPrefixes = List.of(
"bash /opt/CAE_Install_",
"systemctl ",
"echo "
);
private List<String> whitelistPrefixes = List.of();
public Mode getMode() {
return mode;
@ -166,22 +166,13 @@ public class InitExecutorProperties {
public List<String> 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<String> merged = new java.util.ArrayList<>();
merged.add("bash " + getStandardHomeDir() + "/cpackage/");
merged.addAll(whitelistPrefixes);
return merged;
return whitelistPrefixes;
}
public void setWhitelistPrefixes(List<String> 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;
}

View File

@ -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<String> 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<String> command, boolean ignoreFailure) throws IOException, InterruptedException {
return runCommand(logFile, command, ignoreFailure, null, buildRabbitmqEnvironment());
}
private int runCommand(Path logFile, List<String> 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<String, String> buildRabbitmqEnvironment() {
Map<String, String> 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<String, String> buildStandardDbApplyEnvironment(Path loadDir, PlanContext context) {
Map<String, String> 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<String> 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<String> 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);
}

View File

@ -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<Integer, String> componentMap) {
}

View File

@ -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;

View File

@ -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

View File

@ -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,

View File

@ -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",

View File

@ -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<PcieNativeLibrary> restoreLibRef = new AtomicReference<>();
AtomicReference<PcieNativeLibrary> userKeyLibRef = new AtomicReference<>();
CountDownLatch recoverUserKeyStarted = new CountDownLatch(1);
Mockito.when(sessionTemplate.withSession(Mockito.eq("SDFE_RecoverMasterKeyMaterial"), Mockito.any()))
.thenAnswer(invocation -> {
@SuppressWarnings("unchecked")
PcieSessionTemplate.SessionCallback<DeviceStatusResult> callback =
(PcieSessionTemplate.SessionCallback<DeviceStatusResult>) invocation.getArgument(1);
PcieSessionTemplate.SessionCallback<MasterKeyRecoveryResult> callback =
(PcieSessionTemplate.SessionCallback<MasterKeyRecoveryResult>) 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<byte[]> callback =
(PcieSessionTemplate.SessionCallback<byte[]>) invocation.getArgument(1);
PcieSessionTemplate.SessionCallback<Void> callback =
(PcieSessionTemplate.SessionCallback<Void>) 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

View File

@ -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");

View File

@ -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

View File

@ -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());
}

View File

@ -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<Runnable> 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++) {