Skip to content

Commit d23bd97

Browse files
author
root
committed
feat(toolkit-security): add exporter endpoint ACL hardening for toolkit installs
- add perfSONAR-configure-exporter-acls.sh to enforce Apache CIDR allow-lists on /node_exporter/metrics and /perfsonar_host_exporter/ - add --exporter-allowlist to perfSONAR-toolkit-install.sh and wire it into step_security - include new helper in install_tools_scripts bootstrap list - update toolkit docs/README with hardening guidance and examples - refresh scripts.sha256 for updated and new helper scripts
1 parent c481569 commit d23bd97

File tree

6 files changed

+233
-5
lines changed

6 files changed

+233
-5
lines changed

docs/perfsonar/tools_scripts/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ for your site — they are not interchangeable:
2727
| **install_tools_scripts.sh** || Bulk installer for all scripts | [Installation](#installation) |
2828
| **install-systemd-service.sh** || Container auto-start on boot | [Container Management](#container-management) |
2929
| **perfSONAR-install-flowd-go.sh** | v1.1.0 | SciTags flowd-go installer | [SciTags & Fireflies](../scitags-fireflies.md) |
30+
| **perfSONAR-configure-exporter-acls.sh** | v0.1.0 | Restrict exporter endpoints to monitoring CIDRs | [RPM toolkit deployment](#rpm-toolkit-installer) |
3031

3132
**Latest Updates**: v1.0.0 (Feb 2026) adds `perfSONAR-toolkit-install.sh` for RPM-based toolkit deployments.
3233

@@ -75,6 +76,11 @@ Differences from the container orchestrator:
7576
curl -fsSL https://raw.githubusercontent.com/osg-htc/networking/master/docs/perfsonar/tools_scripts/perfSONAR-toolkit-install.sh \
7677
| sudo bash -s -- --experiment-id 1 --non-interactive
7778

79+
# Restrict exporter endpoints to monitoring CIDRs
80+
curl -fsSL https://raw.githubusercontent.com/osg-htc/networking/master/docs/perfsonar/tools_scripts/perfSONAR-toolkit-install.sh \
81+
| sudo bash -s -- --experiment-id 1 --non-interactive \
82+
--exporter-allowlist "192.41.230.0/23,192.41.236.0/23,2001:48a8:68f7::/50"
83+
7884
# With Let's Encrypt
7985
curl -fsSL https://raw.githubusercontent.com/osg-htc/networking/master/docs/perfsonar/tools_scripts/perfSONAR-toolkit-install.sh \
8086
| sudo bash -s -- --fqdn ps.example.org --email admin@example.org \
@@ -86,7 +92,7 @@ curl -fsSL https://raw.githubusercontent.com/osg-htc/networking/master/docs/perf
8692
```
8793

8894
Flags: `--bundle {toolkit|testpoint|core|tools}`, `--fqdn`, `--email`, `--experiment-id N`,
89-
`--no-flowd-go`, `--non-interactive`, `--yes`, `--dry-run`
95+
`--no-flowd-go`, `--exporter-allowlist "CIDR1,CIDR2,..."`, `--non-interactive`, `--yes`, `--dry-run`
9096

9197
> **RHEL 9 note**: The perfSONAR automated install script (`downloads.perfsonar.net/install`)
9298
> does not enable CodeReady Builder on Satellite-managed RHEL systems.

docs/perfsonar/tools_scripts/install_tools_scripts.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ set -euo pipefail
55
# Purpose: Ensure the perfSONAR testpoint repository is cloned and the tools_scripts
66
# directory is present under /opt/perfsonar-tp/tools_scripts.
77
#
8+
# Version: 1.0.7 - 2026-03-02
9+
# - Add perfSONAR-configure-exporter-acls.sh to download list for toolkit/container exporter ACL hardening.
810
# Version: 1.0.6 - 2026-02-27
911
# - Add perfSONAR-install-flowd-go.sh to download list for SciTags/flowd-go integration.
1012
# Version: 1.0.5 - 2026-02-26
@@ -18,7 +20,7 @@ set -euo pipefail
1820
# Author: Shawn McKee, University of Michigan
1921
# Acknowledgements: Supported by IRIS-HEP and OSG-LHC
2022

21-
VERSION="1.0.6"
23+
VERSION="1.0.7"
2224
PROG_NAME="$(basename "$0")"
2325

2426
# Check for --version or --help flags
@@ -97,6 +99,7 @@ files=(
9799
perfSONAR-health-monitor.sh
98100
perfSONAR-diagnostic-report.sh
99101
perfSONAR-install-flowd-go.sh
102+
perfSONAR-configure-exporter-acls.sh
100103
seed_testpoint_host_dirs.sh
101104
update-perfsonar-deployment.sh
102105
perfSONAR-orchestrator.sh
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
VERSION="0.1.0"
5+
LOG_FILE="/var/log/perfSONAR-configure-exporter-acls.log"
6+
ACL_FILE="/etc/httpd/conf.d/apache-osg-exporter-restrictions.conf"
7+
ALLOWLIST=""
8+
DRY_RUN=false
9+
AUTO_YES=false
10+
11+
log() {
12+
local ts
13+
ts="$(date +'%Y-%m-%d %H:%M:%S')"
14+
echo "$ts $*" | tee -a "$LOG_FILE"
15+
}
16+
17+
usage() {
18+
cat <<'EOF'
19+
Usage: perfSONAR-configure-exporter-acls.sh --allowlist "CIDR1,CIDR2,..." [--yes] [--dry-run]
20+
21+
Restrict exporter endpoints exposed by Apache:
22+
- /node_exporter/metrics
23+
- /perfsonar_host_exporter/
24+
25+
Options:
26+
--allowlist CSV Comma-separated CIDRs/IPs allowed to access exporter endpoints
27+
--yes Skip confirmation prompt
28+
--dry-run Print actions without writing files
29+
--help, -h Show help
30+
EOF
31+
}
32+
33+
confirm() {
34+
if [ "$AUTO_YES" = true ]; then
35+
return 0
36+
fi
37+
read -r -p "Apply exporter ACL restrictions now? [y/N]: " ans
38+
case "${ans:-}" in
39+
y|Y|yes|YES) return 0 ;;
40+
*) log "Cancelled by user."; return 1 ;;
41+
esac
42+
}
43+
44+
need_root() {
45+
if [ "$(id -u)" -ne 0 ]; then
46+
echo "Run as root." >&2
47+
exit 1
48+
fi
49+
}
50+
51+
run() {
52+
log "CMD: $*"
53+
if [ "$DRY_RUN" = true ]; then
54+
echo "[DRY-RUN] $*"
55+
return 0
56+
fi
57+
"$@"
58+
}
59+
60+
is_valid_ip_or_cidr() {
61+
python3 - "$1" <<'PY'
62+
import ipaddress
63+
import sys
64+
value = sys.argv[1]
65+
try:
66+
if '/' in value:
67+
ipaddress.ip_network(value, strict=False)
68+
else:
69+
ipaddress.ip_address(value)
70+
except Exception:
71+
sys.exit(1)
72+
sys.exit(0)
73+
PY
74+
}
75+
76+
parse_cli() {
77+
while [[ $# -gt 0 ]]; do
78+
case "$1" in
79+
--allowlist) ALLOWLIST="${2:-}"; shift 2 ;;
80+
--yes) AUTO_YES=true; shift ;;
81+
--dry-run) DRY_RUN=true; shift ;;
82+
--help|-h) usage; exit 0 ;;
83+
*) echo "Unknown argument: $1" >&2; exit 2 ;;
84+
esac
85+
done
86+
}
87+
88+
render_require_lines() {
89+
local csv="$1"
90+
local item
91+
IFS=',' read -r -a entries <<< "$csv"
92+
93+
printf ' Require ip 127.0.0.1\n'
94+
printf ' Require ip ::1\n'
95+
96+
for item in "${entries[@]}"; do
97+
item="${item// /}"
98+
[ -z "$item" ] && continue
99+
if ! is_valid_ip_or_cidr "$item"; then
100+
echo "Invalid IP/CIDR in --allowlist: $item" >&2
101+
exit 3
102+
fi
103+
printf ' Require ip %s\n' "$item"
104+
done
105+
}
106+
107+
write_acl_file() {
108+
local tmp_file
109+
tmp_file="$(mktemp)"
110+
local require_lines
111+
require_lines="$(render_require_lines "$ALLOWLIST")"
112+
113+
cat > "$tmp_file" <<EOF
114+
# Managed by perfSONAR-configure-exporter-acls.sh v${VERSION}
115+
# Restricts exporter endpoints to explicit allow-list entries.
116+
117+
<Location /node_exporter/metrics>
118+
<RequireAny>
119+
${require_lines}
120+
</RequireAny>
121+
</Location>
122+
123+
<Location /perfsonar_host_exporter/>
124+
<RequireAny>
125+
${require_lines}
126+
</RequireAny>
127+
</Location>
128+
EOF
129+
130+
run mkdir -p "$(dirname "$ACL_FILE")"
131+
if [ -f "$ACL_FILE" ]; then
132+
run cp -a "$ACL_FILE" "${ACL_FILE}.bak.$(date +%s)"
133+
fi
134+
run cp "$tmp_file" "$ACL_FILE"
135+
run chmod 0644 "$ACL_FILE"
136+
rm -f "$tmp_file"
137+
}
138+
139+
verify_httpd_config() {
140+
if command -v apachectl >/dev/null 2>&1; then
141+
run apachectl configtest
142+
elif command -v httpd >/dev/null 2>&1; then
143+
run httpd -t
144+
else
145+
log "Apache config test command not found; skipping syntax validation."
146+
fi
147+
}
148+
149+
reload_httpd() {
150+
if command -v systemctl >/dev/null 2>&1; then
151+
run systemctl reload httpd || run systemctl reload apache2 || true
152+
fi
153+
}
154+
155+
main() {
156+
need_root
157+
parse_cli "$@"
158+
159+
if [ -z "$ALLOWLIST" ]; then
160+
echo "--allowlist is required" >&2
161+
usage
162+
exit 2
163+
fi
164+
165+
log "Starting exporter ACL configuration"
166+
log "Allow-list: $ALLOWLIST"
167+
168+
confirm || exit 0
169+
write_acl_file
170+
verify_httpd_config
171+
reload_httpd
172+
173+
log "Exporter ACL configuration complete."
174+
log "ACL file: $ACL_FILE"
175+
}
176+
177+
main "$@"

docs/perfsonar/tools_scripts/perfSONAR-toolkit-install.sh

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
3-
# Version: 1.0.2
3+
# Version: 1.0.3
44
# Author: Shawn McKee, University of Michigan
55
# Acknowledgements: Supported by IRIS-HEP and OSG-LHC
66

@@ -34,6 +34,8 @@ set -euo pipefail
3434
# --experiment-id N SciTags experiment ID for flowd-go (1-14; interactive prompt if omitted)
3535
# --no-firefly-receiver Disable fireflyp plugin in flowd-go config (use with flowd-go 2.4.x RPM)
3636
# Requires flowd-go >= 2.5.0; omit with current 2.4.2 RPM to avoid errors
37+
# --exporter-allowlist Comma-separated CIDRs/IPs allowed to access exporter endpoints
38+
# (/node_exporter/metrics and /perfsonar_host_exporter/)
3739
#
3840
# Log:
3941
# /var/log/perfsonar-toolkit-install.log
@@ -45,6 +47,7 @@ NON_INTERACTIVE=false
4547
INSTALL_FLOWD_GO=true
4648
FLOWD_GO_EXPERIMENT_ID=""
4749
NO_FIREFLY_RECEIVER=false
50+
EXPORTER_ALLOWLIST=""
4851
BUNDLE="toolkit"
4952
LE_FQDN=""
5053
LE_EMAIL=""
@@ -100,6 +103,7 @@ parse_cli() {
100103
--no-flowd-go) INSTALL_FLOWD_GO=false; shift;;
101104
--experiment-id) FLOWD_GO_EXPERIMENT_ID="${2:-}"; shift 2;;
102105
--no-firefly-receiver) NO_FIREFLY_RECEIVER=true; shift;;
106+
--exporter-allowlist) EXPORTER_ALLOWLIST="${2:-}"; shift 2;;
103107
--help|-h) sed -n '1,80p' "$0"; exit 0;;
104108
*) echo "Unknown argument: $1" >&2; exit 2;;
105109
esac
@@ -238,6 +242,19 @@ step_security() {
238242
sec_cmd+=(--perf-ports 80)
239243
fi
240244
run "${sec_cmd[@]}" || true
245+
246+
if [ -n "$EXPORTER_ALLOWLIST" ]; then
247+
log "Applying exporter endpoint ACLs to Apache using allow-list: $EXPORTER_ALLOWLIST"
248+
if [ -x "$HELPER_DIR/tools_scripts/perfSONAR-configure-exporter-acls.sh" ]; then
249+
run "$HELPER_DIR/tools_scripts/perfSONAR-configure-exporter-acls.sh" \
250+
--allowlist "$EXPORTER_ALLOWLIST" --yes || true
251+
run systemctl reload httpd || run systemctl reload apache2 || true
252+
else
253+
log "WARNING: perfSONAR-configure-exporter-acls.sh not found; skipping exporter ACL configuration."
254+
fi
255+
else
256+
log "Exporter ACL hardening not requested (use --exporter-allowlist to restrict exporter endpoints)."
257+
fi
241258
}
242259

243260
# ---------------------------------------------------------------------------

docs/perfsonar/tools_scripts/scripts.sha256

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ bedd3dc88debd85605613922d3b35d4b25f5655edfb824e71aacb3d6a9924dc0 repair-state-j
1616
e1944123e17c89e8f202cca960f147397d64ae1e675af132c84b02ced2564abb node_exporter.defaults
1717
76f49ce6e5ee00a0f35026ee6b87b44448355549fe78b3b0873b49bbece1ccf1 testpoint-entrypoint-wrapper.sh
1818
50dfab90bc21d5c566b713f48b00b079a32a8b8756432a0d0f66ac6a64e6e581 perfSONAR-health-monitor.sh
19-
d8e3cc4a03725c7fb6e12c13e5036e7c6a3af301a1f1e728690ce9ad3ab8aa96 install_tools_scripts.sh
19+
e184c633ca9ca5d3d79321213843a4eaa951e5596d3dc05f082e5f76e860e580 install_tools_scripts.sh
2020
7be726de5dfdbe8f7f5ac8e803b0b71e8f98f1ba274ca70b42f8eba4822cc67b perfSONAR-orchestrator.sh
21-
eea859a611996827d36f852a052c1cb48adac969888110b902d3dd00f53ca10f perfSONAR-toolkit-install.sh
21+
42abd64fffbb2b45731bfe2d6bc14e144bdd8b2b127f612afc8058c9527f4687 perfSONAR-toolkit-install.sh
22+
cd0e7afd1ca7a20a972e585018802be0cb6f67cd3ffee449dea45d785c23145a perfSONAR-configure-exporter-acls.sh
2223
2615a29d65e285391adb547046584c4534ea548e69571b67e0cf35773b010c57 perfSONAR-diagnostic-report.sh
2324
ac0c8fd6f27cc156ec05c7e6ac3547e0732f436a7033dac34475ece5641a284f docs/perfsonar/tools_scripts/perfSONAR-install-flowd-go.sh
2425
39d226a857eb1a0956003c75ca8b558fcb55c63176286ca9597f031d08cb38a7 docs/perfsonar/tools_scripts/update-perfsonar-deployment.sh

docs/personas/quick-deploy/install-perfsonar-toolkit.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ Installation takes approximately 5-10 minutes depending on network speed.
175175
| sudo bash -s -- --experiment-id 1 --non-interactive
176176
```
177177
178+
To harden exporter endpoints with explicit monitoring subnets at install time:
179+
```bash
180+
curl -fsSL https://raw.githubusercontent.com/osg-htc/networking/master/docs/perfsonar/tools_scripts/perfSONAR-toolkit-install.sh \
181+
| sudo bash -s -- --experiment-id 1 --non-interactive \
182+
--exporter-allowlist "192.41.230.0/23,192.41.236.0/23,2001:48a8:68f7::/50"
183+
```
184+
178185
See the [tools_scripts README](../../perfsonar/tools_scripts/README.md) for full
179186
flag documentation and the pre-installation checklist.
180187
@@ -479,6 +486,23 @@ You can use the install script to install the options you want (selinux, fail2ba
479486
The script writes nftables rules for perfSONAR services, derives SSH allow-lists from `/etc/perfSONAR-multi-nic-
480487
config.conf`, optionally adjusts SELinux, and enables Fail2ban jails—only if those components are already installed.
481488
489+
### Optional: Restrict exporter endpoints to monitoring subnets
490+
491+
By default, perfSONAR toolkit Apache configs expose both exporter URLs to any client that can reach HTTPS:
492+
493+
- `/node_exporter/metrics`
494+
- `/perfsonar_host_exporter/`
495+
496+
If you want container-style subnet ACL protection for these endpoints, apply explicit allow-lists:
497+
498+
```bash
499+
/opt/perfsonar-tp/tools_scripts/perfSONAR-configure-exporter-acls.sh \
500+
--allowlist "192.41.230.0/23,192.41.236.0/23,2001:48a8:68f7::/50,2001:1458:d00::/48" --yes
501+
```
502+
503+
If your site keeps helper scripts under `/opt/perfsonar-toolkit/tools_scripts`, use that path instead.
504+
This writes `/etc/httpd/conf.d/apache-osg-exporter-restrictions.conf` and reloads Apache.
505+
482506
??? info "SSH allow-lists and validation"
483507
484508
- **Auto-detects current SSH clients:** The script captures the IP address of your current SSH connection (via `$SSH_CONNECTION`) and active SSH connections (via `ss`) to ensure your SSH access is not interrupted when the firewall is applied.

0 commit comments

Comments
 (0)