Compare commits
2 Commits
b66b425512
...
feature/14
| Author | SHA1 | Date | |
|---|---|---|---|
| 850ff218ce | |||
| 9f4f4cf069 |
@@ -37,12 +37,27 @@ RETAIN_RUNS=100
|
|||||||
# TAL/TA input mode:
|
# TAL/TA input mode:
|
||||||
# file-with-ta: use packaged fixture TAL + TA only.
|
# file-with-ta: use packaged fixture TAL + TA only.
|
||||||
# file-live-ta: use packaged fixture TAL; snapshot waits for live TA refresh, delta refreshes TA in background.
|
# file-live-ta: use packaged fixture TAL; snapshot waits for live TA refresh, delta refreshes TA in background.
|
||||||
|
# custom-file-with-ta: use one custom TAL + TA from the read-only custom fixture mount.
|
||||||
# url: pass TAL URL to child process.
|
# url: pass TAL URL to child process.
|
||||||
TAL_INPUT_MODE=file-live-ta
|
TAL_INPUT_MODE=file-live-ta
|
||||||
LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1
|
LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1
|
||||||
LIVE_TA_REFRESH_CONNECT_TIMEOUT_SECS=15
|
LIVE_TA_REFRESH_CONNECT_TIMEOUT_SECS=15
|
||||||
LIVE_TA_REFRESH_MAX_TIME_SECS=120
|
LIVE_TA_REFRESH_MAX_TIME_SECS=120
|
||||||
|
|
||||||
|
# Custom TAL patch mode. Keep TAL_INPUT_MODE=file-live-ta and the five-RIR RIRS
|
||||||
|
# list above for the original package behavior. For a private fixture, set:
|
||||||
|
# RIRS=custom
|
||||||
|
# TAL_INPUT_MODE=custom-file-with-ta
|
||||||
|
# The paths below are container paths; CUSTOM_FIXTURE_HOST_DIR is a host path
|
||||||
|
# resolved relative to the compose directory when left at its default.
|
||||||
|
CUSTOM_FIXTURE_HOST_DIR=../custom-fixtures
|
||||||
|
CUSTOM_TAL_PATH=/opt/ours-rp/custom-fixtures/tal/custom.tal
|
||||||
|
CUSTOM_TA_PATH=/opt/ours-rp/custom-fixtures/ta/custom-ta.cer
|
||||||
|
CUSTOM_TAL_URI=https://host.docker.internal:18443/tal/custom.tal
|
||||||
|
# Space-separated container paths; paths must not contain spaces. Set this in
|
||||||
|
# custom mode when the RRDP server uses a private/self-signed CA.
|
||||||
|
HTTP_ROOT_CERT_PATHS=
|
||||||
|
|
||||||
# Sync and runtime behavior.
|
# Sync and runtime behavior.
|
||||||
RSYNC_SCOPE=module-root
|
RSYNC_SCOPE=module-root
|
||||||
DISABLE_COMPETING_RPS=0
|
DISABLE_COMPETING_RPS=0
|
||||||
|
|||||||
@@ -11,8 +11,15 @@ services:
|
|||||||
RUN_ROOT: /var/lib/ours-rp
|
RUN_ROOT: /var/lib/ours-rp
|
||||||
BIN_DIR: /opt/ours-rp/bin
|
BIN_DIR: /opt/ours-rp/bin
|
||||||
FIXTURE_DIR: /opt/ours-rp/fixtures
|
FIXTURE_DIR: /opt/ours-rp/fixtures
|
||||||
|
CUSTOM_FIXTURE_DIR: /opt/ours-rp/custom-fixtures
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
volumes:
|
volumes:
|
||||||
- ../.env:/opt/ours-rp/.env:ro
|
- ../.env:/opt/ours-rp/.env:ro
|
||||||
|
# Overlay the patched runner while keeping the original runtime image
|
||||||
|
# and its exact rpki binaries unchanged.
|
||||||
|
- ../scripts/soak/run_soak.sh:/opt/ours-rp/run_soak.sh:ro
|
||||||
|
- ${CUSTOM_FIXTURE_HOST_DIR:-../custom-fixtures}:/opt/ours-rp/custom-fixtures:ro
|
||||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/state:/var/lib/ours-rp/state
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/state:/var/lib/ours-rp/state
|
||||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/runs:/var/lib/ours-rp/runs
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/runs:/var/lib/ours-rp/runs
|
||||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/logs:/var/lib/ours-rp/logs
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/logs:/var/lib/ours-rp/logs
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_cir_trust_anchors",
|
"expr": "max(ours_rp_cir_trust_anchors and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "RIRs",
|
"legendFormat": "RIRs",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_run_duration_seconds",
|
"expr": "max(ours_rp_run_duration_seconds and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "wall",
|
"legendFormat": "wall",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_run_max_rss_bytes",
|
"expr": "max(ours_rp_run_max_rss_bytes and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "rss",
|
"legendFormat": "rss",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_publication_points",
|
"expr": "max(ours_rp_publication_points and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "publication points",
|
"legendFormat": "publication points",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_run_sequence",
|
"expr": "max(ours_rp_run_sequence)",
|
||||||
"legendFormat": "seq",
|
"legendFormat": "seq",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
|
"expr": "max((100 * sum by (exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (exported_instance) (ours_rp_publication_points)) and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "PP cache hit ratio",
|
"legendFormat": "PP cache hit ratio",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_vrps{kind=\"total\"}",
|
"expr": "max(ours_rp_vrps{kind=\"total\"} and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "VRPs raw",
|
"legendFormat": "VRPs raw",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
@@ -377,7 +377,7 @@
|
|||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.3.1",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ours_rp_vaps",
|
"expr": "max(ours_rp_vaps and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||||
"legendFormat": "VAPs",
|
"legendFormat": "VAPs",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
|
|||||||
14
deploy/docker-installer/custom-fixtures/README.md
Normal file
14
deploy/docker-installer/custom-fixtures/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Custom TAL fixture mount
|
||||||
|
|
||||||
|
This directory is intentionally shipped as a mount point, not as a repository
|
||||||
|
of private keys. Run the patch tool `tools/generate_custom_fixture.py` to create
|
||||||
|
the test-only TAL, TA, RPKI objects, HTTPS CA/server certificates, RRDP files,
|
||||||
|
and rsync module data below it.
|
||||||
|
|
||||||
|
The generated fixture contains `baseline-v1`, `sync-hash-mismatch` and
|
||||||
|
`baseline-v2` for snapshot / hash / delta tests, plus
|
||||||
|
`validation-expired`, `validation-max-length`,
|
||||||
|
`validation-max-length-invalid`, `validation-roa-prefix-outside`,
|
||||||
|
`validation-out-of-resource`, `validation-ca-over-resource`, and
|
||||||
|
`validation-nonstandard` for validation cases. Generated keys are test-only
|
||||||
|
and must not be used in a production RPKI hierarchy.
|
||||||
30
deploy/docker-installer/custom-tal-patch/README.zh-CN.md
Normal file
30
deploy/docker-installer/custom-tal-patch/README.zh-CN.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 自建 TAL/TA 补丁
|
||||||
|
|
||||||
|
本目录描述并生成一个叠加到客户 Arm64 Ours RP 组件包上的测试补丁。补丁基线是源码 commit `9f4f4cf069e9fa8f6f97ed0f1065adac38abe82f`,不会替换客户包中的 runtime image 或 `bin/rpki`、`bin/rpki_daemon` 二进制。
|
||||||
|
|
||||||
|
补丁做三件事:
|
||||||
|
|
||||||
|
1. 在 runner 中增加 `RIRS=custom`、`TAL_INPUT_MODE=custom-file-with-ta`、自定义 TAL/TA 路径和 `HTTP_ROOT_CERT_PATHS`;
|
||||||
|
2. 通过 Compose 只读挂载 patched runner、`custom-fixtures/` 和 `host.docker.internal`,连接固定 HTTPS RRDP 与 rsync 服务;
|
||||||
|
3. 提供固定证书链、RPKI 对象生成器和服务启动脚本,不依赖 Barry、Rapport 或公网 RIR。
|
||||||
|
|
||||||
|
先停止 Ours RP,再执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./apply_custom_tal_patch.sh \
|
||||||
|
--component-root /opt/ours-rp-rtr-stack/components/ours-rp \
|
||||||
|
--enable-custom
|
||||||
|
```
|
||||||
|
|
||||||
|
`--enable-custom` 会修改组件 `.env` 的 custom 模式入口;不加该选项只安装 overlay,不改变当前 `.env`。回滚:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./rollback_custom_tal_patch.sh \
|
||||||
|
--component-root /opt/ours-rp-rtr-stack/components/ours-rp
|
||||||
|
```
|
||||||
|
|
||||||
|
补丁应用脚本会校验 `PACKAGE-MANIFEST.env` 的源码 commit 和 `arm64` 架构,并留下 `CUSTOM-TAL-PATCH-MANIFEST.env` 与 `.custom-tal-patch-state/` 备份。升级组件后需要重新检查 patch marker;不要把该 overlay 误当成客户原始安装包。
|
||||||
|
|
||||||
|
生成器默认把容器访问地址写成 `host.docker.internal`,固定服务端口为 HTTPS RRDP `18443`、rsync `1873`,rsync module 为 `custom`。可用 `--host localhost` 生成给宿主机直接运行 `bin/rpki` 的本地变体。
|
||||||
|
|
||||||
|
用例包括:`baseline-v1`、`sync-hash-mismatch`、`baseline-v2`、`validation-expired`、`validation-max-length`、`validation-max-length-invalid`、`validation-roa-prefix-outside`、`validation-out-of-resource`、`validation-ca-over-resource`、`validation-nonstandard`。其中 `validation-ca-over-resource` 在 `RESOURCE_VALIDATION_MODE=rfc6487` 下验证严格拒绝;`validation-update-03` 是原包默认的兼容处理模式。
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PATCH_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
PAYLOAD_ROOT="$SCRIPT_DIR/payload"
|
||||||
|
STATE_DIR_NAME=".custom-tal-patch-state"
|
||||||
|
EXPECTED_SOURCE_COMMIT="9f4f4cf069e9fa8f6f97ed0f1065adac38abe82f"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
./apply_custom_tal_patch.sh --component-root <installed-ours-rp-root>
|
||||||
|
./apply_custom_tal_patch.sh --component-root <root> --enable-custom
|
||||||
|
|
||||||
|
The default operation installs the overlay but does not change the existing
|
||||||
|
.env. --enable-custom writes the custom TAL/TA/root-cert example values into
|
||||||
|
the component .env so the next run uses the fixed local fixture.
|
||||||
|
Stop the ours RP service before applying or rolling back the overlay.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "error: $*" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPONENT_ROOT=""
|
||||||
|
ENABLE_CUSTOM=0
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--component-root)
|
||||||
|
COMPONENT_ROOT="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--enable-custom)
|
||||||
|
ENABLE_CUSTOM=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$COMPONENT_ROOT" ]] || die "--component-root is required"
|
||||||
|
COMPONENT_ROOT="$(cd "$COMPONENT_ROOT" 2>/dev/null && pwd)" \
|
||||||
|
|| die "component root does not exist: $COMPONENT_ROOT"
|
||||||
|
[[ -f "$COMPONENT_ROOT/PACKAGE-MANIFEST.env" ]] \
|
||||||
|
|| die "missing component PACKAGE-MANIFEST.env: $COMPONENT_ROOT"
|
||||||
|
[[ -d "$PAYLOAD_ROOT" ]] || die "missing patch payload: $PAYLOAD_ROOT"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$COMPONENT_ROOT/PACKAGE-MANIFEST.env"
|
||||||
|
[[ "${source_commit:-}" == "$EXPECTED_SOURCE_COMMIT" ]] \
|
||||||
|
|| die "base source commit mismatch: ${source_commit:-missing} != $EXPECTED_SOURCE_COMMIT"
|
||||||
|
[[ "${package_arch:-${PACKAGE_ARCH:-}}" == "arm64" ]] \
|
||||||
|
|| die "this patch is for the customer Arm64 component, got ${package_arch:-${PACKAGE_ARCH:-missing}}"
|
||||||
|
|
||||||
|
STATE_ROOT="$COMPONENT_ROOT/$STATE_DIR_NAME"
|
||||||
|
BACKUP_ROOT="$STATE_ROOT/original"
|
||||||
|
marker="$COMPONENT_ROOT/CUSTOM-TAL-PATCH-MANIFEST.env"
|
||||||
|
|
||||||
|
set_env_value() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local tmp_path="${env_path}.custom-tal-patch.tmp"
|
||||||
|
[[ -f "$env_path" ]] || die "missing component .env: $env_path"
|
||||||
|
awk -v key="$key" -v value="$value" '
|
||||||
|
BEGIN { found = 0 }
|
||||||
|
$0 ~ "^" key "=" { print key "=" value; found = 1; next }
|
||||||
|
{ print }
|
||||||
|
END { if (!found) print key "=" value }
|
||||||
|
' "$env_path" > "$tmp_path"
|
||||||
|
mv "$tmp_path" "$env_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_payload_file() {
|
||||||
|
local source_path="$1"
|
||||||
|
local target_path="$2"
|
||||||
|
mkdir -p "$(dirname "$target_path")"
|
||||||
|
cp "$source_path" "$target_path"
|
||||||
|
chmod --reference="$source_path" "$target_path" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
append_custom_env_example() {
|
||||||
|
local target_path="$1"
|
||||||
|
if ! grep -Eq '^CUSTOM_TAL_URI=' "$target_path"; then
|
||||||
|
{
|
||||||
|
printf '\n# #142 custom TAL patch settings; original package values above are preserved.\n'
|
||||||
|
cat "$SCRIPT_DIR/custom-tal.env.example"
|
||||||
|
} >> "$target_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -e "$STATE_ROOT" ]]; then
|
||||||
|
die "patch is already applied or an incomplete state exists: $STATE_ROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_ROOT"
|
||||||
|
for relative_path in compose/docker-compose.yml .env.example; do
|
||||||
|
[[ -f "$COMPONENT_ROOT/$relative_path" ]] \
|
||||||
|
|| die "missing base file: $COMPONENT_ROOT/$relative_path"
|
||||||
|
mkdir -p "$BACKUP_ROOT/$(dirname "$relative_path")"
|
||||||
|
cp -a "$COMPONENT_ROOT/$relative_path" "$BACKUP_ROOT/$relative_path"
|
||||||
|
done
|
||||||
|
|
||||||
|
runner_path="$COMPONENT_ROOT/scripts/soak/run_soak.sh"
|
||||||
|
if [[ -e "$runner_path" ]]; then
|
||||||
|
mkdir -p "$BACKUP_ROOT/scripts/soak"
|
||||||
|
cp -a "$runner_path" "$BACKUP_ROOT/scripts/soak/run_soak.sh"
|
||||||
|
printf 'runner_was_present=1\n' > "$STATE_ROOT/state.env"
|
||||||
|
else
|
||||||
|
printf 'runner_was_present=0\n' > "$STATE_ROOT/state.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$COMPONENT_ROOT/custom-fixtures" ]]; then
|
||||||
|
cp -a "$COMPONENT_ROOT/custom-fixtures" "$BACKUP_ROOT/custom-fixtures"
|
||||||
|
printf 'custom_fixtures_were_present=1\n' >> "$STATE_ROOT/state.env"
|
||||||
|
else
|
||||||
|
printf 'custom_fixtures_were_present=0\n' >> "$STATE_ROOT/state.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$COMPONENT_ROOT/custom-tal-patch" ]]; then
|
||||||
|
cp -a "$COMPONENT_ROOT/custom-tal-patch" "$BACKUP_ROOT/custom-tal-patch"
|
||||||
|
printf 'patch_tools_were_present=1\n' >> "$STATE_ROOT/state.env"
|
||||||
|
else
|
||||||
|
printf 'patch_tools_were_present=0\n' >> "$STATE_ROOT/state.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$COMPONENT_ROOT/custom-tal.env.example" ]]; then
|
||||||
|
cp -a "$COMPONENT_ROOT/custom-tal.env.example" "$BACKUP_ROOT/custom-tal.env.example"
|
||||||
|
printf 'custom_env_example_was_present=1\n' >> "$STATE_ROOT/state.env"
|
||||||
|
else
|
||||||
|
printf 'custom_env_example_was_present=0\n' >> "$STATE_ROOT/state.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$ENABLE_CUSTOM" == "1" ]]; then
|
||||||
|
[[ -f "$COMPONENT_ROOT/.env" ]] || die "missing component .env: $COMPONENT_ROOT/.env"
|
||||||
|
cp -a "$COMPONENT_ROOT/.env" "$BACKUP_ROOT/.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
copy_payload_file "$PAYLOAD_ROOT/compose/docker-compose.yml" "$COMPONENT_ROOT/compose/docker-compose.yml"
|
||||||
|
append_custom_env_example "$COMPONENT_ROOT/.env.example"
|
||||||
|
copy_payload_file "$PAYLOAD_ROOT/scripts/soak/run_soak.sh" "$runner_path"
|
||||||
|
mkdir -p "$COMPONENT_ROOT/custom-fixtures/tal" \
|
||||||
|
"$COMPONENT_ROOT/custom-fixtures/ta" \
|
||||||
|
"$COMPONENT_ROOT/custom-fixtures/certs" \
|
||||||
|
"$COMPONENT_ROOT/custom-fixtures/services"
|
||||||
|
if [[ -d "$PAYLOAD_ROOT/custom-fixtures" ]]; then
|
||||||
|
cp -a "$PAYLOAD_ROOT/custom-fixtures/." "$COMPONENT_ROOT/custom-fixtures/"
|
||||||
|
fi
|
||||||
|
mkdir -p "$COMPONENT_ROOT/custom-tal-patch/tools"
|
||||||
|
cp -a "$PAYLOAD_ROOT/tools/." "$COMPONENT_ROOT/custom-tal-patch/tools/"
|
||||||
|
copy_payload_file "$SCRIPT_DIR/custom-tal.env.example" \
|
||||||
|
"$COMPONENT_ROOT/custom-tal.env.example"
|
||||||
|
|
||||||
|
if [[ "$ENABLE_CUSTOM" == "1" ]]; then
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" RIRS custom
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" TAL_INPUT_MODE custom-file-with-ta
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_FIXTURE_HOST_DIR ../custom-fixtures
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_TAL_PATH /opt/ours-rp/custom-fixtures/tal/custom.tal
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_TA_PATH /opt/ours-rp/custom-fixtures/ta/custom-ta.cer
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_TAL_URI https://host.docker.internal:18443/tal/custom.tal
|
||||||
|
set_env_value "$COMPONENT_ROOT/.env" HTTP_ROOT_CERT_PATHS /opt/ours-rp/custom-fixtures/certs/rrdp-ca.pem
|
||||||
|
printf 'env_was_enabled=1\n' >> "$STATE_ROOT/state.env"
|
||||||
|
else
|
||||||
|
printf 'env_was_enabled=0\n' >> "$STATE_ROOT/state.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$SCRIPT_DIR/PATCH-MANIFEST.env" ]] \
|
||||||
|
|| die "missing PATCH-MANIFEST.env"
|
||||||
|
cp "$SCRIPT_DIR/PATCH-MANIFEST.env" "$marker"
|
||||||
|
printf 'patch_root=%s\n' "$PATCH_ROOT" >> "$STATE_ROOT/state.env"
|
||||||
|
printf 'applied_at_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$STATE_ROOT/state.env"
|
||||||
|
|
||||||
|
echo "custom TAL patch applied to $COMPONENT_ROOT"
|
||||||
|
if [[ "$ENABLE_CUSTOM" == "1" ]]; then
|
||||||
|
echo "custom mode enabled in $COMPONENT_ROOT/.env"
|
||||||
|
else
|
||||||
|
echo "custom mode remains disabled; review custom-tal.env.example before enabling it"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Copy these values into the component .env after applying the patch.
|
||||||
|
# The patch's --enable-custom option writes the same values automatically.
|
||||||
|
RIRS=custom
|
||||||
|
TAL_INPUT_MODE=custom-file-with-ta
|
||||||
|
CUSTOM_FIXTURE_HOST_DIR=../custom-fixtures
|
||||||
|
CUSTOM_TAL_PATH=/opt/ours-rp/custom-fixtures/tal/custom.tal
|
||||||
|
CUSTOM_TA_PATH=/opt/ours-rp/custom-fixtures/ta/custom-ta.cer
|
||||||
|
CUSTOM_TAL_URI=https://host.docker.internal:18443/tal/custom.tal
|
||||||
|
HTTP_ROOT_CERT_PATHS=/opt/ours-rp/custom-fixtures/certs/rrdp-ca.pem
|
||||||
|
|
||||||
|
# Recommended for a finite acceptance run; keep the customer default for a
|
||||||
|
# long-running test only after the fixture service is supervised.
|
||||||
|
MAX_RUNS=1
|
||||||
|
INTERVAL_SECS=0
|
||||||
|
SOAK_RESTART_POLICY=no
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
./rollback_custom_tal_patch.sh --component-root <installed-ours-rp-root>
|
||||||
|
|
||||||
|
Stop the ours RP service before rolling back the overlay.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "error: $*" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPONENT_ROOT=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--component-root)
|
||||||
|
COMPONENT_ROOT="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$COMPONENT_ROOT" ]] || die "--component-root is required"
|
||||||
|
COMPONENT_ROOT="$(cd "$COMPONENT_ROOT" 2>/dev/null && pwd)" \
|
||||||
|
|| die "component root does not exist: $COMPONENT_ROOT"
|
||||||
|
STATE_ROOT="$COMPONENT_ROOT/.custom-tal-patch-state"
|
||||||
|
BACKUP_ROOT="$STATE_ROOT/original"
|
||||||
|
[[ -d "$STATE_ROOT" ]] || die "patch state not found: $STATE_ROOT"
|
||||||
|
[[ -f "$BACKUP_ROOT/compose/docker-compose.yml" ]] \
|
||||||
|
|| die "patch backup is incomplete: $BACKUP_ROOT"
|
||||||
|
|
||||||
|
cp -a "$BACKUP_ROOT/compose/docker-compose.yml" "$COMPONENT_ROOT/compose/docker-compose.yml"
|
||||||
|
cp -a "$BACKUP_ROOT/.env.example" "$COMPONENT_ROOT/.env.example"
|
||||||
|
|
||||||
|
if [[ -f "$BACKUP_ROOT/scripts/soak/run_soak.sh" ]]; then
|
||||||
|
mkdir -p "$COMPONENT_ROOT/scripts/soak"
|
||||||
|
cp -a "$BACKUP_ROOT/scripts/soak/run_soak.sh" "$COMPONENT_ROOT/scripts/soak/run_soak.sh"
|
||||||
|
else
|
||||||
|
rm -f "$COMPONENT_ROOT/scripts/soak/run_soak.sh"
|
||||||
|
rmdir "$COMPONENT_ROOT/scripts/soak" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$BACKUP_ROOT/custom-fixtures" ]]; then
|
||||||
|
rm -rf "$COMPONENT_ROOT/custom-fixtures"
|
||||||
|
cp -a "$BACKUP_ROOT/custom-fixtures" "$COMPONENT_ROOT/custom-fixtures"
|
||||||
|
else
|
||||||
|
rm -rf "$COMPONENT_ROOT/custom-fixtures"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$BACKUP_ROOT/custom-tal-patch" ]]; then
|
||||||
|
rm -rf "$COMPONENT_ROOT/custom-tal-patch"
|
||||||
|
cp -a "$BACKUP_ROOT/custom-tal-patch" "$COMPONENT_ROOT/custom-tal-patch"
|
||||||
|
else
|
||||||
|
rm -rf "$COMPONENT_ROOT/custom-tal-patch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$BACKUP_ROOT/custom-tal.env.example" ]]; then
|
||||||
|
cp -a "$BACKUP_ROOT/custom-tal.env.example" "$COMPONENT_ROOT/custom-tal.env.example"
|
||||||
|
else
|
||||||
|
rm -f "$COMPONENT_ROOT/custom-tal.env.example"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$BACKUP_ROOT/.env" ]]; then
|
||||||
|
cp -a "$BACKUP_ROOT/.env" "$COMPONENT_ROOT/.env"
|
||||||
|
fi
|
||||||
|
rm -f "$COMPONENT_ROOT/CUSTOM-TAL-PATCH-MANIFEST.env"
|
||||||
|
rm -rf "$STATE_ROOT"
|
||||||
|
echo "custom TAL patch rolled back from $COMPONENT_ROOT"
|
||||||
@@ -0,0 +1,636 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate a small self-contained RPKI repository for installer tests.
|
||||||
|
|
||||||
|
The generator uses Python cryptography and the OpenSSL CLI for CMS signing.
|
||||||
|
It does not invoke Barry, Rapport, or a public RIR service.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import ipaddress
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
|
||||||
|
from cryptography.x509 import AccessDescription, AuthorityInformationAccess, DistributionPoint, UniformResourceIdentifier
|
||||||
|
from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID, ObjectIdentifier
|
||||||
|
|
||||||
|
|
||||||
|
RRDP_HOST = "host.docker.internal"
|
||||||
|
RRDP_PORT = 18443
|
||||||
|
RSYNC_PORT = 1873
|
||||||
|
TAL_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/ta.cer"
|
||||||
|
TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer"
|
||||||
|
RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml"
|
||||||
|
ROOT_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/root/"
|
||||||
|
ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft"
|
||||||
|
CHILD_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/child/"
|
||||||
|
CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft"
|
||||||
|
OID_SIA = ObjectIdentifier("1.3.6.1.5.5.7.1.11")
|
||||||
|
OID_RESOURCES_IP = ObjectIdentifier("1.3.6.1.5.5.7.1.7")
|
||||||
|
OID_RESOURCES_AS = ObjectIdentifier("1.3.6.1.5.5.7.1.8")
|
||||||
|
OID_POLICY_IP_AS = ObjectIdentifier("1.3.6.1.5.5.7.14.2")
|
||||||
|
OID_ROA_ECONTENT = "1.2.840.113549.1.9.16.1.24"
|
||||||
|
OID_MANIFEST_ECONTENT = "1.2.840.113549.1.9.16.1.26"
|
||||||
|
OID_SHA256 = "2.16.840.1.101.3.4.2.1"
|
||||||
|
SIA_CA_REPOSITORY = "1.3.6.1.5.5.7.48.5"
|
||||||
|
SIA_RPKI_MANIFEST = "1.3.6.1.5.5.7.48.10"
|
||||||
|
SIA_SIGNED_OBJECT = "1.3.6.1.5.5.7.48.11"
|
||||||
|
SIA_RPKI_NOTIFY = "1.3.6.1.5.5.7.48.13"
|
||||||
|
UTC = timezone.utc
|
||||||
|
VALID_FROM = datetime(2026, 1, 1, tzinfo=UTC)
|
||||||
|
VALID_TO = datetime(2027, 12, 31, 23, 59, 59, tzinfo=UTC)
|
||||||
|
CHILD_PREFIX = ipaddress.ip_network("203.0.113.0/24")
|
||||||
|
SECOND_PREFIX = ipaddress.ip_network("203.0.113.128/25")
|
||||||
|
OUTSIDE_PREFIX = ipaddress.ip_network("203.0.114.0/24")
|
||||||
|
TEST_ASN = 64496
|
||||||
|
SESSION_ID = "11111111-2222-4333-8444-555555555555"
|
||||||
|
|
||||||
|
|
||||||
|
def tlv(tag: int, value: bytes) -> bytes:
|
||||||
|
length = len(value)
|
||||||
|
if length < 128:
|
||||||
|
encoded_length = bytes([length])
|
||||||
|
else:
|
||||||
|
raw = length.to_bytes((length.bit_length() + 7) // 8, "big")
|
||||||
|
encoded_length = bytes([0x80 | len(raw)]) + raw
|
||||||
|
return bytes([tag]) + encoded_length + value
|
||||||
|
|
||||||
|
|
||||||
|
def seq(*values: bytes) -> bytes:
|
||||||
|
return tlv(0x30, b"".join(values))
|
||||||
|
|
||||||
|
|
||||||
|
def integer(value: int) -> bytes:
|
||||||
|
raw = value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big")
|
||||||
|
if raw[0] & 0x80:
|
||||||
|
raw = b"\x00" + raw
|
||||||
|
return tlv(0x02, raw)
|
||||||
|
|
||||||
|
|
||||||
|
def oid(oid_value: str) -> bytes:
|
||||||
|
parts = [int(part) for part in oid_value.split(".")]
|
||||||
|
encoded = bytearray([40 * parts[0] + parts[1]])
|
||||||
|
for part in parts[2:]:
|
||||||
|
chunks = [part & 0x7F]
|
||||||
|
part >>= 7
|
||||||
|
while part:
|
||||||
|
chunks.append(0x80 | (part & 0x7F))
|
||||||
|
part >>= 7
|
||||||
|
encoded.extend(reversed(chunks))
|
||||||
|
return tlv(0x06, bytes(encoded))
|
||||||
|
|
||||||
|
|
||||||
|
def octet(value: bytes) -> bytes:
|
||||||
|
return tlv(0x04, value)
|
||||||
|
|
||||||
|
|
||||||
|
def uri(value: str) -> bytes:
|
||||||
|
return tlv(0x86, value.encode("ascii"))
|
||||||
|
|
||||||
|
|
||||||
|
def bit_string(value: bytes, unused: int = 0) -> bytes:
|
||||||
|
return tlv(0x03, bytes([unused]) + value)
|
||||||
|
|
||||||
|
|
||||||
|
def generalized_time(value: datetime) -> bytes:
|
||||||
|
return tlv(0x18, value.astimezone(UTC).strftime("%Y%m%d%H%M%SZ").encode("ascii"))
|
||||||
|
|
||||||
|
|
||||||
|
def context_zero(value: bytes) -> bytes:
|
||||||
|
return tlv(0xA0, value)
|
||||||
|
|
||||||
|
|
||||||
|
def ip_choice(networks: list[ipaddress._BaseNetwork]) -> bytes:
|
||||||
|
entries = []
|
||||||
|
for network in networks:
|
||||||
|
length = (network.prefixlen + 7) // 8
|
||||||
|
raw = network.network_address.packed[:length]
|
||||||
|
entries.append(bit_string(raw, length * 8 - network.prefixlen))
|
||||||
|
return seq(*entries)
|
||||||
|
|
||||||
|
|
||||||
|
def ip_resources(networks: list[ipaddress._BaseNetwork]) -> bytes:
|
||||||
|
families: dict[int, list[ipaddress._BaseNetwork]] = {1: [], 2: []}
|
||||||
|
for network in networks:
|
||||||
|
families[1 if network.version == 4 else 2].append(network)
|
||||||
|
return seq(
|
||||||
|
*(
|
||||||
|
seq(octet(afi.to_bytes(2, "big")), ip_choice(items))
|
||||||
|
for afi, items in families.items()
|
||||||
|
if items
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ip_inherit() -> bytes:
|
||||||
|
return seq(
|
||||||
|
seq(octet(b"\x00\x01"), tlv(0x05, b"")),
|
||||||
|
seq(octet(b"\x00\x02"), tlv(0x05, b"")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def as_resources(asns: list[int] | None) -> bytes:
|
||||||
|
if asns is None:
|
||||||
|
return seq(context_zero(tlv(0x05, b"")))
|
||||||
|
return seq(context_zero(seq(*(integer(asn) for asn in asns))))
|
||||||
|
|
||||||
|
|
||||||
|
def sia(entries: list[tuple[str, str]]) -> bytes:
|
||||||
|
return seq(*(seq(oid(method), uri(location)) for method, location in entries))
|
||||||
|
|
||||||
|
|
||||||
|
def write(path: Path, data: bytes, mode: int | None = None) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_bytes(data)
|
||||||
|
if mode is not None:
|
||||||
|
path.chmod(mode)
|
||||||
|
|
||||||
|
|
||||||
|
def pem_cert(cert: x509.Certificate) -> bytes:
|
||||||
|
return cert.public_bytes(Encoding.PEM)
|
||||||
|
|
||||||
|
|
||||||
|
def pem_key(key: rsa.RSAPrivateKey) -> bytes:
|
||||||
|
return key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption())
|
||||||
|
|
||||||
|
|
||||||
|
def write_key(path: Path, key: rsa.RSAPrivateKey) -> None:
|
||||||
|
write(path, pem_key(key), 0o600)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_cert(
|
||||||
|
subject: x509.Name,
|
||||||
|
issuer: x509.Name,
|
||||||
|
public_key,
|
||||||
|
signing_key: rsa.RSAPrivateKey,
|
||||||
|
serial: int,
|
||||||
|
is_ca: bool,
|
||||||
|
ski_key: rsa.RSAPrivateKey,
|
||||||
|
aki_key: rsa.RSAPrivateKey | None = None,
|
||||||
|
ip_ext: bytes | None = None,
|
||||||
|
as_ext: bytes | None = None,
|
||||||
|
ca_repository: str | None = None,
|
||||||
|
manifest_uri: str | None = None,
|
||||||
|
notify_uri: str | None = None,
|
||||||
|
signed_object_uri: str | None = None,
|
||||||
|
crl_uri: str | None = None,
|
||||||
|
ca_issuer_uri: str | None = None,
|
||||||
|
not_before: datetime = VALID_FROM,
|
||||||
|
not_after: datetime = VALID_TO,
|
||||||
|
) -> x509.Certificate:
|
||||||
|
builder = (
|
||||||
|
x509.CertificateBuilder()
|
||||||
|
.subject_name(subject)
|
||||||
|
.issuer_name(issuer)
|
||||||
|
.public_key(public_key)
|
||||||
|
.serial_number(serial)
|
||||||
|
.not_valid_before(not_before)
|
||||||
|
.not_valid_after(not_after)
|
||||||
|
)
|
||||||
|
if is_ca:
|
||||||
|
builder = builder.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
|
||||||
|
if is_ca:
|
||||||
|
usage = x509.KeyUsage(False, False, False, False, False, True, True, False, False)
|
||||||
|
else:
|
||||||
|
usage = x509.KeyUsage(True, False, False, False, False, False, False, False, False)
|
||||||
|
builder = builder.add_extension(usage, critical=True)
|
||||||
|
builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(ski_key.public_key()), critical=False)
|
||||||
|
if aki_key is not None:
|
||||||
|
builder = builder.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(aki_key.public_key()), critical=False)
|
||||||
|
builder = builder.add_extension(
|
||||||
|
x509.CertificatePolicies([x509.PolicyInformation(OID_POLICY_IP_AS, None)]),
|
||||||
|
critical=True,
|
||||||
|
)
|
||||||
|
if crl_uri is not None:
|
||||||
|
builder = builder.add_extension(
|
||||||
|
x509.CRLDistributionPoints(
|
||||||
|
[DistributionPoint([UniformResourceIdentifier(crl_uri)], None, None, None)]
|
||||||
|
),
|
||||||
|
critical=False,
|
||||||
|
)
|
||||||
|
if ca_issuer_uri is not None:
|
||||||
|
builder = builder.add_extension(
|
||||||
|
AuthorityInformationAccess(
|
||||||
|
[AccessDescription(AuthorityInformationAccessOID.CA_ISSUERS, UniformResourceIdentifier(ca_issuer_uri))]
|
||||||
|
),
|
||||||
|
critical=False,
|
||||||
|
)
|
||||||
|
sia_entries: list[tuple[str, str]] = []
|
||||||
|
if ca_repository is not None:
|
||||||
|
sia_entries.append((SIA_CA_REPOSITORY, ca_repository))
|
||||||
|
if manifest_uri is not None:
|
||||||
|
sia_entries.append((SIA_RPKI_MANIFEST, manifest_uri))
|
||||||
|
if notify_uri is not None:
|
||||||
|
sia_entries.append((SIA_RPKI_NOTIFY, notify_uri))
|
||||||
|
if signed_object_uri is not None:
|
||||||
|
sia_entries.append((SIA_SIGNED_OBJECT, signed_object_uri))
|
||||||
|
if sia_entries:
|
||||||
|
builder = builder.add_extension(x509.UnrecognizedExtension(OID_SIA, sia(sia_entries)), critical=False)
|
||||||
|
if ip_ext is not None:
|
||||||
|
builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_IP, ip_ext), critical=True)
|
||||||
|
if as_ext is not None:
|
||||||
|
builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_AS, as_ext), critical=True)
|
||||||
|
return builder.sign(signing_key, hashes.SHA256())
|
||||||
|
|
||||||
|
|
||||||
|
def make_crl(subject: x509.Name, key: rsa.RSAPrivateKey, number: int) -> x509.CertificateRevocationList:
|
||||||
|
return (
|
||||||
|
x509.CertificateRevocationListBuilder()
|
||||||
|
.issuer_name(subject)
|
||||||
|
.last_update(VALID_FROM + timedelta(days=30))
|
||||||
|
.next_update(VALID_TO - timedelta(days=30))
|
||||||
|
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()), critical=False)
|
||||||
|
.add_extension(x509.CRLNumber(number), critical=False)
|
||||||
|
.sign(key, hashes.SHA256())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def roa_content(prefix: ipaddress._BaseNetwork, max_length: int) -> bytes:
|
||||||
|
length = (prefix.prefixlen + 7) // 8
|
||||||
|
raw = prefix.network_address.packed[:length]
|
||||||
|
address = seq(bit_string(raw, length * 8 - prefix.prefixlen), integer(max_length))
|
||||||
|
family = seq(octet((1 if prefix.version == 4 else 2).to_bytes(2, "big")), seq(address))
|
||||||
|
return seq(integer(TEST_ASN), seq(family))
|
||||||
|
|
||||||
|
|
||||||
|
def manifest_content(number: int, files: list[tuple[str, bytes]]) -> bytes:
|
||||||
|
entries = [
|
||||||
|
seq(tlv(0x16, name.encode("ascii")), bit_string(hashlib.sha256(content).digest()))
|
||||||
|
for name, content in files
|
||||||
|
]
|
||||||
|
return seq(
|
||||||
|
integer(number),
|
||||||
|
generalized_time(VALID_FROM + timedelta(days=30)),
|
||||||
|
generalized_time(VALID_TO - timedelta(days=30)),
|
||||||
|
oid(OID_SHA256),
|
||||||
|
seq(*entries),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_cms(content: bytes, content_type: str, signer_cert: x509.Certificate, signer_key: rsa.RSAPrivateKey, output: Path, work: Path) -> None:
|
||||||
|
content_path = work / f"{output.name}.content.der"
|
||||||
|
signer_path = work / f"{output.name}.signer.pem"
|
||||||
|
key_path = work / f"{output.name}.key.pem"
|
||||||
|
write(content_path, content)
|
||||||
|
write(signer_path, pem_cert(signer_cert))
|
||||||
|
write_key(key_path, signer_key)
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"openssl", "cms", "-sign", "-binary", "-in", str(content_path),
|
||||||
|
"-signer", str(signer_path), "-inkey", str(key_path),
|
||||||
|
"-outform", "DER", "-nodetach", "-nosmimecap", "-keyid", "-md", "sha256",
|
||||||
|
"-econtent_type", content_type, "-out", str(output),
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_roa(
|
||||||
|
name: str,
|
||||||
|
prefix,
|
||||||
|
max_length: int,
|
||||||
|
not_before: datetime,
|
||||||
|
not_after: datetime,
|
||||||
|
child_name,
|
||||||
|
child_key,
|
||||||
|
common: Path,
|
||||||
|
work: Path,
|
||||||
|
certificate_prefix=None,
|
||||||
|
) -> None:
|
||||||
|
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
cert = sign_cert(
|
||||||
|
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"#142 EE {name}")]),
|
||||||
|
child_name,
|
||||||
|
key.public_key(),
|
||||||
|
child_key,
|
||||||
|
x509.random_serial_number(),
|
||||||
|
False,
|
||||||
|
key,
|
||||||
|
child_key,
|
||||||
|
ip_ext=ip_resources([certificate_prefix or prefix]),
|
||||||
|
signed_object_uri=f"{CHILD_REPO_URI}{name}",
|
||||||
|
crl_uri=f"{CHILD_REPO_URI}child.crl",
|
||||||
|
ca_issuer_uri=f"{CHILD_REPO_URI}child.cer",
|
||||||
|
not_before=not_before,
|
||||||
|
not_after=not_after,
|
||||||
|
)
|
||||||
|
write_cms(roa_content(prefix, max_length), OID_ROA_ECONTENT, cert, key, common / name, work)
|
||||||
|
|
||||||
|
|
||||||
|
def make_manifest(name: str, number: int, files: list[tuple[str, bytes]], child_name, child_key, common: Path, work: Path) -> bytes:
|
||||||
|
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
cert = sign_cert(
|
||||||
|
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"#142 Manifest EE {name}")]),
|
||||||
|
child_name,
|
||||||
|
key.public_key(),
|
||||||
|
child_key,
|
||||||
|
x509.random_serial_number(),
|
||||||
|
False,
|
||||||
|
key,
|
||||||
|
child_key,
|
||||||
|
ip_ext=ip_inherit(),
|
||||||
|
as_ext=as_resources(None),
|
||||||
|
signed_object_uri=CHILD_MFT_URI if name != "root" else ROOT_MFT_URI,
|
||||||
|
crl_uri=f"{CHILD_REPO_URI}child.crl" if name != "root" else f"{ROOT_REPO_URI}root.crl",
|
||||||
|
ca_issuer_uri=f"{CHILD_REPO_URI}child.cer" if name != "root" else f"{ROOT_REPO_URI}ta.cer",
|
||||||
|
)
|
||||||
|
output = common / f"{name}.mft"
|
||||||
|
write_cms(manifest_content(number, files), OID_MANIFEST_ECONTENT, cert, key, output, work)
|
||||||
|
return output.read_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
def publish(uri: str, path: Path) -> str:
|
||||||
|
encoded = base64.b64encode(path.read_bytes()).decode("ascii")
|
||||||
|
return f' <publish uri="{uri}">{encoded}</publish>\n'
|
||||||
|
|
||||||
|
|
||||||
|
def snapshot(case_root: Path, names: list[str], serial: int, extra_root_names: list[str] | None = None) -> bytes:
|
||||||
|
repository = case_root / "repository"
|
||||||
|
objects = [
|
||||||
|
(f"{ROOT_REPO_URI}root.crl", repository / "root/root.crl"),
|
||||||
|
(f"{ROOT_REPO_URI}child.cer", repository / "root/child.cer"),
|
||||||
|
*[
|
||||||
|
(f"{ROOT_REPO_URI}{name}", repository / f"root/{name}")
|
||||||
|
for name in extra_root_names or []
|
||||||
|
],
|
||||||
|
(f"{CHILD_REPO_URI}child.crl", repository / "child/child.crl"),
|
||||||
|
*[(f"{CHILD_REPO_URI}{name}", repository / f"child/{name}") for name in names],
|
||||||
|
(ROOT_MFT_URI, repository / "root/root.mft"),
|
||||||
|
(CHILD_MFT_URI, repository / "child/child.mft"),
|
||||||
|
]
|
||||||
|
body = "".join(publish(uri, path) for uri, path in objects)
|
||||||
|
return (
|
||||||
|
f'<snapshot xmlns="http://www.ripe.net/rpki/rrdp" session_id="{SESSION_ID}" serial="{serial}">\n'
|
||||||
|
f"{body}</snapshot>\n"
|
||||||
|
).encode()
|
||||||
|
|
||||||
|
|
||||||
|
def notification(serial: int, snap: bytes, delta: bytes | None) -> bytes:
|
||||||
|
lines = [
|
||||||
|
f'<notification xmlns="http://www.ripe.net/rpki/rrdp" version="1" session_id="{SESSION_ID}" serial="{serial}">',
|
||||||
|
f' <snapshot uri="https://{RRDP_HOST}:{RRDP_PORT}/rrdp/snapshot.xml" hash="{hashlib.sha256(snap).hexdigest()}"/>',
|
||||||
|
]
|
||||||
|
if delta is not None:
|
||||||
|
lines.append(
|
||||||
|
f' <delta serial="{serial}" uri="https://{RRDP_HOST}:{RRDP_PORT}/rrdp/delta-{serial}.xml" hash="{hashlib.sha256(delta).hexdigest()}"/>'
|
||||||
|
)
|
||||||
|
return ("\n".join(lines) + "\n</notification>\n").encode()
|
||||||
|
|
||||||
|
|
||||||
|
def corrupt_first_hash(xml: bytes) -> bytes:
|
||||||
|
marker = b'hash="'
|
||||||
|
start = xml.index(marker) + len(marker)
|
||||||
|
end = xml.index(b'"', start)
|
||||||
|
value = bytearray(xml[start:end])
|
||||||
|
value[0] = ord("0") if value[0] != ord("0") else ord("1")
|
||||||
|
return xml[:start] + bytes(value) + xml[end:]
|
||||||
|
|
||||||
|
|
||||||
|
def delta(add_name: str, add_path: Path, new_mft: Path) -> bytes:
|
||||||
|
body = publish(CHILD_MFT_URI, new_mft)
|
||||||
|
body += publish(f"{CHILD_REPO_URI}{add_name}", add_path)
|
||||||
|
return f'<delta xmlns="http://www.ripe.net/rpki/rrdp" session_id="{SESSION_ID}" serial="2">\n{body}</delta>\n'.encode()
|
||||||
|
|
||||||
|
|
||||||
|
def build_case(
|
||||||
|
output: Path,
|
||||||
|
common: Path,
|
||||||
|
tal: bytes,
|
||||||
|
ta: bytes,
|
||||||
|
name: str,
|
||||||
|
number: int,
|
||||||
|
names: list[str],
|
||||||
|
child_mft: bytes,
|
||||||
|
change: bytes | None = None,
|
||||||
|
root_mft: bytes | None = None,
|
||||||
|
extra_root_objects: list[tuple[str, bytes]] | None = None,
|
||||||
|
notification_hash_mismatch: bool = False,
|
||||||
|
) -> None:
|
||||||
|
case_root = output / "cases" / name
|
||||||
|
repo = case_root / "repository"
|
||||||
|
(repo / "root").mkdir(parents=True, exist_ok=True)
|
||||||
|
(repo / "child").mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(common / "ta.cer", repo / "ta.cer")
|
||||||
|
for item in ("root.crl", "child.cer"):
|
||||||
|
shutil.copy2(common / item, repo / "root" / item)
|
||||||
|
(repo / "root/root.mft").write_bytes(root_mft if root_mft is not None else (common / "root.mft").read_bytes())
|
||||||
|
for item, content in extra_root_objects or []:
|
||||||
|
(repo / "root" / item).write_bytes(content)
|
||||||
|
for item in (
|
||||||
|
"child.crl", "valid.roa", "valid2.roa", "expired.roa", "max-length.roa",
|
||||||
|
"max-length-invalid.roa", "roa-prefix-outside.roa", "out-of-resource.roa", "bad-format.roa",
|
||||||
|
):
|
||||||
|
if (common / item).exists():
|
||||||
|
shutil.copy2(common / item, repo / "child" / item)
|
||||||
|
(repo / "child/child.mft").write_bytes(child_mft)
|
||||||
|
snap = snapshot(case_root, names, number, [item for item, _ in extra_root_objects or []])
|
||||||
|
(case_root / "http/rrdp").mkdir(parents=True, exist_ok=True)
|
||||||
|
(case_root / "http/tal").mkdir(parents=True, exist_ok=True)
|
||||||
|
(case_root / "http/ta").mkdir(parents=True, exist_ok=True)
|
||||||
|
(case_root / "http/rrdp/snapshot.xml").write_bytes(snap)
|
||||||
|
notification_xml = notification(number, snap, change)
|
||||||
|
if notification_hash_mismatch:
|
||||||
|
notification_xml = corrupt_first_hash(notification_xml)
|
||||||
|
(case_root / "http/rrdp/notification.xml").write_bytes(notification_xml)
|
||||||
|
if change is not None:
|
||||||
|
(case_root / "http/rrdp/delta-2.xml").write_bytes(change)
|
||||||
|
(case_root / "http/tal/custom.tal").write_bytes(tal)
|
||||||
|
(case_root / "http/ta/custom-ta.cer").write_bytes(ta)
|
||||||
|
(case_root / "CASE-MANIFEST.txt").write_text(
|
||||||
|
f"case={name}\nserial={number}\nobjects={','.join(names)}\nsnapshot_sha256={hashlib.sha256(snap).hexdigest()}\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_https_certificates(output: Path) -> None:
|
||||||
|
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 fixed RRDP test CA")])
|
||||||
|
ca_cert = (
|
||||||
|
x509.CertificateBuilder()
|
||||||
|
.subject_name(ca_name).issuer_name(ca_name).public_key(ca_key.public_key())
|
||||||
|
.serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO)
|
||||||
|
.add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True)
|
||||||
|
.add_extension(x509.KeyUsage(False, False, False, False, False, True, True, False, False), critical=True)
|
||||||
|
.sign(ca_key, hashes.SHA256())
|
||||||
|
)
|
||||||
|
server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, RRDP_HOST)])
|
||||||
|
try:
|
||||||
|
server_host = x509.IPAddress(ipaddress.ip_address(RRDP_HOST))
|
||||||
|
except ValueError:
|
||||||
|
server_host = x509.DNSName(RRDP_HOST)
|
||||||
|
san_entries = [server_host]
|
||||||
|
if RRDP_HOST != "localhost":
|
||||||
|
san_entries.append(x509.DNSName("localhost"))
|
||||||
|
server_cert = (
|
||||||
|
x509.CertificateBuilder()
|
||||||
|
.subject_name(server_name).issuer_name(ca_cert.subject).public_key(server_key.public_key())
|
||||||
|
.serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO)
|
||||||
|
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
||||||
|
.add_extension(x509.KeyUsage(True, False, False, False, False, False, False, False, False), critical=True)
|
||||||
|
.add_extension(x509.SubjectAlternativeName(san_entries), critical=False)
|
||||||
|
.sign(ca_key, hashes.SHA256())
|
||||||
|
)
|
||||||
|
write(output / "certs/rrdp-ca.pem", pem_cert(ca_cert))
|
||||||
|
write_key(output / "certs/rrdp-ca.key", ca_key)
|
||||||
|
write(output / "certs/rrdp-server.pem", pem_cert(server_cert))
|
||||||
|
write_key(output / "certs/rrdp-server.key", server_key)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
global RRDP_HOST, TAL_URI, TAL_HTTPS_URI, RRDP_URI
|
||||||
|
global ROOT_REPO_URI, ROOT_MFT_URI, CHILD_REPO_URI, CHILD_MFT_URI
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
"--host",
|
||||||
|
default=RRDP_HOST,
|
||||||
|
help="host embedded in TAL, certificate SIA and RRDP data (default: host.docker.internal)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
RRDP_HOST = args.host
|
||||||
|
TAL_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/ta.cer"
|
||||||
|
TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer"
|
||||||
|
RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml"
|
||||||
|
ROOT_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/root/"
|
||||||
|
ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft"
|
||||||
|
CHILD_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/child/"
|
||||||
|
CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft"
|
||||||
|
output = args.output.resolve()
|
||||||
|
if output.exists():
|
||||||
|
shutil.rmtree(output)
|
||||||
|
for directory in ("tal", "ta", "certs", "services", "keys"):
|
||||||
|
(output / directory).mkdir(parents=True)
|
||||||
|
|
||||||
|
root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
child_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
root_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Root")])
|
||||||
|
child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Child")])
|
||||||
|
root_cert = sign_cert(
|
||||||
|
root_name, root_name, root_key.public_key(), root_key, 0x14200001, True, root_key,
|
||||||
|
ip_ext=ip_resources([CHILD_PREFIX]),
|
||||||
|
as_ext=as_resources([TEST_ASN]), ca_repository=ROOT_REPO_URI, manifest_uri=ROOT_MFT_URI,
|
||||||
|
notify_uri=RRDP_URI,
|
||||||
|
)
|
||||||
|
child_cert = sign_cert(
|
||||||
|
child_name, root_name, child_key.public_key(), root_key, 0x14200002, True, child_key, root_key,
|
||||||
|
ip_ext=ip_resources([CHILD_PREFIX]), as_ext=as_resources([TEST_ASN]),
|
||||||
|
ca_repository=CHILD_REPO_URI, manifest_uri=CHILD_MFT_URI, notify_uri=RRDP_URI,
|
||||||
|
crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer",
|
||||||
|
)
|
||||||
|
bad_child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Overclaiming Child")])
|
||||||
|
bad_child_repo_uri = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/bad-child/"
|
||||||
|
bad_child_cert = sign_cert(
|
||||||
|
bad_child_name, root_name, child_key.public_key(), root_key, 0x14200003, True, child_key, root_key,
|
||||||
|
ip_ext=ip_resources([OUTSIDE_PREFIX]), as_ext=as_resources([TEST_ASN]),
|
||||||
|
ca_repository=bad_child_repo_uri, manifest_uri=f"{bad_child_repo_uri}bad-child.mft", notify_uri=RRDP_URI,
|
||||||
|
crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer",
|
||||||
|
)
|
||||||
|
root_crl = make_crl(root_name, root_key, 1)
|
||||||
|
child_crl = make_crl(child_name, child_key, 1)
|
||||||
|
tal = (
|
||||||
|
f"{TAL_URI}\n{TAL_HTTPS_URI}\n\n"
|
||||||
|
+ base64.b64encode(root_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)).decode()
|
||||||
|
+ "\n"
|
||||||
|
).encode()
|
||||||
|
write(output / "tal/custom.tal", tal)
|
||||||
|
write(output / "ta/custom-ta.cer", root_cert.public_bytes(Encoding.DER))
|
||||||
|
with tempfile.TemporaryDirectory(prefix="custom-rpki-cms-") as temp:
|
||||||
|
work = Path(temp)
|
||||||
|
common = output / ".common"
|
||||||
|
common.mkdir()
|
||||||
|
write(common / "ta.cer", root_cert.public_bytes(Encoding.DER))
|
||||||
|
write(common / "child.cer", child_cert.public_bytes(Encoding.DER))
|
||||||
|
write(common / "bad-child.cer", bad_child_cert.public_bytes(Encoding.DER))
|
||||||
|
write(common / "root.crl", root_crl.public_bytes(Encoding.DER))
|
||||||
|
write(common / "child.crl", child_crl.public_bytes(Encoding.DER))
|
||||||
|
for name, prefix, max_length, before, after, certificate_prefix in (
|
||||||
|
("valid.roa", CHILD_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||||
|
("valid2.roa", SECOND_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||||
|
("expired.roa", CHILD_PREFIX, 24, VALID_FROM - timedelta(days=30), VALID_FROM - timedelta(days=1), None),
|
||||||
|
("max-length.roa", CHILD_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||||
|
("max-length-invalid.roa", CHILD_PREFIX, 33, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||||
|
("roa-prefix-outside.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), CHILD_PREFIX),
|
||||||
|
("out-of-resource.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||||
|
):
|
||||||
|
make_roa(
|
||||||
|
name, prefix, max_length, before, after, child_name, child_key, common, work,
|
||||||
|
certificate_prefix=certificate_prefix,
|
||||||
|
)
|
||||||
|
bad = bytearray((common / "valid.roa").read_bytes())
|
||||||
|
bad[-1] ^= 1
|
||||||
|
write(common / "bad-format.roa", bytes(bad))
|
||||||
|
|
||||||
|
root_mft = make_manifest(
|
||||||
|
"root", 1, [
|
||||||
|
("root.crl", (common / "root.crl").read_bytes()),
|
||||||
|
("child.cer", (common / "child.cer").read_bytes()),
|
||||||
|
], root_name, root_key, common, work,
|
||||||
|
)
|
||||||
|
write(common / "root.mft", root_mft)
|
||||||
|
bad_root_mft = make_manifest(
|
||||||
|
"root", 1, [
|
||||||
|
("root.crl", (common / "root.crl").read_bytes()),
|
||||||
|
("child.cer", (common / "child.cer").read_bytes()),
|
||||||
|
("bad-child.cer", (common / "bad-child.cer").read_bytes()),
|
||||||
|
], root_name, root_key, common, work,
|
||||||
|
)
|
||||||
|
write(common / "root.mft", root_mft)
|
||||||
|
case_specs = (
|
||||||
|
("baseline-v1", 1, ["child.crl", "valid.roa"]),
|
||||||
|
("sync-hash-mismatch", 1, ["child.crl", "valid.roa"]),
|
||||||
|
("baseline-v2", 2, ["child.crl", "valid.roa", "valid2.roa"]),
|
||||||
|
("validation-expired", 1, ["child.crl", "expired.roa"]),
|
||||||
|
("validation-max-length", 1, ["child.crl", "max-length.roa"]),
|
||||||
|
("validation-max-length-invalid", 1, ["child.crl", "max-length-invalid.roa"]),
|
||||||
|
("validation-roa-prefix-outside", 1, ["child.crl", "roa-prefix-outside.roa"]),
|
||||||
|
("validation-out-of-resource", 1, ["child.crl", "out-of-resource.roa"]),
|
||||||
|
("validation-ca-over-resource", 1, ["child.crl", "valid.roa"]),
|
||||||
|
("validation-nonstandard", 1, ["child.crl", "bad-format.roa"]),
|
||||||
|
)
|
||||||
|
child_mfts = {}
|
||||||
|
for name, number, names in case_specs:
|
||||||
|
child_mfts[name] = make_manifest(
|
||||||
|
name, number, [(item, (common / item).read_bytes()) for item in names],
|
||||||
|
child_name, child_key, common, work,
|
||||||
|
)
|
||||||
|
for name, number, names in case_specs:
|
||||||
|
change = None
|
||||||
|
if name == "baseline-v2":
|
||||||
|
change = delta("valid2.roa", common / "valid2.roa", common / "baseline-v2.mft")
|
||||||
|
build_case(
|
||||||
|
output, common, tal, root_cert.public_bytes(Encoding.DER), name, number, names,
|
||||||
|
child_mfts[name], change,
|
||||||
|
root_mft=bad_root_mft if name == "validation-ca-over-resource" else None,
|
||||||
|
extra_root_objects=[("bad-child.cer", (common / "bad-child.cer").read_bytes())]
|
||||||
|
if name == "validation-ca-over-resource" else None,
|
||||||
|
notification_hash_mismatch=name == "sync-hash-mismatch",
|
||||||
|
)
|
||||||
|
write_key(output / "keys/root.key", root_key)
|
||||||
|
write_key(output / "keys/child.key", child_key)
|
||||||
|
make_https_certificates(output)
|
||||||
|
shutil.rmtree(output / ".common")
|
||||||
|
(output / "FIXTURE-MANIFEST.txt").write_text(
|
||||||
|
"fixture_schema_version=1\nfixture_kind=custom-tal-fixed-local-rpki\n"
|
||||||
|
f"tal_uri={TAL_URI}\nrrdp_uri={RRDP_URI}\nrsync_module=custom\n"
|
||||||
|
f"rrdp_port={RRDP_PORT}\nrsync_port={RSYNC_PORT}\n"
|
||||||
|
"cases=baseline-v1,sync-hash-mismatch,baseline-v2,validation-expired,validation-max-length,validation-max-length-invalid,"
|
||||||
|
"validation-roa-prefix-outside,validation-out-of-resource,validation-ca-over-resource,validation-nonstandard\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
print(f"generated custom fixture: {output}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Serve a fixture directory over HTTPS with request logging."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import http.server
|
||||||
|
import ssl
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def log_message(self, format: str, *args) -> None:
|
||||||
|
print("%s %s" % (self.log_date_time_string(), format % args), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--directory", type=Path, required=True)
|
||||||
|
parser.add_argument("--certfile", type=Path, required=True)
|
||||||
|
parser.add_argument("--keyfile", type=Path, required=True)
|
||||||
|
parser.add_argument("--host", default="0.0.0.0")
|
||||||
|
parser.add_argument("--port", type=int, default=18443)
|
||||||
|
args = parser.parse_args()
|
||||||
|
handler = lambda *handler_args, directory=str(args.directory): RequestHandler(
|
||||||
|
*handler_args, directory=directory
|
||||||
|
)
|
||||||
|
server = http.server.ThreadingHTTPServer((args.host, args.port), handler)
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
context.load_cert_chain(certfile=args.certfile, keyfile=args.keyfile)
|
||||||
|
server.socket = context.wrap_socket(server.socket, server_side=True)
|
||||||
|
print(f"https fixture server listening on {args.host}:{args.port} root={args.directory}", flush=True)
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$BASH_SOURCE")" && pwd)"
|
||||||
|
FIXTURE_ROOT=""
|
||||||
|
CASE_NAME="baseline-v1"
|
||||||
|
RRDP_PORT="$(printenv RRDP_PORT 2>/dev/null || true)"
|
||||||
|
RSYNC_PORT="$(printenv RSYNC_PORT 2>/dev/null || true)"
|
||||||
|
PID_ROOT=""
|
||||||
|
|
||||||
|
[[ -n "$RRDP_PORT" ]] || RRDP_PORT=18443
|
||||||
|
[[ -n "$RSYNC_PORT" ]] || RSYNC_PORT=1873
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
./start_fixed_services.sh --fixture-root <generated-fixtures> [--case NAME]
|
||||||
|
|
||||||
|
Cases: baseline-v1, sync-hash-mismatch, baseline-v2, validation-expired,
|
||||||
|
validation-max-length, validation-max-length-invalid,
|
||||||
|
validation-roa-prefix-outside, validation-out-of-resource,
|
||||||
|
validation-ca-over-resource, validation-nonstandard
|
||||||
|
Environment: RRDP_PORT (default 18443), RSYNC_PORT (default 1873)
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "error: $*" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--fixture-root) FIXTURE_ROOT="$2"; shift 2 ;;
|
||||||
|
--case) CASE_NAME="$2"; shift 2 ;;
|
||||||
|
--pid-root) PID_ROOT="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$FIXTURE_ROOT" ]] || die "--fixture-root is required"
|
||||||
|
FIXTURE_ROOT="$(cd "$FIXTURE_ROOT" 2>/dev/null && pwd)" || die "fixture root not found"
|
||||||
|
CASE_ROOT="$FIXTURE_ROOT/cases/$CASE_NAME"
|
||||||
|
[[ -d "$CASE_ROOT/repository" && -d "$CASE_ROOT/http" ]] || die "fixture case not found: $CASE_NAME"
|
||||||
|
if [[ -z "$PID_ROOT" ]]; then
|
||||||
|
PID_ROOT="$FIXTURE_ROOT/services/$CASE_NAME"
|
||||||
|
fi
|
||||||
|
mkdir -p "$PID_ROOT"
|
||||||
|
[[ ! -f "$PID_ROOT/pids.env" ]] || die "services already appear to be running: $PID_ROOT/pids.env"
|
||||||
|
rm -f "$PID_ROOT/rsyncd.pid" "$PID_ROOT/rsyncd.lock"
|
||||||
|
|
||||||
|
RSYNCD_CONF="$PID_ROOT/rsyncd.conf"
|
||||||
|
RSYNC_UID="$(id -un)"
|
||||||
|
RSYNC_GID="$(id -gn)"
|
||||||
|
cat > "$RSYNCD_CONF" <<EOF
|
||||||
|
uid = $RSYNC_UID
|
||||||
|
gid = $RSYNC_GID
|
||||||
|
use chroot = no
|
||||||
|
read only = yes
|
||||||
|
port = $RSYNC_PORT
|
||||||
|
pid file = $PID_ROOT/rsyncd.pid
|
||||||
|
lock file = $PID_ROOT/rsyncd.lock
|
||||||
|
log file = $PID_ROOT/rsyncd.log
|
||||||
|
|
||||||
|
[custom]
|
||||||
|
path = $CASE_ROOT/repository
|
||||||
|
comment = #142 fixed custom RPKI repository
|
||||||
|
read only = yes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rsync --daemon --no-detach --config="$RSYNCD_CONF" >"$PID_ROOT/rsyncd.stdout.log" 2>&1 &
|
||||||
|
RSYNC_PID=$!
|
||||||
|
python3 "$SCRIPT_DIR/serve_https.py" \
|
||||||
|
--directory "$CASE_ROOT/http" \
|
||||||
|
--certfile "$FIXTURE_ROOT/certs/rrdp-server.pem" \
|
||||||
|
--keyfile "$FIXTURE_ROOT/certs/rrdp-server.key" \
|
||||||
|
--port "$RRDP_PORT" \
|
||||||
|
>"$PID_ROOT/https.log" 2>&1 &
|
||||||
|
HTTPS_PID=$!
|
||||||
|
|
||||||
|
printf 'case=%s\nrrdp_pid=%s\nrsync_pid=%s\nrrdp_port=%s\nrsync_port=%s\n' \
|
||||||
|
"$CASE_NAME" "$HTTPS_PID" "$RSYNC_PID" "$RRDP_PORT" "$RSYNC_PORT" > "$PID_ROOT/pids.env"
|
||||||
|
sleep 1
|
||||||
|
kill -0 "$HTTPS_PID" 2>/dev/null || die "HTTPS fixture server exited; see $PID_ROOT/https.log"
|
||||||
|
kill -0 "$RSYNC_PID" 2>/dev/null || die "rsync fixture server exited; see $PID_ROOT/rsyncd.stdout.log"
|
||||||
|
echo "fixed RRDP/rsync services started case=$CASE_NAME rrdp=$RRDP_PORT rsync=$RSYNC_PORT pid_root=$PID_ROOT"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PID_ROOT="$1"
|
||||||
|
[[ -n "$PID_ROOT" ]] || { echo "Usage: $0 <pid-root>" >&2; exit 2; }
|
||||||
|
[[ -f "$PID_ROOT/pids.env" ]] || { echo "services are not running: $PID_ROOT" >&2; exit 2; }
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$PID_ROOT/pids.env"
|
||||||
|
service_pids=("${rrdp_pid:-}" "${rsync_pid:-}")
|
||||||
|
for pid in "${service_pids[@]}"; do
|
||||||
|
if [[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null; then
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for pid in "${service_pids[@]}"; do
|
||||||
|
[[ "$pid" =~ ^[0-9]+$ ]] || continue
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
kill -0 "$pid" 2>/dev/null || break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
done
|
||||||
|
rm -f "$PID_ROOT/pids.env"
|
||||||
|
echo "fixed RRDP/rsync services stopped: $PID_ROOT"
|
||||||
@@ -6,7 +6,7 @@ This package deploys ours RP on Linux servers whose host architecture matches th
|
|||||||
|
|
||||||
The package includes four architecture-matched images: ours RP runtime, artifact metrics, Prometheus, and Grafana. Deployment does not need to pull application images on the target host. Runtime state, run artifacts, logs, Prometheus data and Grafana data are persisted through host bind mounts.
|
The package includes four architecture-matched images: ours RP runtime, artifact metrics, Prometheus, and Grafana. Deployment does not need to pull application images on the target host. Runtime state, run artifacts, logs, Prometheus data and Grafana data are persisted through host bind mounts.
|
||||||
|
|
||||||
Every installer build rebuilds the ours RP runtime and artifact metrics images. The deliverable name is `ours-rp-installer-{arch}-{git8}-{YYYYMMDDTHHMMSSZ}.tar.gz`, and both ours RP image tags use the current source commit. `PACKAGE-MANIFEST.env` records the full commit, UTC build time, dirty state, and image archive hashes; `./status.sh --brief` displays that provenance after installation.
|
Every installer build rebuilds the ours RP runtime and artifact metrics images. The deliverable name is `ours-rp-installer-{arch}-{git8}-{YYYYMMDDTHHMMSSZ}.tar.gz`, and both ours RP image tags use the current source commit. `PACKAGE-MANIFEST.env` records the full commit, UTC build time, dirty state, and image archive hashes; `./scripts/status.sh --brief` displays that provenance after installation.
|
||||||
|
|
||||||
By default the host architecture must match `PACKAGE_ARCH` from `PACKAGE-MANIFEST.env`. If you intentionally want to run this package on a different host architecture through QEMU/binfmt, set:
|
By default the host architecture must match `PACKAGE_ARCH` from `PACKAGE-MANIFEST.env`. If you intentionally want to run this package on a different host architecture through QEMU/binfmt, set:
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ ALLOW_CROSS_ARCH=1
|
|||||||
|
|
||||||
`compose/` is the single production Docker Compose template source. The Docker installer build script copies this directory directly. Do not maintain a second copy of the Compose, Prometheus, or Grafana dashboard files elsewhere.
|
`compose/` is the single production Docker Compose template source. The Docker installer build script copies this directory directly. Do not maintain a second copy of the Compose, Prometheus, or Grafana dashboard files elsewhere.
|
||||||
|
|
||||||
The legacy direct Compose deployment entry point has been removed. For either amd64 or arm64, build the matching installer package with `scripts/docker/build_docker_installer_package.sh`, then use `install.sh`, `start.sh`, and `status.sh` as described here.
|
The legacy direct Compose deployment entry point has been removed. For either amd64 or arm64, build the matching installer package with `scripts/docker/build_docker_installer_package.sh`, then use `scripts/install.sh`, `scripts/start.sh`, and `scripts/status.sh` as described here.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ The legacy direct Compose deployment entry point has been removed. For either am
|
|||||||
tar -xzf ours-rp-installer-__PACKAGE_ARCH__-*.tar.gz
|
tar -xzf ours-rp-installer-__PACKAGE_ARCH__-*.tar.gz
|
||||||
cd ours-rp-installer-__PACKAGE_ARCH__-*
|
cd ours-rp-installer-__PACKAGE_ARCH__-*
|
||||||
|
|
||||||
./install.sh
|
./scripts/install.sh
|
||||||
cp .env.example .env # install.sh creates .env automatically if missing
|
cp .env.example .env # install.sh creates .env automatically if missing
|
||||||
vim .env
|
vim .env
|
||||||
./start.sh
|
./scripts/start.sh
|
||||||
./status.sh
|
./scripts/status.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Defaults:
|
Defaults:
|
||||||
@@ -52,7 +52,7 @@ Defaults:
|
|||||||
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
- `ALLOW_CROSS_ARCH=0`
|
- `ALLOW_CROSS_ARCH=0`
|
||||||
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`, which mounts an empty fallback directory by default; set it to a real host directory such as `/root/rpki/report` to ingest reports from a separately deployed RTR service
|
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`, which mounts an empty fallback directory by default; after deploying a separate RTR service, register its same-host report directory through `scripts/register_rtr_monitor.sh`
|
||||||
|
|
||||||
## First Start Semantics
|
## First Start Semantics
|
||||||
|
|
||||||
@@ -117,14 +117,13 @@ Change the password and restrict public access for production deployments.
|
|||||||
|
|
||||||
## Optional RTR report metrics input
|
## Optional RTR report metrics input
|
||||||
|
|
||||||
If a separately deployed RTR service on the same host continuously writes `rtr-source-*`, `rtr-runtime-*`, and `rtr-clients-*` JSON reports, configure:
|
If a separately deployed RTR service on the same host continuously writes `rtr-source-*`, `rtr-runtime-*`, and `rtr-clients-*` JSON reports, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
RTR_REPORT_DIR=/root/rpki/report
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||||
RTR_REPORT_CONTAINER_DIR=/var/lib/ours-rp/rtr-report
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `artifact-metrics` container mounts the host directory read-only and reads it through `RPKI_METRICS_RTR_REPORT_DIR`, producing `ours_rp_rtr_*` metrics. By default, `RTR_REPORT_DIR` points at an empty fallback directory under the installer data root, so the metrics container also starts cleanly without an RTR service.
|
The command accepts only an absolute path on the same host, validates the candidate Compose mount, and atomically updates `.env`. It recreates only `artifact-metrics`, Prometheus, and Grafana; it does not restart or recreate `ours-rp-soak`. The `artifact-metrics` container mounts the directory read-only and reads it through `RPKI_METRICS_RTR_REPORT_DIR`, producing `ours_rp_rtr_*` metrics. By default, `RTR_REPORT_DIR` points at an empty fallback directory under the installer data root, so the metrics container also starts cleanly without an RTR service.
|
||||||
|
|
||||||
## Data Directory
|
## Data Directory
|
||||||
|
|
||||||
@@ -172,12 +171,12 @@ Check the latest `run-meta.json` for:
|
|||||||
## Common Commands
|
## Common Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./status.sh
|
./scripts/status.sh
|
||||||
./logs.sh ours-rp-soak --tail 200
|
./scripts/logs.sh ours-rp-soak --tail 200
|
||||||
./restart.sh
|
./scripts/restart.sh
|
||||||
./stop.sh
|
./scripts/stop.sh
|
||||||
./cleanup.sh --keep-runs 100 --execute
|
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||||
./uninstall.sh
|
./scripts/uninstall.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
For finite acceptance tests, for example `MAX_RUNS=3`, also set:
|
For finite acceptance tests, for example `MAX_RUNS=3`, also set:
|
||||||
@@ -191,5 +190,5 @@ Otherwise Compose `unless-stopped` will restart the container after it exits suc
|
|||||||
`uninstall.sh` keeps data by default. Use the following only when you really want to delete `HOST_DATA_DIR`:
|
`uninstall.sh` keeps data by default. Use the following only when you really want to delete `HOST_DATA_DIR`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./uninstall.sh --purge-data
|
./scripts/uninstall.sh --purge-data
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
安装包内置与包架构一致的四类镜像:ours RP runtime、artifact metrics、Prometheus、Grafana。部署时不需要现场拉取应用镜像。运行产物、状态数据库、日志、Prometheus 和 Grafana 数据均通过宿主机目录挂载保存。
|
安装包内置与包架构一致的四类镜像:ours RP runtime、artifact metrics、Prometheus、Grafana。部署时不需要现场拉取应用镜像。运行产物、状态数据库、日志、Prometheus 和 Grafana 数据均通过宿主机目录挂载保存。
|
||||||
|
|
||||||
每次构建安装包都会重新构建 ours RP runtime 与 artifact metrics 镜像。交付包命名为 `ours-rp-installer-{arch}-{git8}-{YYYYMMDDTHHMMSSZ}.tar.gz`,两类 ours RP 镜像分别使用当前源码 commit 的 tag。完整 commit、UTC 构建时间、dirty 状态和镜像归档 hash 保存在 `PACKAGE-MANIFEST.env`,可通过 `./status.sh --brief` 查看。
|
每次构建安装包都会重新构建 ours RP runtime 与 artifact metrics 镜像。交付包命名为 `ours-rp-installer-{arch}-{git8}-{YYYYMMDDTHHMMSSZ}.tar.gz`,两类 ours RP 镜像分别使用当前源码 commit 的 tag。完整 commit、UTC 构建时间、dirty 状态和镜像归档 hash 保存在 `PACKAGE-MANIFEST.env`,可通过 `./scripts/status.sh --brief` 查看。
|
||||||
|
|
||||||
默认情况下,宿主架构必须与 `PACKAGE-MANIFEST.env` 中的 `PACKAGE_ARCH` 匹配;若确需在异构主机上通过 QEMU/binfmt 运行,必须显式设置:
|
默认情况下,宿主架构必须与 `PACKAGE-MANIFEST.env` 中的 `PACKAGE_ARCH` 匹配;若确需在异构主机上通过 QEMU/binfmt 运行,必须显式设置:
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ ALLOW_CROSS_ARCH=1
|
|||||||
|
|
||||||
`compose/` 是唯一的生产 Docker Compose 模板来源,Docker 安装包构建脚本会直接复制该目录。不要在其他目录维护第二份 compose、Prometheus 或 Grafana dashboard 文件。
|
`compose/` 是唯一的生产 Docker Compose 模板来源,Docker 安装包构建脚本会直接复制该目录。不要在其他目录维护第二份 compose、Prometheus 或 Grafana dashboard 文件。
|
||||||
|
|
||||||
旧的直接 Compose 部署入口已经移除。需要部署 amd64 或 arm64 时,统一先用 `scripts/docker/build_docker_installer_package.sh` 构建对应架构安装包,再按本说明执行 `install.sh`、`start.sh` 和 `status.sh`。
|
旧的直接 Compose 部署入口已经移除。需要部署 amd64 或 arm64 时,统一先用 `scripts/docker/build_docker_installer_package.sh` 构建对应架构安装包,再按本说明执行 `scripts/install.sh`、`scripts/start.sh` 和 `scripts/status.sh`。
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ ALLOW_CROSS_ARCH=1
|
|||||||
tar -xzf ours-rp-installer-__PACKAGE_ARCH__-*.tar.gz
|
tar -xzf ours-rp-installer-__PACKAGE_ARCH__-*.tar.gz
|
||||||
cd ours-rp-installer-__PACKAGE_ARCH__-*
|
cd ours-rp-installer-__PACKAGE_ARCH__-*
|
||||||
|
|
||||||
./install.sh
|
./scripts/install.sh
|
||||||
cp .env.example .env # 如 install.sh 已自动创建,可直接编辑现有 .env
|
cp .env.example .env # 如 install.sh 已自动创建,可直接编辑现有 .env
|
||||||
vim .env
|
vim .env
|
||||||
./start.sh
|
./scripts/start.sh
|
||||||
./status.sh
|
./scripts/status.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
默认配置:
|
默认配置:
|
||||||
@@ -52,7 +52,7 @@ vim .env
|
|||||||
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
- `ALLOW_CROSS_ARCH=0`
|
- `ALLOW_CROSS_ARCH=0`
|
||||||
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`,默认挂载空目录;如需接入独立 RTR 服务 report,可设置为宿主机真实目录,例如 `/root/rpki/report`
|
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`,默认挂载空目录;独立 RTR 服务部署完成后,使用 `scripts/register_rtr_monitor.sh` 注册同机 report 目录
|
||||||
|
|
||||||
## 首次启动语义
|
## 首次启动语义
|
||||||
|
|
||||||
@@ -117,14 +117,13 @@ GRAFANA_ADMIN_PASSWORD=admin
|
|||||||
|
|
||||||
## 可选 RTR report 监控接入
|
## 可选 RTR report 监控接入
|
||||||
|
|
||||||
如果同一台机器上已经有独立部署的 RTR 服务,并且它持续输出 `rtr-source-*`、`rtr-runtime-*`、`rtr-clients-*` JSON report,可在 `.env` 中配置:
|
如果同一台机器上已经有独立部署的 RTR 服务,并且它持续输出 `rtr-source-*`、`rtr-runtime-*`、`rtr-clients-*` JSON report,执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
RTR_REPORT_DIR=/root/rpki/report
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||||
RTR_REPORT_CONTAINER_DIR=/var/lib/ours-rp/rtr-report
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`artifact-metrics` 容器会以只读方式挂载该目录,并通过 `RPKI_METRICS_RTR_REPORT_DIR` 读取 report,输出 `ours_rp_rtr_*` 指标。默认 `RTR_REPORT_DIR` 指向安装包数据目录下的空 fallback 目录,因此未接入 RTR 服务时 metrics 容器也能稳定启动。
|
该命令只接受本机绝对路径,会先校验候选 Compose 挂载,再原子更新 `.env`。随后只重建 `artifact-metrics`、Prometheus 和 Grafana,不重启或重建 `ours-rp-soak`。`artifact-metrics` 以只读方式挂载该目录,并通过 `RPKI_METRICS_RTR_REPORT_DIR` 读取 report,输出 `ours_rp_rtr_*` 指标。默认 `RTR_REPORT_DIR` 指向安装包数据目录下的空 fallback 目录,因此未接入 RTR 服务时 metrics 容器也能稳定启动。
|
||||||
|
|
||||||
## 数据目录
|
## 数据目录
|
||||||
|
|
||||||
@@ -172,12 +171,12 @@ PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
|||||||
## 常用命令
|
## 常用命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./status.sh
|
./scripts/status.sh
|
||||||
./logs.sh ours-rp-soak --tail 200
|
./scripts/logs.sh ours-rp-soak --tail 200
|
||||||
./restart.sh
|
./scripts/restart.sh
|
||||||
./stop.sh
|
./scripts/stop.sh
|
||||||
./cleanup.sh --keep-runs 100 --execute
|
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||||
./uninstall.sh
|
./scripts/uninstall.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
如果做有限轮次验收,例如 `MAX_RUNS=3`,建议同时设置:
|
如果做有限轮次验收,例如 `MAX_RUNS=3`,建议同时设置:
|
||||||
@@ -191,7 +190,7 @@ SOAK_RESTART_POLICY=no
|
|||||||
`uninstall.sh` 默认不删除数据。只有显式执行:
|
`uninstall.sh` 默认不删除数据。只有显式执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./uninstall.sh --purge-data
|
./scripts/uninstall.sh --purge-data
|
||||||
```
|
```
|
||||||
|
|
||||||
才会删除 `HOST_DATA_DIR`。
|
才会删除 `HOST_DATA_DIR`。
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./install.sh
|
./scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer is idempotent:
|
The installer is idempotent:
|
||||||
@@ -17,13 +17,13 @@ The installer is idempotent:
|
|||||||
## Start
|
## Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh
|
./scripts/start.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Start without waiting for the first snapshot:
|
Start without waiting for the first snapshot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh --no-wait-first-run
|
./scripts/start.sh --no-wait-first-run
|
||||||
```
|
```
|
||||||
|
|
||||||
If you intentionally want to run this package on a different host architecture through QEMU/binfmt, first set:
|
If you intentionally want to run this package on a different host architecture through QEMU/binfmt, first set:
|
||||||
@@ -32,20 +32,20 @@ If you intentionally want to run this package on a different host architecture t
|
|||||||
ALLOW_CROSS_ARCH=1
|
ALLOW_CROSS_ARCH=1
|
||||||
```
|
```
|
||||||
|
|
||||||
Then rerun `./install.sh` or `./self-check.sh`, and only then will the scripts attempt to enable the matching binfmt handler.
|
Then rerun `./scripts/install.sh` or `./scripts/self-check.sh`, and only then will the scripts attempt to enable the matching binfmt handler.
|
||||||
|
|
||||||
## Stop and Restart
|
## Stop and Restart
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./stop.sh
|
./scripts/stop.sh
|
||||||
./restart.sh
|
./scripts/restart.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Status Checks
|
## Status Checks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./status.sh
|
./scripts/status.sh
|
||||||
./self-check.sh
|
./scripts/self-check.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Important checks:
|
Important checks:
|
||||||
@@ -73,15 +73,20 @@ Important checks:
|
|||||||
To verify RTR report ingestion:
|
To verify RTR report ingestion:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||||
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||||
|
./scripts/status.sh
|
||||||
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Registration accepts only an absolute directory on the same host. The command retains a timestamped `.env` backup and updates only the metrics, Prometheus, and Grafana sidecars; the `ours-rp-soak` container ID must remain unchanged.
|
||||||
|
|
||||||
## Upgrade
|
## Upgrade
|
||||||
|
|
||||||
Extract the new package into a new directory and explicitly reuse the existing `.env` through the upgrade script:
|
Extract the new package into a new directory and explicitly reuse the existing `.env` through the upgrade script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
./scripts/upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
If the new package directory already has a `.env`, the upgrade script keeps it.
|
If the new package directory already has a `.env`, the upgrade script keeps it.
|
||||||
@@ -125,8 +130,8 @@ PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
|||||||
## Cleanup
|
## Cleanup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cleanup.sh --keep-runs 100
|
./scripts/cleanup.sh --keep-runs 100
|
||||||
./cleanup.sh --keep-runs 100 --execute
|
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||||
```
|
```
|
||||||
|
|
||||||
Cleanup is dry-run by default. Add `--execute` after reviewing the output.
|
Cleanup is dry-run by default. Add `--execute` after reviewing the output.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./install.sh
|
./scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
安装脚本是幂等的:
|
安装脚本是幂等的:
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
## 启动
|
## 启动
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh
|
./scripts/start.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
如需后台启动后不等待首轮 snapshot:
|
如需后台启动后不等待首轮 snapshot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh --no-wait-first-run
|
./scripts/start.sh --no-wait-first-run
|
||||||
```
|
```
|
||||||
|
|
||||||
如需显式允许异构主机通过 QEMU/binfmt 跑包,先在 `.env` 中设置:
|
如需显式允许异构主机通过 QEMU/binfmt 跑包,先在 `.env` 中设置:
|
||||||
@@ -32,20 +32,20 @@
|
|||||||
ALLOW_CROSS_ARCH=1
|
ALLOW_CROSS_ARCH=1
|
||||||
```
|
```
|
||||||
|
|
||||||
然后重新执行 `./install.sh` 或 `./self-check.sh`,脚本才会尝试启用对应 binfmt。
|
然后重新执行 `./scripts/install.sh` 或 `./scripts/self-check.sh`,脚本才会尝试启用对应 binfmt。
|
||||||
|
|
||||||
## 停止和重启
|
## 停止和重启
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./stop.sh
|
./scripts/stop.sh
|
||||||
./restart.sh
|
./scripts/restart.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 状态检查
|
## 状态检查
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./status.sh
|
./scripts/status.sh
|
||||||
./self-check.sh
|
./scripts/self-check.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
重点检查项:
|
重点检查项:
|
||||||
@@ -73,15 +73,20 @@ ALLOW_CROSS_ARCH=1
|
|||||||
如需确认 RTR report 已接入:
|
如需确认 RTR report 已接入:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||||
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||||
|
./scripts/status.sh
|
||||||
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
||||||
```
|
```
|
||||||
|
|
||||||
|
注册只支持同机的绝对目录。注册命令会保留一份带时间戳的 `.env` 备份,并且只更新 metrics、Prometheus、Grafana 三个旁路容器;`ours-rp-soak` 的容器 ID 应保持不变。
|
||||||
|
|
||||||
## 升级
|
## 升级
|
||||||
|
|
||||||
把新安装包解压到新目录后,推荐通过升级脚本显式复用旧 `.env`:
|
把新安装包解压到新目录后,推荐通过升级脚本显式复用旧 `.env`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
./scripts/upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
如果新目录已经存在 `.env`,升级脚本会保留它,不覆盖。
|
如果新目录已经存在 `.env`,升级脚本会保留它,不覆盖。
|
||||||
@@ -125,8 +130,8 @@ PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
|||||||
## 清理
|
## 清理
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cleanup.sh --keep-runs 100
|
./scripts/cleanup.sh --keep-runs 100
|
||||||
./cleanup.sh --keep-runs 100 --execute
|
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||||
```
|
```
|
||||||
|
|
||||||
默认 dry-run,确认后加 `--execute`。
|
默认 dry-run,确认后加 `--execute`。
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ docker compose version
|
|||||||
If missing, run:
|
If missing, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./install.sh
|
./scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Package Architecture Mismatch
|
## Package Architecture Mismatch
|
||||||
@@ -55,11 +55,11 @@ docker run --rm --platform __PACKAGE_PLATFORM__ \
|
|||||||
|
|
||||||
## RTR report metrics are empty
|
## RTR report metrics are empty
|
||||||
|
|
||||||
Check `.env`:
|
First check registration and the report directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
grep '^RTR_REPORT_DIR=' .env
|
./scripts/status.sh
|
||||||
test -d "$(grep '^RTR_REPORT_DIR=' .env | cut -d= -f2-)" && echo ok
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
Check the metrics container mount:
|
Check the metrics container mount:
|
||||||
@@ -74,7 +74,7 @@ docker inspect "$(grep '^COMPOSE_PROJECT_NAME=' .env | cut -d= -f2-)-artifact-me
|
|||||||
All-five snapshot can be slow, especially under QEMU. Increase timeout:
|
All-five snapshot can be slow, especially under QEMU. Increase timeout:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh --timeout-secs 14400
|
./scripts/start.sh --timeout-secs 14400
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output Counts Are Too Low
|
## Output Counts Are Too Low
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ docker compose version
|
|||||||
如果缺失,重新执行:
|
如果缺失,重新执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./install.sh
|
./scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 架构不匹配报错
|
## 架构不匹配报错
|
||||||
@@ -55,11 +55,11 @@ docker run --rm --platform __PACKAGE_PLATFORM__ \
|
|||||||
|
|
||||||
## RTR report 指标为空
|
## RTR report 指标为空
|
||||||
|
|
||||||
检查 `.env`:
|
先检查注册状态和 report 目录:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
grep '^RTR_REPORT_DIR=' .env
|
./scripts/status.sh
|
||||||
test -d "$(grep '^RTR_REPORT_DIR=' .env | cut -d= -f2-)" && echo ok
|
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
检查 metrics 容器是否挂载该目录:
|
检查 metrics 容器是否挂载该目录:
|
||||||
@@ -74,7 +74,7 @@ docker inspect "$(grep '^COMPOSE_PROJECT_NAME=' .env | cut -d= -f2-)-artifact-me
|
|||||||
all5 snapshot 可能很慢,尤其在 QEMU 环境。可以提高超时:
|
all5 snapshot 可能很慢,尤其在 QEMU 环境。可以提高超时:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh --timeout-secs 14400
|
./scripts/start.sh --timeout-secs 14400
|
||||||
```
|
```
|
||||||
|
|
||||||
## 产物数量异常偏低
|
## 产物数量异常偏低
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
DRY_RUN=1
|
DRY_RUN=1
|
||||||
KEEP_RUNS=""
|
KEEP_RUNS=""
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
Usage: ./cleanup.sh [--execute] [--keep-runs N]
|
Usage: ./scripts/cleanup.sh [--execute] [--keep-runs N]
|
||||||
|
|
||||||
By default this is a dry-run. It removes old run_* directories beyond KEEP_RUNS
|
By default this is a dry-run. It removes old run_* directories beyond KEEP_RUNS
|
||||||
and clears tmp contents.
|
and clears tmp contents.
|
||||||
@@ -224,6 +224,82 @@ validate_rtr_report_dir() {
|
|||||||
[[ -d "$RTR_REPORT_DIR" ]] || die "RTR_REPORT_DIR does not exist or is not a directory: $RTR_REPORT_DIR"
|
[[ -d "$RTR_REPORT_DIR" ]] || die "RTR_REPORT_DIR does not exist or is not a directory: $RTR_REPORT_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_rtr_registration_dir() {
|
||||||
|
local report_dir="$1"
|
||||||
|
|
||||||
|
[[ -n "$report_dir" ]] || die "--report-dir is required"
|
||||||
|
[[ "$report_dir" = /* ]] || die "--report-dir must be an absolute host path: $report_dir"
|
||||||
|
[[ "$report_dir" =~ ^/[A-Za-z0-9._/@:+,-]+$ ]] || die "--report-dir contains unsupported characters: $report_dir"
|
||||||
|
[[ -d "$report_dir" ]] || die "--report-dir does not exist: $report_dir"
|
||||||
|
[[ -r "$report_dir" ]] || die "--report-dir is not readable: $report_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
rtr_report_file_count() {
|
||||||
|
local report_dir="$1"
|
||||||
|
{
|
||||||
|
find "$report_dir" -maxdepth 1 -type f \( \
|
||||||
|
-name 'rtr-source-*.json' -o \
|
||||||
|
-name 'rtr-runtime-*.json' -o \
|
||||||
|
-name 'rtr-clients-*.json' \
|
||||||
|
\) -print 2>/dev/null || true
|
||||||
|
} | wc -l | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
latest_rtr_report_file() {
|
||||||
|
local report_dir="$1"
|
||||||
|
{
|
||||||
|
find "$report_dir" -maxdepth 1 -type f \( \
|
||||||
|
-name 'rtr-source-*.json' -o \
|
||||||
|
-name 'rtr-runtime-*.json' -o \
|
||||||
|
-name 'rtr-clients-*.json' \
|
||||||
|
\) -printf '%T@ %p\n' 2>/dev/null || true
|
||||||
|
} | sort -nr | awk 'NR == 1 { sub(/^[^ ]+ /, ""); print }'
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_env_key_file() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local temp_path
|
||||||
|
|
||||||
|
[[ "$key" =~ ^[A-Z][A-Z0-9_]*$ ]] || die "invalid environment key: $key"
|
||||||
|
[[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "environment value must be single-line"
|
||||||
|
|
||||||
|
temp_path="$(mktemp "${env_path}.tmp.XXXXXX")"
|
||||||
|
awk -v key="$key" -v value="$value" '
|
||||||
|
BEGIN { replaced = 0 }
|
||||||
|
$0 ~ "^" key "=" {
|
||||||
|
print key "=" value
|
||||||
|
replaced = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!replaced) {
|
||||||
|
print key "=" value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$env_path" > "$temp_path"
|
||||||
|
chmod --reference="$env_path" "$temp_path" 2>/dev/null || true
|
||||||
|
mv "$temp_path" "$env_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_endpoint() {
|
||||||
|
local url="$1"
|
||||||
|
local timeout_secs="$2"
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while (( elapsed < timeout_secs )); do
|
||||||
|
if endpoint_ok "$url"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
elapsed=$((elapsed + 2))
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
create_data_dirs() {
|
create_data_dirs() {
|
||||||
load_env
|
load_env
|
||||||
mkdir -p \
|
mkdir -p \
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
Usage: ./install.sh [--skip-dep-install]
|
Usage: ./scripts/install.sh [--skip-dep-install]
|
||||||
|
|
||||||
Install or update the ours RP Docker installer package idempotently.
|
Install or update the ours RP Docker installer package idempotently.
|
||||||
USAGE
|
USAGE
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
load_env
|
load_env
|
||||||
compose_cmd --profile core --profile sidecar --profile monitor logs "$@"
|
compose_cmd --profile core --profile sidecar --profile monitor logs "$@"
|
||||||
149
deploy/docker-installer/scripts/register_rtr_monitor.sh
Executable file
149
deploy/docker-installer/scripts/register_rtr_monitor.sh
Executable file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
REPORT_DIR=""
|
||||||
|
DRY_RUN=0
|
||||||
|
CANDIDATE_ENV=""
|
||||||
|
CONFIG_FILE=""
|
||||||
|
ORIGINAL_ENV_FILE=""
|
||||||
|
ENV_BACKUP=""
|
||||||
|
REGISTRATION_APPLIED=0
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: ./scripts/register_rtr_monitor.sh --report-dir <absolute-host-path> [--dry-run]
|
||||||
|
|
||||||
|
Registers one local RTR report directory for the artifact metrics sidecar.
|
||||||
|
The directory must be on the same host as this installer and may contain
|
||||||
|
rtr-source-*.json, rtr-runtime-*.json, and rtr-clients-*.json report files.
|
||||||
|
|
||||||
|
This command recreates only artifact-metrics, Prometheus, and Grafana. It does
|
||||||
|
not restart or recreate the ours-rp-soak container.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[[ -n "$CANDIDATE_ENV" && -f "$CANDIDATE_ENV" ]] && rm -f "$CANDIDATE_ENV"
|
||||||
|
[[ -n "$CONFIG_FILE" && -f "$CONFIG_FILE" ]] && rm -f "$CONFIG_FILE"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
restore_failed_registration() {
|
||||||
|
if (( REGISTRATION_APPLIED == 0 )) || [[ -z "$ENV_BACKUP" || ! -f "$ENV_BACKUP" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
warn "registration failed after .env replacement; restoring previous metrics configuration"
|
||||||
|
cp -p "$ENV_BACKUP" "$ORIGINAL_ENV_FILE"
|
||||||
|
ENV_FILE="$ORIGINAL_ENV_FILE"
|
||||||
|
load_env || warn "unable to reload restored .env"
|
||||||
|
compose_cmd --profile sidecar up -d --force-recreate artifact-metrics \
|
||||||
|
|| warn "unable to recreate artifact-metrics with restored configuration"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_after_apply() {
|
||||||
|
restore_failed_registration
|
||||||
|
die "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
--report-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--report-dir requires a value"
|
||||||
|
REPORT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown argument: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
require_cmd docker
|
||||||
|
require_cmd curl
|
||||||
|
require_cmd grep
|
||||||
|
require_cmd jq
|
||||||
|
validate_rtr_registration_dir "$REPORT_DIR"
|
||||||
|
load_env
|
||||||
|
assert_arch_compatibility
|
||||||
|
|
||||||
|
ORIGINAL_ENV_FILE="$ENV_FILE"
|
||||||
|
REPORT_FILE_COUNT="$(rtr_report_file_count "$REPORT_DIR")"
|
||||||
|
LATEST_REPORT_FILE="$(latest_rtr_report_file "$REPORT_DIR")"
|
||||||
|
if (( REPORT_FILE_COUNT == 0 )); then
|
||||||
|
warn "no recognized RTR report JSON files are currently present in $REPORT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CANDIDATE_ENV="$(mktemp "$INSTALLER_ROOT/.env.rtr-register.XXXXXX")"
|
||||||
|
cp -p "$ORIGINAL_ENV_FILE" "$CANDIDATE_ENV"
|
||||||
|
replace_env_key_file "$CANDIDATE_ENV" "RTR_REPORT_DIR" "$REPORT_DIR"
|
||||||
|
|
||||||
|
ENV_FILE="$CANDIDATE_ENV"
|
||||||
|
load_env
|
||||||
|
CONFIG_FILE="$(mktemp "$INSTALLER_ROOT/.rtr-compose-config.XXXXXX")"
|
||||||
|
compose_cmd --profile sidecar --profile monitor config --format json > "$CONFIG_FILE"
|
||||||
|
jq -e --arg source "$REPORT_DIR" --arg target "$RTR_REPORT_CONTAINER_DIR" '
|
||||||
|
.services["artifact-metrics"].volumes
|
||||||
|
| any(.[]; .type == "bind" and .source == $source and .target == $target and .read_only == true)
|
||||||
|
' "$CONFIG_FILE" >/dev/null || die "candidate compose configuration is missing expected read-only RTR bind mount"
|
||||||
|
|
||||||
|
if (( DRY_RUN == 1 )); then
|
||||||
|
printf 'dry_run=ok\n'
|
||||||
|
printf 'report_dir=%s\n' "$REPORT_DIR"
|
||||||
|
printf 'recognized_report_files=%s\n' "$REPORT_FILE_COUNT"
|
||||||
|
printf 'latest_report=%s\n' "${LATEST_REPORT_FILE:--}"
|
||||||
|
printf 'expected_mount_source=%s\n' "$REPORT_DIR"
|
||||||
|
printf 'expected_mount_target=%s\n' "$RTR_REPORT_CONTAINER_DIR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENV_FILE="$ORIGINAL_ENV_FILE"
|
||||||
|
load_env
|
||||||
|
soak_container_before="$(compose_cmd ps -q ours-rp-soak || true)"
|
||||||
|
ENV_BACKUP="${ORIGINAL_ENV_FILE}.rtr-register-$(date -u +%Y%m%dT%H%M%SZ).bak"
|
||||||
|
cp -p "$ORIGINAL_ENV_FILE" "$ENV_BACKUP"
|
||||||
|
mv "$CANDIDATE_ENV" "$ORIGINAL_ENV_FILE"
|
||||||
|
CANDIDATE_ENV=""
|
||||||
|
REGISTRATION_APPLIED=1
|
||||||
|
|
||||||
|
load_env
|
||||||
|
compose_cmd --profile sidecar up -d --force-recreate artifact-metrics \
|
||||||
|
|| fail_after_apply "unable to recreate artifact-metrics"
|
||||||
|
compose_cmd --profile sidecar --profile monitor up -d prometheus grafana \
|
||||||
|
|| fail_after_apply "unable to start Prometheus and Grafana"
|
||||||
|
|
||||||
|
soak_container_after="$(compose_cmd ps -q ours-rp-soak || true)"
|
||||||
|
if [[ -n "$soak_container_before" && "$soak_container_before" != "$soak_container_after" ]]; then
|
||||||
|
fail_after_apply "ours-rp-soak container changed during RTR registration; investigate before continuing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_endpoint "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" 60 \
|
||||||
|
|| fail_after_apply "artifact-metrics did not become ready"
|
||||||
|
wait_for_endpoint "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" 60 \
|
||||||
|
|| fail_after_apply "Prometheus did not become ready"
|
||||||
|
wait_for_endpoint "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" 60 \
|
||||||
|
|| fail_after_apply "Grafana did not become ready"
|
||||||
|
|
||||||
|
if (( REPORT_FILE_COUNT > 0 )); then
|
||||||
|
curl -fsS "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep -q '^ours_rp_rtr_' \
|
||||||
|
|| fail_after_apply "artifact-metrics exposes no RTR metrics after registration"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'registration=ok\n'
|
||||||
|
printf 'report_dir=%s\n' "$REPORT_DIR"
|
||||||
|
printf 'recognized_report_files=%s\n' "$REPORT_FILE_COUNT"
|
||||||
|
printf 'latest_report=%s\n' "${LATEST_REPORT_FILE:--}"
|
||||||
|
printf 'env_backup=%s\n' "$ENV_BACKUP"
|
||||||
|
printf 'ours_rp_soak_container_unchanged=%s\n' "${soak_container_before:-not-running}"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
QUICK=0
|
QUICK=0
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@@ -12,7 +12,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "Usage: ./self-check.sh [--quick]"
|
echo "Usage: ./scripts/self-check.sh [--quick]"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
WAIT_FIRST_RUN=1
|
WAIT_FIRST_RUN=1
|
||||||
TIMEOUT_SECS=""
|
TIMEOUT_SECS=""
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
Usage: ./start.sh [--no-wait-first-run] [--timeout-secs N]
|
Usage: ./scripts/start.sh [--no-wait-first-run] [--timeout-secs N]
|
||||||
|
|
||||||
Start ours RP. If no successful run exists, wait for the first snapshot to succeed
|
Start ours RP. If no successful run exists, wait for the first snapshot to succeed
|
||||||
before starting metrics, Prometheus and Grafana.
|
before starting metrics, Prometheus and Grafana.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
BRIEF=0
|
BRIEF=0
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@@ -12,7 +12,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "Usage: ./status.sh [--brief]"
|
echo "Usage: ./scripts/status.sh [--brief]"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -76,4 +76,12 @@ if [[ "$BRIEF" == "0" ]]; then
|
|||||||
endpoint_ok "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" && echo "metrics=ok" || echo "metrics=unavailable"
|
endpoint_ok "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" && echo "metrics=ok" || echo "metrics=unavailable"
|
||||||
endpoint_ok "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" && echo "prometheus=ok" || echo "prometheus=unavailable"
|
endpoint_ok "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" && echo "prometheus=ok" || echo "prometheus=unavailable"
|
||||||
endpoint_ok "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" && echo "grafana=ok" || echo "grafana=unavailable"
|
endpoint_ok "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" && echo "grafana=ok" || echo "grafana=unavailable"
|
||||||
|
echo
|
||||||
|
if [[ "$RTR_REPORT_DIR" == "$HOST_DATA_DIR/empty-rtr-report" ]]; then
|
||||||
|
echo "rtr_registration=fallback-empty-directory"
|
||||||
|
else
|
||||||
|
echo "rtr_registration=registered"
|
||||||
|
fi
|
||||||
|
echo "rtr_report_files=$(rtr_report_file_count "$RTR_REPORT_DIR")"
|
||||||
|
echo "rtr_latest_report=$(latest_rtr_report_file "$RTR_REPORT_DIR" || true)"
|
||||||
fi
|
fi
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
load_env
|
load_env
|
||||||
compose_cmd --profile core --profile sidecar --profile monitor stop "$@"
|
compose_cmd --profile core --profile sidecar --profile monitor stop "$@"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
PURGE_DATA=0
|
PURGE_DATA=0
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@@ -12,7 +12,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "Usage: ./uninstall.sh [--purge-data]"
|
echo "Usage: ./scripts/uninstall.sh [--purge-data]"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# shellcheck source=scripts/common.sh
|
# shellcheck source=common.sh
|
||||||
source "$SCRIPT_DIR/scripts/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
REUSE_ENV_FROM=""
|
REUSE_ENV_FROM=""
|
||||||
UPDATE_PACKAGE_IMAGE=1
|
UPDATE_PACKAGE_IMAGE=1
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
Usage: ./upgrade.sh [--reuse-env-from /path/to/.env] [--keep-reused-image]
|
Usage: ./scripts/upgrade.sh [--reuse-env-from /path/to/.env] [--keep-reused-image]
|
||||||
|
|
||||||
By default, --reuse-env-from creates the new .env from this package's
|
By default, --reuse-env-from creates the new .env from this package's
|
||||||
.env.example first, then overlays existing user settings from the old .env.
|
.env.example first, then overlays existing user settings from the old .env.
|
||||||
146
scripts/docker/build_custom_tal_patch.sh
Normal file
146
scripts/docker/build_custom_tal_patch.sh
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$BASH_SOURCE")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
PATCH_SOURCE="$REPO_ROOT/deploy/docker-installer/custom-tal-patch"
|
||||||
|
BASE_COMPONENT_PACKAGE=""
|
||||||
|
OUT_DIR="$(printenv OUT_DIR 2>/dev/null || true)"
|
||||||
|
PREFIX="$(printenv PREFIX 2>/dev/null || true)"
|
||||||
|
ALLOW_DIRTY=0
|
||||||
|
[[ -n "$OUT_DIR" ]] || OUT_DIR="$REPO_ROOT/target/custom-tal-patch"
|
||||||
|
[[ -n "$PREFIX" ]] || PREFIX="ours-rp-custom-tal-patch"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
scripts/docker/build_custom_tal_patch.sh \
|
||||||
|
--base-component-package <ours-rp-installer-*.tar.gz> \
|
||||||
|
[--out-dir <dir>] [--prefix <name>] [--allow-dirty]
|
||||||
|
|
||||||
|
The patch targets the Arm64 component package from the customer stack. It
|
||||||
|
overlays the runner, Compose, fixture mount and test-service tools; it does not
|
||||||
|
rebuild or replace the runtime image.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "error: $*" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--base-component-package) BASE_COMPONENT_PACKAGE="$2"; shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--prefix) PREFIX="$2"; shift 2 ;;
|
||||||
|
--allow-dirty) ALLOW_DIRTY=1; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -f "$BASE_COMPONENT_PACKAGE" ]] || die "missing base component package: $BASE_COMPONENT_PACKAGE"
|
||||||
|
[[ -d "$PATCH_SOURCE" ]] || die "missing patch source: $PATCH_SOURCE"
|
||||||
|
|
||||||
|
SOURCE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD)"
|
||||||
|
SOURCE_COMMIT_SHORT="$(git -C "$REPO_ROOT" rev-parse --short=8 HEAD)"
|
||||||
|
SOURCE_DIRTY=false
|
||||||
|
if [[ -n "$(git -C "$REPO_ROOT" status --short)" ]]; then
|
||||||
|
SOURCE_DIRTY=true
|
||||||
|
fi
|
||||||
|
if [[ "$SOURCE_DIRTY" == true && "$ALLOW_DIRTY" != 1 ]]; then
|
||||||
|
die "source worktree is dirty; use --allow-dirty for a development patch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
package_root="$(tar -tzf "$BASE_COMPONENT_PACKAGE" | awk -F/ '!seen { print $1; seen = 1 }')"
|
||||||
|
[[ -n "$package_root" ]] || die "cannot determine component package root"
|
||||||
|
manifest_text="$(tar -xOf "$BASE_COMPONENT_PACKAGE" "$package_root/PACKAGE-MANIFEST.env")" \
|
||||||
|
|| die "base component package has no PACKAGE-MANIFEST.env"
|
||||||
|
read_manifest_value() {
|
||||||
|
printf '%s\n' "$manifest_text" | awk -F= -v key="$1" '$1 == key { print substr($0, index($0, "=") + 1); exit }'
|
||||||
|
}
|
||||||
|
base_source_commit="$(read_manifest_value source_commit)"
|
||||||
|
base_package_arch="$(read_manifest_value package_arch)"
|
||||||
|
base_runtime_image="$(read_manifest_value runtime_image)"
|
||||||
|
[[ "$base_source_commit" == 9f4f4cf069e9fa8f6f97ed0f1065adac38abe82f ]] \
|
||||||
|
|| die "base source commit mismatch: $base_source_commit"
|
||||||
|
[[ "$base_package_arch" == arm64 ]] || die "base package must be arm64: $base_package_arch"
|
||||||
|
|
||||||
|
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
patch_name="$PREFIX-$SOURCE_COMMIT_SHORT"
|
||||||
|
if [[ "$SOURCE_DIRTY" == true ]]; then
|
||||||
|
patch_name="$patch_name-dirty"
|
||||||
|
fi
|
||||||
|
patch_name="$patch_name-$timestamp"
|
||||||
|
stage="$OUT_DIR/$patch_name"
|
||||||
|
tar_path="$OUT_DIR/$patch_name.tar.gz"
|
||||||
|
base_sha256="$(sha256sum "$BASE_COMPONENT_PACKAGE" | awk '{ print $1 }')"
|
||||||
|
rm -rf "$stage"
|
||||||
|
mkdir -p "$stage/payload/compose" "$stage/payload/scripts/soak" \
|
||||||
|
"$stage/payload/custom-fixtures" "$stage/payload/tools"
|
||||||
|
|
||||||
|
cp "$PATCH_SOURCE/apply_custom_tal_patch.sh" "$stage/"
|
||||||
|
cp "$PATCH_SOURCE/rollback_custom_tal_patch.sh" "$stage/"
|
||||||
|
cp "$PATCH_SOURCE/custom-tal.env.example" "$stage/"
|
||||||
|
cp "$REPO_ROOT/deploy/docker-installer/compose/docker-compose.yml" "$stage/payload/compose/"
|
||||||
|
cp "$REPO_ROOT/scripts/soak/run_soak.sh" "$stage/payload/scripts/soak/"
|
||||||
|
cp "$REPO_ROOT/deploy/docker-installer/custom-fixtures/README.md" "$stage/payload/custom-fixtures/"
|
||||||
|
cp "$PATCH_SOURCE/tools/generate_custom_fixture.py" "$stage/payload/tools/"
|
||||||
|
cp "$PATCH_SOURCE/tools/serve_https.py" "$stage/payload/tools/"
|
||||||
|
cp "$PATCH_SOURCE/tools/start_fixed_services.sh" "$stage/payload/tools/"
|
||||||
|
cp "$PATCH_SOURCE/tools/stop_fixed_services.sh" "$stage/payload/tools/"
|
||||||
|
chmod +x "$stage/apply_custom_tal_patch.sh" "$stage/rollback_custom_tal_patch.sh" \
|
||||||
|
"$stage/payload/scripts/soak/run_soak.sh" "$stage/payload/tools/"*.sh \
|
||||||
|
"$stage/payload/tools/"*.py
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "patch_schema_version=1"
|
||||||
|
echo "patch_name=$patch_name"
|
||||||
|
echo "created_at_utc=$timestamp"
|
||||||
|
echo "source_commit=$SOURCE_COMMIT"
|
||||||
|
echo "source_commit_short=$SOURCE_COMMIT_SHORT"
|
||||||
|
echo "source_dirty=$SOURCE_DIRTY"
|
||||||
|
echo "base_component_archive=$(basename "$BASE_COMPONENT_PACKAGE")"
|
||||||
|
echo "base_component_archive_sha256=$base_sha256"
|
||||||
|
echo "base_component_root=$package_root"
|
||||||
|
echo "base_source_commit=$base_source_commit"
|
||||||
|
echo "base_package_arch=$base_package_arch"
|
||||||
|
echo "base_runtime_image=$base_runtime_image"
|
||||||
|
echo "overlay_runtime_image_unchanged=true"
|
||||||
|
echo "custom_mode=RIRS=custom,TAL_INPUT_MODE=custom-file-with-ta"
|
||||||
|
echo "rrdp_port=18443"
|
||||||
|
echo "rsync_port=1873"
|
||||||
|
echo "payload_root=payload"
|
||||||
|
while IFS= read -r relative_path; do
|
||||||
|
safe_name="$(printf '%s' "$relative_path" | tr '/.' '__')"
|
||||||
|
file_sha256="$(sha256sum "$stage/payload/$relative_path" | awk '{ print $1 }')"
|
||||||
|
echo "payload_"$safe_name"_sha256=$file_sha256"
|
||||||
|
done < <(cd "$stage/payload" && find . -type f -printf '%P\n' | sort)
|
||||||
|
} > "$stage/PATCH-MANIFEST.env"
|
||||||
|
|
||||||
|
cat > "$stage/PATCH-SUMMARY.txt" <<EOF
|
||||||
|
patch_name: $patch_name
|
||||||
|
base_component_archive: $(basename "$BASE_COMPONENT_PACKAGE")
|
||||||
|
base_component_archive_sha256: $base_sha256
|
||||||
|
base_source_commit: $base_source_commit
|
||||||
|
source_commit: $SOURCE_COMMIT
|
||||||
|
source_dirty: $SOURCE_DIRTY
|
||||||
|
runtime_image: $base_runtime_image
|
||||||
|
runtime_image_changed: false
|
||||||
|
custom_fixture_generator: payload/tools/generate_custom_fixture.py
|
||||||
|
fixed_services: payload/tools/start_fixed_services.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
tar -C "$OUT_DIR" -czf "$tar_path" "$patch_name"
|
||||||
|
patch_sha256="$(sha256sum "$tar_path" | awk '{ print $1 }')"
|
||||||
|
{
|
||||||
|
echo "patch=$tar_path"
|
||||||
|
echo "patch_dir=$stage"
|
||||||
|
echo "patch_sha256=$patch_sha256"
|
||||||
|
echo "manifest=$stage/PATCH-MANIFEST.env"
|
||||||
|
echo "base_component_archive_sha256=$base_sha256"
|
||||||
|
echo "source_commit=$SOURCE_COMMIT"
|
||||||
|
echo "source_dirty=$SOURCE_DIRTY"
|
||||||
|
} > "$OUT_DIR/$patch_name.summary.env"
|
||||||
|
echo "patch built: $tar_path"
|
||||||
@@ -437,7 +437,12 @@ cross_arch_default: deny
|
|||||||
cross_arch_enable_var: ALLOW_CROSS_ARCH
|
cross_arch_enable_var: ALLOW_CROSS_ARCH
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x "$stage"/*.sh "$stage/scripts"/*.sh
|
chmod +x "$stage/scripts"/*.sh
|
||||||
|
|
||||||
|
if find "$stage" -maxdepth 1 -type f -name '*.sh' -print -quit | grep -q .; then
|
||||||
|
echo "error: operational scripts must be installed under scripts/ only" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
tar -C "$OUT_DIR" -czf "$tar_path" "$package_name"
|
tar -C "$OUT_DIR" -czf "$tar_path" "$package_name"
|
||||||
package_sha256="$(sha256sum "$tar_path" | awk '{print $1}')"
|
package_sha256="$(sha256sum "$tar_path" | awk '{print $1}')"
|
||||||
rm -rf "$monitor_image_stage"
|
rm -rf "$monitor_image_stage"
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ INTERVAL_SECS="${INTERVAL_SECS:-0}"
|
|||||||
STOP_AFTER_SECS="${STOP_AFTER_SECS:-0}"
|
STOP_AFTER_SECS="${STOP_AFTER_SECS:-0}"
|
||||||
RIRS="${RIRS:-afrinic,apnic,arin,lacnic,ripe}"
|
RIRS="${RIRS:-afrinic,apnic,arin,lacnic,ripe}"
|
||||||
TAL_INPUT_MODE="${TAL_INPUT_MODE:-file-with-ta}"
|
TAL_INPUT_MODE="${TAL_INPUT_MODE:-file-with-ta}"
|
||||||
|
CUSTOM_TAL_PATH="${CUSTOM_TAL_PATH:-$PACKAGE_ROOT/custom-fixtures/tal/custom.tal}"
|
||||||
|
CUSTOM_TA_PATH="${CUSTOM_TA_PATH:-$PACKAGE_ROOT/custom-fixtures/ta/custom-ta.cer}"
|
||||||
|
CUSTOM_TAL_URI="${CUSTOM_TAL_URI:-https://host.docker.internal:18443/tal/custom.tal}"
|
||||||
|
HTTP_ROOT_CERT_PATHS="${HTTP_ROOT_CERT_PATHS:-}"
|
||||||
RESOURCE_VALIDATION_MODE="${RESOURCE_VALIDATION_MODE:-validation-update-03}"
|
RESOURCE_VALIDATION_MODE="${RESOURCE_VALIDATION_MODE:-validation-update-03}"
|
||||||
RUN_ROOT="${RUN_ROOT:-$PACKAGE_ROOT}"
|
RUN_ROOT="${RUN_ROOT:-$PACKAGE_ROOT}"
|
||||||
RETAIN_RUNS="${RETAIN_RUNS:-10}"
|
RETAIN_RUNS="${RETAIN_RUNS:-10}"
|
||||||
@@ -118,10 +122,10 @@ validate_rsync_scope() {
|
|||||||
|
|
||||||
validate_tal_input_mode() {
|
validate_tal_input_mode() {
|
||||||
case "$TAL_INPUT_MODE" in
|
case "$TAL_INPUT_MODE" in
|
||||||
file-with-ta|file-live-ta|url)
|
file-with-ta|file-live-ta|custom-file-with-ta|url)
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
die "TAL_INPUT_MODE must be file-with-ta, file-live-ta or url: $TAL_INPUT_MODE"
|
die "TAL_INPUT_MODE must be file-with-ta, file-live-ta, custom-file-with-ta or url: $TAL_INPUT_MODE"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -155,14 +159,46 @@ parse_rirs() {
|
|||||||
afrinic|apnic|arin|lacnic|ripe)
|
afrinic|apnic|arin|lacnic|ripe)
|
||||||
RIR_LIST+=("$normalized")
|
RIR_LIST+=("$normalized")
|
||||||
;;
|
;;
|
||||||
|
custom)
|
||||||
|
[[ "$TAL_INPUT_MODE" == "custom-file-with-ta" ]] \
|
||||||
|
|| die "RIRS=custom requires TAL_INPUT_MODE=custom-file-with-ta"
|
||||||
|
RIR_LIST+=("$normalized")
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
die "invalid RIRS entry: $raw_token; allowed: afrinic,apnic,arin,lacnic,ripe"
|
die "invalid RIRS entry: $raw_token; allowed: afrinic,apnic,arin,lacnic,ripe,custom (custom mode only)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[[ "${#RIR_LIST[@]}" -gt 0 ]] || die "RIRS must contain at least one RIR"
|
[[ "${#RIR_LIST[@]}" -gt 0 ]] || die "RIRS must contain at least one RIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_custom_inputs() {
|
||||||
|
local root_cert_path
|
||||||
|
local -a root_cert_paths=()
|
||||||
|
|
||||||
|
if [[ "$TAL_INPUT_MODE" == "custom-file-with-ta" ]]; then
|
||||||
|
[[ "${#RIR_LIST[@]}" -eq 1 && "${RIR_LIST[0]}" == "custom" ]] \
|
||||||
|
|| die "custom-file-with-ta requires exactly RIRS=custom"
|
||||||
|
[[ -s "$CUSTOM_TAL_PATH" ]] \
|
||||||
|
|| die "missing custom TAL: $CUSTOM_TAL_PATH"
|
||||||
|
[[ -s "$CUSTOM_TA_PATH" ]] \
|
||||||
|
|| die "missing custom TA DER: $CUSTOM_TA_PATH"
|
||||||
|
[[ -n "$CUSTOM_TAL_URI" ]] \
|
||||||
|
|| die "CUSTOM_TAL_URI is required in custom-file-with-ta mode"
|
||||||
|
elif [[ "$RIRS" == *custom* ]]; then
|
||||||
|
die "RIRS=custom requires TAL_INPUT_MODE=custom-file-with-ta"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$HTTP_ROOT_CERT_PATHS" ]]; then
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
root_cert_paths=( $HTTP_ROOT_CERT_PATHS )
|
||||||
|
for root_cert_path in "${root_cert_paths[@]}"; do
|
||||||
|
[[ -s "$root_cert_path" ]] \
|
||||||
|
|| die "missing HTTP root certificate: $root_cert_path"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
tal_file_for_rir() {
|
tal_file_for_rir() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
afrinic) printf '%s' "$FIXTURE_DIR/tal/afrinic.tal" ;;
|
afrinic) printf '%s' "$FIXTURE_DIR/tal/afrinic.tal" ;;
|
||||||
@@ -170,6 +206,7 @@ tal_file_for_rir() {
|
|||||||
arin) printf '%s' "$FIXTURE_DIR/tal/arin.tal" ;;
|
arin) printf '%s' "$FIXTURE_DIR/tal/arin.tal" ;;
|
||||||
lacnic) printf '%s' "$FIXTURE_DIR/tal/lacnic.tal" ;;
|
lacnic) printf '%s' "$FIXTURE_DIR/tal/lacnic.tal" ;;
|
||||||
ripe) printf '%s' "$FIXTURE_DIR/tal/ripe-ncc.tal" ;;
|
ripe) printf '%s' "$FIXTURE_DIR/tal/ripe-ncc.tal" ;;
|
||||||
|
custom) printf '%s' "$CUSTOM_TAL_PATH" ;;
|
||||||
*) die "unknown RIR: $1" ;;
|
*) die "unknown RIR: $1" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -181,6 +218,7 @@ ta_file_for_rir() {
|
|||||||
arin) printf '%s' "$FIXTURE_DIR/ta/arin-ta.cer" ;;
|
arin) printf '%s' "$FIXTURE_DIR/ta/arin-ta.cer" ;;
|
||||||
lacnic) printf '%s' "$FIXTURE_DIR/ta/lacnic-ta.cer" ;;
|
lacnic) printf '%s' "$FIXTURE_DIR/ta/lacnic-ta.cer" ;;
|
||||||
ripe) printf '%s' "$FIXTURE_DIR/ta/ripe-ncc-ta.cer" ;;
|
ripe) printf '%s' "$FIXTURE_DIR/ta/ripe-ncc-ta.cer" ;;
|
||||||
|
custom) printf '%s' "$CUSTOM_TA_PATH" ;;
|
||||||
*) die "unknown RIR: $1" ;;
|
*) die "unknown RIR: $1" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -192,6 +230,7 @@ tal_url_for_rir() {
|
|||||||
arin) printf '%s' "https://www.arin.net/resources/manage/rpki/arin.tal" ;;
|
arin) printf '%s' "https://www.arin.net/resources/manage/rpki/arin.tal" ;;
|
||||||
lacnic) printf '%s' "https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal" ;;
|
lacnic) printf '%s' "https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal" ;;
|
||||||
ripe) printf '%s' "https://tal.rpki.ripe.net/ripe-ncc.tal" ;;
|
ripe) printf '%s' "https://tal.rpki.ripe.net/ripe-ncc.tal" ;;
|
||||||
|
custom) printf '%s' "$CUSTOM_TAL_URI" ;;
|
||||||
*) die "unknown RIR: $1" ;;
|
*) die "unknown RIR: $1" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -1057,6 +1096,16 @@ build_child_args() {
|
|||||||
CHILD_ARGS+=(--rsync-mirror-root "$TMP_DIR/rsync-mirror-{run_id}")
|
CHILD_ARGS+=(--rsync-mirror-root "$TMP_DIR/rsync-mirror-{run_id}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
local root_cert_path
|
||||||
|
local -a root_cert_paths=()
|
||||||
|
if [[ -n "$HTTP_ROOT_CERT_PATHS" ]]; then
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
root_cert_paths=( $HTTP_ROOT_CERT_PATHS )
|
||||||
|
for root_cert_path in "${root_cert_paths[@]}"; do
|
||||||
|
CHILD_ARGS+=(--http-root-cert "$root_cert_path")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
CHILD_ARGS+=(
|
CHILD_ARGS+=(
|
||||||
--parallel-phase2-ready-batch-size 256
|
--parallel-phase2-ready-batch-size 256
|
||||||
--parallel-phase2-ready-batch-wall-time-budget-ms 100
|
--parallel-phase2-ready-batch-wall-time-budget-ms 100
|
||||||
@@ -1344,6 +1393,7 @@ main() {
|
|||||||
validate_positive_int "DB_STATS_EXACT_EVERY" "$DB_STATS_EXACT_EVERY"
|
validate_positive_int "DB_STATS_EXACT_EVERY" "$DB_STATS_EXACT_EVERY"
|
||||||
fi
|
fi
|
||||||
parse_rirs
|
parse_rirs
|
||||||
|
validate_custom_inputs
|
||||||
[[ -x "$RPKI_BIN" ]] || die "missing executable: $RPKI_BIN"
|
[[ -x "$RPKI_BIN" ]] || die "missing executable: $RPKI_BIN"
|
||||||
[[ -x "$RPKI_DAEMON_BIN" ]] || die "missing executable: $RPKI_DAEMON_BIN"
|
[[ -x "$RPKI_DAEMON_BIN" ]] || die "missing executable: $RPKI_DAEMON_BIN"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user