Skip to content

Commit 52ef7a7

Browse files
committed
Include parameter to indicate endpoint
This change allows specifying -e option, so that endpoint parameter is used. This parameter gives the possibility to change URL where Tang listens, so that endpoint provided is prepended between host port information and advertisement/recovery suffix (rec/adv). Without endpoint, advertisement URL is: http://localhost:port/adv Meanwhile, if using endpoint (-e this/is/endpoint), advertisement URL is: http://localhost:port/this/is/endpoint/adv For more information, check man page Resolves: #116 Signed-off-by: Sergio Arroutbi <[email protected]>
1 parent df3cc46 commit 52ef7a7

13 files changed

+256
-42
lines changed

doc/tang.8.adoc

+21
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,27 @@ be changed with the *-p* option.
7171

7272
tang -l -p 9090
7373

74+
== ENDPOINT
75+
76+
The Tang server can be provided an endpoint. This endpoint will act as a prefix
77+
for the URL to be accessed by the client. This endpoint can be specified with
78+
the *-e* option.
79+
80+
tang -l -p 9090 -e this/is/an/endpoint
81+
82+
When endpoint is specified, the endpoint will be prepended to the normal adv/rec
83+
URL. If no endpoint is provided, and assuming port 9090 is used, Tang server
84+
will listen on next URLs:
85+
86+
http://localhost:9090/adv (GET)
87+
http://localhost:9090/rec (POST)
88+
89+
If endpoint is provided, and assuming endpoint is /this/is/an/endpoint/, and
90+
assuming also port 9090 is used, Tang server will listen on next URLs:
91+
92+
http://localhost:9090/this/is/an/endpoint/adv (GET)
93+
http://localhost:9090/this/is/an/endpoint/rec (POST)
94+
7495
== KEY ROTATION
7596

7697
In order to preserve the security of the system over the long run, you need to

src/socket.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ static int listen_port(socket_list **slist, int port)
146146
return r;
147147
}
148148

149-
static void spawn_process(int fd, const char *jwkdir,
149+
static void spawn_process(int fd, const char* jwkdir,
150150
process_request_func pfunc,
151151
socket_list *slist)
152152
{
@@ -166,7 +166,6 @@ static void spawn_process(int fd, const char *jwkdir,
166166
}
167167

168168
close(fd);
169-
170169
pfunc(jwkdir, STDOUT_FILENO);
171170
free_socket_list(slist);
172171
exit(0);
@@ -233,7 +232,6 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
233232
perror("accept");
234233
continue;
235234
}
236-
237235
spawn_process(accept_fd, jwkdir, pfunc, slist);
238236
}
239237
}

src/socket.h

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
* You should have received a copy of the GNU General Public License
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
18-
1918
typedef int (*process_request_func)(const char *jwkdir, int in_fileno);
2019

2120
int run_service(const char *jwkdir, int port, process_request_func);

src/tang-show-keys

+12-3
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,23 @@
2020

2121
set -e
2222

23-
if [ $# -gt 1 ]; then
24-
echo "Usage: $0 [<port>]" >&2
23+
if [ $# -gt 2 ]; then
24+
echo "Usage: $0 [<port>] [<endpoint>]" >&2
2525
exit 1
2626
fi
2727

2828
port=${1-80}
2929

30-
adv=$(curl -sSf "localhost:$port/adv")
30+
if test -n "$2"; then
31+
first_letter=$(printf %.1s "$2")
32+
if [ "${first_letter}" = "/" ]; then
33+
adv=$(curl -sSf "localhost:$port$2/adv")
34+
else
35+
adv=$(curl -sSf "localhost:$port/$2/adv")
36+
fi
37+
else
38+
adv=$(curl -sSf "localhost:$port/adv")
39+
fi
3140

3241
THP_DEFAULT_HASH=S256 # SHA-256.
3342
jose fmt --json "${adv}" -g payload -y -o- \

src/tangd.c

+35-11
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232
#include "keys.h"
3333
#include "socket.h"
3434

35+
const unsigned int MAX_URL = 256;
36+
3537
static const struct option long_options[] = {
3638
{"port", 1, 0, 'p'},
39+
{"endpoint", 1, 0, 'e'},
3740
{"listen", 0, 0, 'l'},
3841
{"version", 0, 0, 'v'},
3942
{"help", 0, 0, 'h'},
@@ -45,6 +48,7 @@ print_help(const char *name)
4548
{
4649
fprintf(stderr, "Usage: %s [OPTIONS] <jwkdir>\n", name);
4750
fprintf(stderr, " -p, --port=PORT Specify the port to listen (default 9090)\n");
51+
fprintf(stderr, " -e, --endpoint=ENDPOINT Specify endpoint to listen (empty by default)\n");
4852
fprintf(stderr, " -l, --listen Run as a service and wait for connections\n");
4953
fprintf(stderr, " -v, --version Display program version\n");
5054
fprintf(stderr, " -h, --help Show this help message\n");
@@ -184,19 +188,19 @@ rec(http_method_t method, const char *path, const char *body,
184188
"\r\n%s", strlen(enc), enc);
185189
}
186190

187-
static struct http_dispatch dispatch[] = {
188-
{ adv, 1 << HTTP_GET, 2, "^/+adv/+([0-9A-Za-z_-]+)$" },
189-
{ adv, 1 << HTTP_GET, 2, "^/+adv/*$" },
190-
{ rec, 1 << HTTP_POST, 2, "^/+rec/+([0-9A-Za-z_-]+)$" },
191-
{}
192-
};
193-
194191
#define DEFAULT_PORT 9090
195192

193+
static struct http_dispatch s_dispatch[] = {
194+
{ adv, 1 << HTTP_GET, 2, "^/+adv/+([0-9A-Za-z_-]+)$"},
195+
{ adv, 1 << HTTP_GET, 2, "^/+adv/*$"},
196+
{ rec, 1 << HTTP_POST, 2, "^/+rec/+([0-9A-Za-z_-]+)$"},
197+
{}
198+
};
199+
196200
static int
197201
process_request(const char *jwkdir, int in_fileno)
198202
{
199-
struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
203+
struct http_state state = { .dispatch = s_dispatch, .misc = (char*)jwkdir };
200204
http_parser_t parser;
201205
struct stat st = {};
202206
char req[4096] = {};
@@ -244,9 +248,10 @@ main(int argc, char *argv[])
244248
int listen = 0;
245249
int port = DEFAULT_PORT;
246250
const char *jwkdir = NULL;
251+
const char *endpoint = NULL;
247252

248253
while (1) {
249-
int c = getopt_long(argc, argv, "lp:vh", long_options, NULL);
254+
int c = getopt_long(argc, argv, "lp:e:vh", long_options, NULL);
250255
if (c == -1)
251256
break;
252257

@@ -260,6 +265,9 @@ main(int argc, char *argv[])
260265
case 'p':
261266
port = atoi(optarg);
262267
break;
268+
case 'e':
269+
endpoint = optarg;
270+
break;
263271
case 'l':
264272
listen = 1;
265273
break;
@@ -272,9 +280,25 @@ main(int argc, char *argv[])
272280
}
273281
jwkdir = argv[optind++];
274282

283+
// Adjust endpoint
284+
char adv_endpoint[MAX_URL];
285+
char adv_thp_endpoint[MAX_URL];
286+
char rec_endpoint[MAX_URL];
287+
if (endpoint != NULL) {
288+
char *endpoint_ptr = (char*)endpoint;
289+
while (*endpoint_ptr == '/') {
290+
endpoint_ptr++;
291+
}
292+
snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", endpoint_ptr);
293+
snprintf(adv_thp_endpoint, MAX_URL, "^/%s/+adv/*$", endpoint_ptr);
294+
snprintf(rec_endpoint, MAX_URL, "^/%s/+rec/+([0-9A-Za-z_-]+)$", endpoint_ptr);
295+
s_dispatch[0].re = adv_endpoint;
296+
s_dispatch[1].re = adv_thp_endpoint;
297+
s_dispatch[2].re = rec_endpoint;
298+
}
275299
if (listen == 0) { /* process one-shot query from stdin */
276-
return process_request(jwkdir, STDIN_FILENO);
300+
return process_request(jwkdir, STDIN_FILENO);
277301
} else { /* listen and process all incoming connections */
278-
return run_service(jwkdir, port, process_request);
302+
return run_service(jwkdir, port, process_request);
279303
}
280304
}

tests/adv

+23-23
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,47 @@ adv_startup () {
3636

3737
adv_second_phase () {
3838
# Make sure requests on the root fail
39-
fetch / && expected_fail
39+
fetch "${ENDPOINT}"/ && expected_fail
4040

4141
# The request should fail (404) for non-signature key IDs
42-
fetch /adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
43-
fetch /adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail
42+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
43+
fetch "${ENDPOINT}"/adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail
4444

4545
# The default advertisement fetch should succeed and pass verification
46-
fetch /adv
47-
fetch /adv | ver $TMP/db/sig.jwk
48-
fetch /adv/ | ver $TMP/db/sig.jwk
46+
fetch "${ENDPOINT}"/adv
47+
fetch "${ENDPOINT}"/adv | ver $TMP/db/sig.jwk
48+
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/sig.jwk
4949

5050
# Fetching by any thumbprint should work
51-
fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
52-
fetch /adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
51+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
52+
fetch "${ENDPOINT}"/adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
5353

5454
# Requesting an adv by an advertised key ID should't be signed by hidden keys
55-
fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
56-
fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
55+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
56+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
5757

5858
# Verify that the default advertisement is not signed with hidden signature keys
59-
fetch /adv/ | ver $TMP/db/.oth.jwk && expected_fail
60-
fetch /adv/ | ver $TMP/db/.sig.jwk && expected_fail
59+
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/.oth.jwk && expected_fail
60+
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/.sig.jwk && expected_fail
6161

6262
# A private key advertisement is signed by all advertised keys and the requested private key
63-
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
64-
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
65-
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
63+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
64+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
65+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
6666

6767
# Verify that the advertisements contain the cty parameter
68-
fetch /adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
69-
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
68+
fetch "${ENDPOINT}"/adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
69+
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
7070
| jose fmt -j- -Og signatures -A \
7171
-g 0 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU \
7272
-g 1 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU
7373

7474
THP_DEFAULT_HASH=S256 # SHA-256.
75-
test "$(tang-show-keys $PORT)" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i $TMP/db/sig.jwk)"
75+
test "$(tang-show-keys $PORT $ENDPOINT)" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i $TMP/db/sig.jwk)"
7676

7777
# Check that new keys will be created if none exist.
7878
rm -rf "${TMP}/db" && mkdir -p "${TMP}/db"
79-
fetch /adv
79+
fetch "${ENDPOINT}"/adv
8080

8181
# Now let's make sure the new keys were named using our default thumbprint
8282
# hash and then rotate them and check if we still create new keys.
@@ -88,7 +88,7 @@ adv_second_phase () {
8888
mv -f -- "${k}" ".${k}"
8989
done
9090
cd -
91-
fetch /adv
91+
fetch "${ENDPOINT}"/adv
9292

9393
# Lets's now test with multiple pairs of keys.
9494
for i in 1 2 3 4 5 6 7 8 9; do
@@ -103,12 +103,12 @@ adv_second_phase () {
103103
done
104104

105105
# Verify the advertisement is correct.
106-
validate "$(fetch /adv)"
106+
validate "$(fetch "${ENDPOINT}"/adv)"
107107

108108
# And make sure we can fetch an adv by its thumbprint.
109109
for jwk in "${TMP}"/db/other-sig-*.jwk; do
110110
for alg in $(jose alg -k hash); do
111-
fetch /adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
111+
fetch "${ENDPOINT}"/adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
112112
done
113113
done
114114

@@ -130,5 +130,5 @@ adv_second_phase () {
130130
valid_key_perm "${jwk}"
131131
done
132132
[ -z "${thp}" ] && die "There should be valid keys after rotation"
133-
test "$(tang-show-keys $PORT)" = "${thp}"
133+
test "$(tang-show-keys $PORT $ENDPOINT)" = "${thp}"
134134
}

tests/adv-socat-endpoint

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/sh -ex
2+
#
3+
# Copyright (c) 2023 Red Hat, Inc.
4+
# Author: Sergio Arroutbi <[email protected]>
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
20+
. adv
21+
22+
sanity_check
23+
24+
adv_startup
25+
26+
port=$(random_port)
27+
export PORT=$((port+3))
28+
export ENDPOINT="/api/dee-hms"
29+
start_server_endpoint "${PORT}" "${ENDPOINT}"
30+
export PID=$!
31+
wait_for_port ${PORT}
32+
33+
adv_second_phase

tests/adv-standalone-endpoint

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/sh -ex
2+
#
3+
# Copyright (c) 2023 Red Hat, Inc.
4+
# Author: Sergio Arroutbi <[email protected]>
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
20+
. adv
21+
22+
adv_startup
23+
24+
port=$(random_port)
25+
export PORT=$((port+1))
26+
export ENDPOINT="/api/dee-hms"
27+
start_standalone_server_endpoint "${PORT}" "${ENDPOINT}"
28+
export PID=$!
29+
wait_for_port ${PORT}
30+
31+
adv_second_phase

tests/helpers

+14-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ random_port() {
3030
if [ -n "${TANG_BSD}" ]; then
3131
jot -r 1 1024 65536
3232
else
33-
shuf -i 1024-65536 -n 1
33+
if test -f /dev/urandom;
34+
then
35+
shuf -i 1024-65535 -n 1 --random-file=/dev/urandom
36+
else
37+
shuf -i 1024-65535 -n 1
38+
fi
3439
fi
3540
}
3641

@@ -62,10 +67,18 @@ start_server() {
6267
"${SOCAT}" TCP-LISTEN:"${1}",bind=127.0.0.1,fork SYSTEM:"${VALGRIND} tangd ${TMP}/db" &
6368
}
6469

70+
start_server_endpoint() {
71+
"${SOCAT}" TCP-LISTEN:"${1}",bind=127.0.0.1,fork SYSTEM:"${VALGRIND} tangd ${TMP}/db -e ${ENDPOINT}" &
72+
}
73+
6574
start_standalone_server() {
6675
${VALGRIND} tangd -p ${1} -l ${TMP}/db &
6776
}
6877

78+
start_standalone_server_endpoint() {
79+
${VALGRIND} tangd -p ${1} -l ${TMP}/db -e ${2} &
80+
}
81+
6982
on_exit() {
7083
if [ "${PID}" ]; then
7184
kill "${PID}" || true

tests/meson.build

+4
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ if socat.found()
4141
endif
4242

4343
test('adv-standalone', find_program('adv-standalone'), env: env, timeout: 360)
44+
test('adv-standalone-endpoint', find_program('adv-standalone-endpoint'), env: env, timeout: 360)
4445
test('adv-socat', find_program('adv-socat'), env: env, timeout: 360)
46+
test('adv-socat-endpoint', find_program('adv-socat-endpoint'), env: env, timeout: 360)
4547
test('rec-standalone', find_program('rec-standalone'), env: env, timeout: 360)
48+
test('rec-standalone-endpoint', find_program('rec-standalone-endpoint'), env: env, timeout: 360)
4649
test('rec-socat', find_program('rec-socat'), env: env, timeout: 360)
50+
test('rec-socat-endpoint', find_program('rec-socat-endpoint'), env: env, timeout: 360)
4751
test('test-keys', test_keys, env: env, timeout: 360)
4852

4953
# vim:set ts=2 sw=2 et:

0 commit comments

Comments
 (0)