Skip to content

Commit 3335e5f

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 3335e5f

13 files changed

+265
-49
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

+7-7
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,10 @@ 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,
151-
socket_list *slist)
151+
socket_list *slist,
152+
const char* endpoint)
152153
{
153154
pid_t pid;
154155
socket_list *ptr;
@@ -166,8 +167,7 @@ static void spawn_process(int fd, const char *jwkdir,
166167
}
167168

168169
close(fd);
169-
170-
pfunc(jwkdir, STDOUT_FILENO);
170+
pfunc(jwkdir, STDOUT_FILENO, endpoint);
171171
free_socket_list(slist);
172172
exit(0);
173173
} else if (pid == -1) {
@@ -183,7 +183,8 @@ static void handle_child(int sig)
183183
while ((waitpid(-1, &status, WNOHANG)) > 0);
184184
}
185185

186-
int run_service(const char *jwkdir, int port, process_request_func pfunc)
186+
int run_service(const char *jwkdir, int port, process_request_func pfunc,
187+
const char *endpoint)
187188
{
188189
socket_list *slist, *ptr;
189190
int r, n = 0, accept_fd;
@@ -233,8 +234,7 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
233234
perror("accept");
234235
continue;
235236
}
236-
237-
spawn_process(accept_fd, jwkdir, pfunc, slist);
237+
spawn_process(accept_fd, jwkdir, pfunc, slist, endpoint);
238238
}
239239
}
240240

src/socket.h

+2-3
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+
typedef int (*process_request_func)(const char *jwkdir, int in_fileno, const char *endpoint);
1819

19-
typedef int (*process_request_func)(const char *jwkdir, int in_fileno);
20-
21-
int run_service(const char *jwkdir, int port, process_request_func);
20+
int run_service(const char *jwkdir, int port, process_request_func, const char *endpoint);

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

+36-12
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,35 @@ 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
197-
process_request(const char *jwkdir, int in_fileno)
201+
process_request(const char *jwkdir, int in_fileno, const char *endpoint)
198202
{
199-
struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
203+
char adv_endpoint[MAX_URL];
204+
char adv_thp_endpoint[MAX_URL];
205+
char rec_endpoint[MAX_URL];
206+
if (endpoint != NULL) {
207+
char *endpoint_ptr = (char*)endpoint;
208+
while (*endpoint_ptr == '/') {
209+
endpoint_ptr++;
210+
}
211+
snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", endpoint_ptr);
212+
snprintf(adv_thp_endpoint, MAX_URL, "^/%s/+adv/*$", endpoint_ptr);
213+
snprintf(rec_endpoint, MAX_URL, "^/%s/+rec/+([0-9A-Za-z_-]+)$", endpoint_ptr);
214+
s_dispatch[0].re = adv_endpoint;
215+
s_dispatch[1].re = adv_thp_endpoint;
216+
s_dispatch[2].re = rec_endpoint;
217+
}
218+
219+
struct http_state state = { .dispatch = s_dispatch, .misc = (char*)jwkdir };
200220
http_parser_t parser;
201221
struct stat st = {};
202222
char req[4096] = {};
@@ -244,9 +264,10 @@ main(int argc, char *argv[])
244264
int listen = 0;
245265
int port = DEFAULT_PORT;
246266
const char *jwkdir = NULL;
267+
const char *endpoint = NULL;
247268

248269
while (1) {
249-
int c = getopt_long(argc, argv, "lp:vh", long_options, NULL);
270+
int c = getopt_long(argc, argv, "lp:e:vh", long_options, NULL);
250271
if (c == -1)
251272
break;
252273

@@ -260,6 +281,9 @@ main(int argc, char *argv[])
260281
case 'p':
261282
port = atoi(optarg);
262283
break;
284+
case 'e':
285+
endpoint = optarg;
286+
break;
263287
case 'l':
264288
listen = 1;
265289
break;
@@ -273,8 +297,8 @@ main(int argc, char *argv[])
273297
jwkdir = argv[optind++];
274298

275299
if (listen == 0) { /* process one-shot query from stdin */
276-
return process_request(jwkdir, STDIN_FILENO);
300+
return process_request(jwkdir, STDIN_FILENO, endpoint);
277301
} else { /* listen and process all incoming connections */
278-
return run_service(jwkdir, port, process_request);
302+
return run_service(jwkdir, port, process_request, endpoint);
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

0 commit comments

Comments
 (0)