-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart-node2.sh
More file actions
executable file
·269 lines (252 loc) · 11.5 KB
/
Copy pathstart-node2.sh
File metadata and controls
executable file
·269 lines (252 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env bash
# Configuration C (mTLS + OAuth 2.0) — TR-10-SEC §12.3 RAAM=2. Second node.
#
# The peer of start-node1.sh: same argument contract, same policy matrix, same
# flag surface, with SNX00002 substituted throughout. The two differ only in
# serial, port and node configuration — so a two-node rig exercises the same
# security posture at both ends of a cross-node route, rather than a secured
# node talking to a simplified one.
#
# IPMX security validator launch contract:
# start-node2.sh <as-host> <as-port> [<rds-host> <rds-port>] \
# [--nap=N] [--rap=R] [--oaim=O] [--tct=T]
#
# Positional args:
# $1 = OAuth 2.0 authorization server host (default: XYZ-SNX00000)
# $2 = OAuth 2.0 authorization server port (default: 9443)
# $3 = Registry host (default: 127.0.0.1)
# $4 = Registry registration port (default: 8444; query port = $4-1)
#
# Named args:
# --nap=N Node Access Policy. Config C pins NAP=2 per §9.2.
# --rap=R Registry Access Policy: 0=HTTP, 1=server-TLS, 2=mTLS.
# --oaim=O OAuth2 Audience ID Mode: 0=serial, 1=cert, 2=either.
# --tct=T TLS Cert Type: 0=RSA (default), 1=ECDSA, 2=both (presents whichever each client asks for)
#
# Requires hosts-file entries. This script addresses its peers by DNS name
# because the certificates carry DNS SANs (XYZ-SNX000nn) and an IP literal
# matches none of them. Map to 127.0.0.1 in /etc/hosts before running:
#
# 127.0.0.1 XYZ-SNX00000 # registry + Authorization Server
# 127.0.0.1 XYZ-SNX00001 # node 1 + Controller UI
# 127.0.0.1 XYZ-SNX00002 # node 2
#
# Passing 127.0.0.1 as the registry-host argument fails TLS verification for
# the same reason -- pass XYZ-SNX00000.
#
# On tokens: an Authorization Server scopes a token to the devices named in
# its `aud` claim, so a token minted for SNX00001 alone is rejected here — and
# the Controller reports that rather than failing at the first click. That is
# what `tutorial-security` demonstrates, and it needs both nodes running.
set -e
# Positional arguments are consumed only while they do not look like an option.
# Taking them by index instead meant `start-node3.sh --rap=2` landed in the first
# positional and was then shifted away: the flag looked accepted and changed
# nothing, so a rig meant to be RAP=2 ran as RAP=0 without a word.
POSITIONAL=()
while [ $# -gt 0 ] && [ "${#POSITIONAL[@]}" -lt 4 ]; do
case "$1" in
--*) break ;;
*) POSITIONAL+=("$1"); shift ;;
esac
done
AS_HOST="${POSITIONAL[0]:-XYZ-SNX00000}"
AS_PORT="${POSITIONAL[1]:-9443}"
RDS_HOST="${POSITIONAL[2]:-127.0.0.1}"
RDS_REG_PORT="${POSITIONAL[3]:-8444}"
# Ports arrive on the command line, and arithmetic is no defence: $(( )) treats
# a bare name as a variable and re-evaluates its VALUE as an expression, so a
# non-numeric port becomes 0 and a derived port -1 -- which argparse then
# accepts as a perfectly good int, leaving the failure to surface much later as
# a bind error with nothing pointing back here. Check the value itself, with a
# minimum that leaves room for the ports derived from it.
require_port() {
case "$2" in
''|*[!0-9]*)
echo "$(basename "$0"): $1 must be a whole number, got '$2'" >&2
exit 64 ;;
esac
if [ "$2" -lt "$3" ] || [ "$2" -gt "$4" ]; then
echo "$(basename "$0"): $1 must be between $3 and $4, got '$2'" >&2
exit 64
fi
}
if [ -n "${AS_PORT:-}" ]; then
require_port "<as-port>" "$AS_PORT" 1 65535
fi
# The query port is one below this one, so 1 would leave nothing below it.
require_port "<rds-registration-port>" "$RDS_REG_PORT" 2 65535
RDS_QUERY_PORT=$((RDS_REG_PORT - 1))
NAP=2
RAP=0
OAIM=0
TCT=0
for arg in "$@"; do
case "$arg" in
--nap=*) NAP="${arg#*=}" ;;
--rap=*) RAP="${arg#*=}" ;;
--oaim=*) OAIM="${arg#*=}" ;;
--tct=*) TCT="${arg#*=}" ;;
*) echo "start-node2.sh: unknown arg $arg" >&2; exit 64 ;;
esac
done
if [ "$NAP" != "2" ]; then
echo "start-node2.sh: Config C (RAAM=2) pins NAP=2; got --nap=$NAP" >&2
exit 64
fi
# Every remaining argument is settled here, before the script touches the
# filesystem. These checks used to live in the same `case` statements that
# built the certificate paths, further down, which put them after the
# certificate probe: on a checkout that could not resolve a PKI, `--tct=9`
# answered "missing ExampleRootCA.pem" and exited 66 (EX_NOINPUT) rather than
# naming the bad argument and exiting 64 (EX_USAGE). Each value is now decided
# once, into a token that names the choice without naming a path; the paths are
# built from the tokens once the certificates resolve.
# "" for RSA, ".ec" for ECDSA -- the infix this PKI uses for the ECDSA
# generation of an identity. TCT=2 selects the RSA certificate as TCT=0 does;
# it differs elsewhere, not in which file the Node presents.
# One infix per identity the Node presents. TR-10-SEC gives TCT=2 as "Both":
# the listener then holds an RSA and an ECDSA identity at once and serves each
# client whichever its ClientHello can verify. TCT=2 selects the RSA
# certificate as TCT=0 does *and* the ECDSA one; it is not a third flavour.
case "$TCT" in
0) TCT_INFIXES=("") ;;
1) TCT_INFIXES=(".ec") ;;
2) TCT_INFIXES=("" ".ec") ;;
*) echo "start-node2.sh: unsupported --tct=$TCT" >&2; exit 64 ;;
esac
# Never depended on the certificates at all; it is here to keep all argument
# validation in one place rather than half of it either side of the probe.
case "$OAIM" in
0) OAIM_FLAG="serial" ;;
1) OAIM_FLAG="cert" ;;
2) OAIM_FLAG="either" ;;
*) echo "start-node2.sh: unsupported --oaim=$OAIM" >&2; exit 64 ;;
esac
# How the Node presents itself to the Registration API. Named rather than left
# as the digit, so the block that turns it into flags below is exhaustive over
# values this script chose itself instead of re-listing what the operator may
# type -- one place to add a mode, not two.
case "$RAP" in
0) RDS_MODE="plaintext" ;;
1) RDS_MODE="server-tls" ;;
2) RDS_MODE="mutual-tls" ;;
*) echo "start-node2.sh: unsupported --rap=$RAP" >&2; exit 64 ;;
esac
# Cert directory resolution — override IPMX_CERT_ROOT to point at a
# different `Certificates/` layout. Default: this repository's own
# Certificates/ tree.
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# Certificates come from the subset bundled inside this repository, so a
# standalone clone runs the whole rig with no wider workspace: SNX00000 is the
# infrastructure serial (registry + Authorization Server) and SNX00001..
# SNX00003 are the Nodes.
#
# Resolution order: IPMX_CERT_ROOT, then this checkout, then the workspace
# tree one level up. That last step is what lets the IPMX security test suite
# drive this launcher against a PKI carrying serials this repository does not
# ship, so it stays -- but it announces itself, because the silent version of
# it hid a missing serial through an entire 3-node bring-up. Matching nothing
# anywhere is a hard error naming every directory searched.
CERT_PROBE="pem/ExampleDeviceServer.ABC.SNX00002.chain.pem"
if [ -n "${IPMX_CERT_ROOT:-}" ]; then
CERT_ROOT="$IPMX_CERT_ROOT"
elif [ -f "$SCRIPT_DIR/Certificates/build.0/$CERT_PROBE" ]; then
CERT_ROOT="$SCRIPT_DIR/Certificates"
elif [ -f "$SCRIPT_DIR/../Certificates/build.0/$CERT_PROBE" ]; then
CERT_ROOT="$SCRIPT_DIR/../Certificates"
echo "$(basename "$0"): $CERT_PROBE is not in this checkout — using the" \
"workspace PKI at $CERT_ROOT" >&2
else
echo "$(basename "$0"): missing build.0/$CERT_PROBE" >&2
echo " Searched $SCRIPT_DIR/Certificates and $SCRIPT_DIR/../Certificates." >&2
echo " Set IPMX_CERT_ROOT to a Certificates/ tree that carries it." >&2
exit 66
fi
CERTS="$CERT_ROOT/build.0"
# Trust follows the certificate type, like the identities below: TR-10-SEC
# §12.5 makes the TCT "common to all certificates and Root CAs of the
# device", so a TCT=0 Node trusts the RSA root only, a TCT=1 Node the ECDSA
# root only, and only a TCT=2 Node both.
CA_ROOTS=()
for infix in "${TCT_INFIXES[@]}"; do
CA_ROOTS+=("$CERTS/ExampleRootCA${infix}.pem")
done
for root in "${CA_ROOTS[@]}"; do
if [ ! -f "$root" ]; then
echo "$(basename "$0"): missing $root" >&2
echo " Set IPMX_CERT_ROOT to a Certificates/ tree that carries it." >&2
exit 66
fi
done
CA="${CA_ROOTS[0]}"
if [ "${#CA_ROOTS[@]}" -gt 1 ]; then
# TCT=2: one file holding both roots -- the RSA and the ECDSA generation of
# the same CA -- so either certificate flavour validates against a single
# --trustedRootCA. It ships in Certificates/ next to the two roots it is
# built from, rather than being written to a scratch path at every start-up.
CA="$CERTS/ExampleRootCA-bundle.pem"
if [ ! -f "$CA" ]; then
# A PKI supplied from outside this checkout -- IPMX_CERT_ROOT, or the
# workspace tree the IPMX security test suite drives these launchers with --
# carries the two roots but not the combined file, so derive it from them.
# mktemp rather than a fixed path: /tmp/ExampleRootCA-bundle.pem used to be
# shared by every launcher and rewritten on each start-up.
CA="$(mktemp -t ExampleRootCA-bundle.XXXXXX)"
cat "${CA_ROOTS[@]}" > "$CA"
fi
fi
# $TCT was validated above; one --nodeCertificate/--nodeKey pair per
# identity. Repeating the flags is how TCT=2 reaches the listener --
# see nmos/tls_identity.py for why one context holds them all.
NODE_CERT_ARGS=()
for infix in "${TCT_INFIXES[@]}"; do
NODE_CERT_ARGS+=(--nodeCertificate "$CERTS/pem/ExampleDeviceServer.ABC.SNX00002.chain${infix}.pem")
NODE_CERT_ARGS+=(--nodeKey "$CERTS/key/ExampleDeviceServer.ABC.SNX00002${infix}.key")
done
# The client identities follow the same infixes: TR-10-SEC applies the
# certificate type "to both endpoint and client accesses, and to server and
# client certificates" (§11), so a TCT=1 Node authenticates with its
# ECDSA certificate and a TCT=2 Node holds both -- one pair per identity, as
# for the listener above.
NODE_CLIENT_ARGS=()
RDS_CLIENT_ARGS=()
for infix in "${TCT_INFIXES[@]}"; do
NODE_CLIENT_ARGS+=(--nodeClientCertificate "$CERTS/pem/ExampleDeviceClient.ABC.SNX00002.chain${infix}.pem")
NODE_CLIENT_ARGS+=(--nodeClientKey "$CERTS/key/ExampleDeviceClient.ABC.SNX00002${infix}.key")
RDS_CLIENT_ARGS+=(--rdsClientCertificate "$CERTS/pem/ExampleDeviceClient.ABC.SNX00002.chain${infix}.pem")
RDS_CLIENT_ARGS+=(--rdsClientKey "$CERTS/key/ExampleDeviceClient.ABC.SNX00002${infix}.key")
done
case "$RDS_MODE" in
plaintext) RDS_FLAGS=(--rdsDisableTLS) ;;
server-tls) RDS_FLAGS=() ;;
mutual-tls) RDS_FLAGS=("${RDS_CLIENT_ARGS[@]}") ;;
esac
# --nodeControlPort is deliberately absent, unlike node1. The Controller is a
# single-instance affordance in this rig: node1 serves it on 5050 and shows
# every node it discovers through the registry, this one included. A second
# Controller would work but would present the same devices twice and give the
# tutorials two URLs to explain. Add --nodeControlPort 5060 and
# --controllerAdminPassword here if you want one.
exec python3 nmos_node.py \
--nodeSerialNumber SNX00002 \
--nodeAddr XYZ-SNX00002 \
--nodePort 7052 \
"${NODE_CERT_ARGS[@]}" \
--nodeTrustedRootCA "$CA" \
"${NODE_CLIENT_ARGS[@]}" \
--oauth2 \
--oauth2Host "${AS_HOST}" \
--oauth2Port "${AS_PORT}" \
--oauth2TrustedRootCA "$CA" \
--oauth2ClientSecret secret \
--oauth2ApiSelector realms/TR-10-SEC \
--oauth2AudienceMode "${OAIM_FLAG}" \
--oauth2ClientId Example.Company.Device.Client.ABC.SNX00002.example.com \
--rdsHost "${RDS_HOST}" \
--rdsRegistrationPort "${RDS_REG_PORT}" \
--rdsQueryPort "${RDS_QUERY_PORT}" \
"${RDS_FLAGS[@]}" \
--trustedRootCA "$CA" \
--debug-in-depth \
--nodeConfig config_av_usb_tb_A