Skip to content

Commit

Permalink
Move database handling out of process
Browse files Browse the repository at this point in the history
José now ships all the shell commands we need to generate a database
with a shell script. So we setup a systemd path watch on the JWK
directory and, when any changes are detected, we regenerate all the
signatures and create symlinks for the keys based on their thumbprints.
This allows us to move most of the code out of the daemon (and C).

In this new structure, tangd just hands off preconfigured advertisement
files. It is also able to look up JWKs using the thumbprint symlinks
for recovery requests.
  • Loading branch information
npmccallum committed Oct 17, 2016
1 parent 7cfd8bb commit 71b093b
Show file tree
Hide file tree
Showing 23 changed files with 595 additions and 1,070 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
*.lo
*.log
*.m4
*.path
*.service
*.so
*.socket
*.swp
*.swo
*.trs
Expand Down Expand Up @@ -35,4 +38,5 @@ Makefile.in
Makefile
missing
tags
tangd
test-driver
23 changes: 20 additions & 3 deletions Makefile.am
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
SUBDIRS = src

TESTS = tests/adv tests/rec
EXTRA_DIST = COPYING $(TESTS)
EXTRA_DIST = COPYING $(TESTS) [email protected] tangd.socket.in tangd-update.path.in tangd-update.service.in
CLEANFILES = tangd.serivce tangd.socket tangd-update.path tangd-update.service

systemdsystemunit_DATA = [email protected] tangd.socket tangd-update.path tangd-update.service
dist_libexec_SCRIPTS = tangd-update
libexec_PROGRAMS = tangd

tangd_SOURCES = http.c http.h tangd.c
tangd_CFLAGS = @TANG_CFLAGS@ @jose_zlib_CFLAGS@ @jose_openssl_CFLAGS@
tangd_LDADD = @jose_zlib_LIBS@ @jose_openssl_LIBS@ @http_parser_LIBS@

jwkdir = $(localstatedir)/db/$(PACKAGE_NAME)
cachedir = $(localstatedir)/cache/$(PACKAGE_NAME)

%: %.in
$(AM_V_GEN)$(SED) \
-e 's,@libexecdir\@,$(libexecdir),g' \
-e 's,@jwkdir\@,$(jwkdir),g' \
-e 's,@cachedir\@,$(cachedir),g' \
$(srcdir)/$@.in > $@
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,17 @@ Enabling a Tang server is a simple two-step process.

First, we need to generate a signing key and an exchange key.

# jose gen -t '{"alg": "ES256"}' \
> /var/db/tang/keys/sig.jwk
# sudo jose gen -t '{"alg":"ES256"}' -o /var/db/tang/sig.jwk

# jose gen -t '{"kty": "EC", "crv": "P-256", "key_ops": ["deriveKey"]}' \
> /var/db/tang/keys/exc.jwk
# sudo jose gen -t '{"kty":"EC","crv":"P-256","key_ops":["deriveKey"]}' \
-o /var/db/tang/exc.jwk

Second, enable the service using systemd socket activation.
Second, enable and start the service using systemd.

# systemctl enable tangd.socket
# systemctl start tangd.socket
# sudo systemctl enable tangd-update.path
# sudo systemctl start tangd-update.path
# sudo systemctl enable tangd.socket
# sudo systemctl start tangd.socket

That's it! You're up and running!

Expand All @@ -102,12 +103,11 @@ should be rotated.

First, generate a new key:

# jose gen -t '{"alg": "ES256"}' \
> /var/db/tang/keys/newsig.jwk
# sudo jose gen -t '{"alg":"ES256"}' -o /var/db/tang/newsig.jwk

Second, disable advertisement of the previous key:

# mv /var/db/tang/keys/sig.jwk /var/db/tang/keys/.sig.jwk
# sudo mv /var/db/tang/sig.jwk /var/db/tang/.sig.jwk

Third, after some reasonable period of time you may delete the old keys. You
should only delete the old keys when you are sure that no client require them
Expand Down
10 changes: 6 additions & 4 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ AC_CHECK_LIB([http_parser], [http_parser_execute],
[AC_SUBST(http_parser_LIBS, [-lhttp_parser])],
[AC_MSG_ERROR([http-parser required!])])

PKG_CHECK_MODULES([jose], [jose >= 4])
PKG_CHECK_MODULES([jose_zlib], [jose-zlib >= 4])
PKG_CHECK_MODULES([jose_openssl], [jose-openssl >= 4])
PKG_CHECK_MODULES([jose_zlib], [jose-zlib >= 5])
PKG_CHECK_MODULES([jose_openssl], [jose-openssl >= 5])

AC_SUBST([systemdsystemunitdir], [${prefix}/lib/systemd/system])

PKG_CHECK_MODULES([systemd], [systemd],
[AC_SUBST([systemdsystemunitdir], [$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])],
[AC_SUBST([systemdsystemunitdir], [${prefix}/lib/systemd/system])])

TANG_CFLAGS="\
-Wall \
Expand Down
168 changes: 168 additions & 0 deletions http.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
/*
* Copyright (c) 2016 Red Hat, Inc.
* Author: Nathaniel McCallum <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "http.h"
#undef http_reply

#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static const char *METHOD_NAMES[] = {
#define XX(num, name, string) [num] = # string,
HTTP_METHOD_MAP(XX)
#undef XX
NULL
};

static int
on_url(http_parser *parser, const char *at, size_t length)
{
struct http_state *state = parser->data;

if (state->req.status == 0) {
if (strlen(state->req.path) + length >= sizeof(state->req.path))
state->req.status = HTTP_STATUS_URI_TOO_LONG;
else
strncat(state->req.path, at, length);
}

return 0;
}

static int
on_body(http_parser *parser, const char *at, size_t length)
{
struct http_state *state = parser->data;

if (state->req.status == 0) {
if (strlen(state->req.body) + length >= sizeof(state->req.body))
state->req.status = HTTP_STATUS_PAYLOAD_TOO_LARGE;
else
strncat(state->req.body, at, length);
}

return 0;
}

static int
on_message_complete(http_parser *parser)
{
struct http_state *state = parser->data;
const char *addr = NULL;
bool pathmatch = false;
bool methmatch = false;
int r = 0;

if (state->req.status != 0)
goto error;

addr = getenv("REMOTE_ADDR");
fprintf(stderr, "%s %s %s",
addr ? addr : "<unknown>",
METHOD_NAMES[parser->method],
state->req.path);

for (size_t i = 0; state->dispatch[i].re && r == 0; i++) {
const struct http_dispatch *d = &state->dispatch[i];
regmatch_t match[d->nmatches];
regex_t re = {};

memset(match, 0, sizeof(match));

r = regcomp(&re, d->re, REG_EXTENDED) == 0 ? 0 : -EINVAL;
if (r != 0) {
state->req.status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
goto error;
}

if (regexec(&re, state->req.path, d->nmatches, match, 0) == 0) {
pathmatch = true;

if (((1 << parser->method) & d->methods) != 0) {
methmatch = true;

r = d->func(parser->method, state->req.path,
state->req.body, match, state->misc);
}
}

regfree(&re);
}

if (r > 0)
goto egress;

if (r == 0) {
if (!pathmatch)
state->req.status = HTTP_STATUS_NOT_FOUND;
else if (!methmatch)
state->req.status = HTTP_STATUS_METHOD_NOT_ALLOWED;
else
state->req.status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
} else {
state->req.status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
}

error:
http_reply(__FILE__, __LINE__, state->req.status, NULL);

egress:
memset(&state->req, 0, sizeof(state->req));
return 0;
}

const http_parser_settings http_settings = {
.on_url = on_url,
.on_body = on_body,
.on_message_complete = on_message_complete,
};

int
http_reply(const char *file, int line,
enum http_status code, const char *fmt, ...)
{
const char *msg = NULL;
va_list ap;
int a;
int b;

switch (code) {
#define XX(num, name, string) case num: msg = # string; break;
HTTP_STATUS_MAP(XX)
#undef XX
default:
return http_reply(file, line, HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
}

fprintf(stderr, " => %d (%s:%d)\n", code, file, line);

a = dprintf(STDOUT_FILENO, "HTTP/1.1 %d %s\r\n", code, msg);
if (a < 0)
return a;

va_start(ap, fmt);
b = vdprintf(STDOUT_FILENO, fmt ? fmt : "\r\n", ap);
va_end(ap);
return b < 0 ? b : a + b;
}
28 changes: 16 additions & 12 deletions src/plugin.h → http.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@

#pragma once

#include "db.h"

#include <http_parser.h>
#include <sys/types.h>
#include <regex.h>
Expand Down Expand Up @@ -95,25 +93,31 @@ enum http_status
};
#endif

struct tang_plugin_map {
struct http_dispatch {
int (*func)(enum http_method method, const char *path,
const char *body, regmatch_t matches[]);
const char *body, regmatch_t matches[], void *misc);
uint64_t methods;
size_t nmatches;
const char *re;
struct tang_plugin_map *next;
};

typedef void (*tang_plugin_epoll)(void);
struct http_request {
int status;
char path[1024 * 4];
char body[1024 * 64];
};

extern struct tang_plugin_map *tang_plugin_maps;
struct http_state {
const struct http_dispatch *dispatch;
struct http_request req;
void *misc;
};

extern int
tang_plugin_init(int epoll, const char *cfg);
extern const http_parser_settings http_settings;

int __attribute__ ((format(printf, 4, 5)))
tang_reply(const char *file, int line,
http_reply(const char *file, int line,
enum http_status code, const char *fmt, ...);

#define tang_reply(code, ...) \
tang_reply(__FILE__, __LINE__, code, __VA_ARGS__)
#define http_reply(code, ...) \
http_reply(__FILE__, __LINE__, code, __VA_ARGS__)
23 changes: 9 additions & 14 deletions src/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
AM_CFLAGS = @TANG_CFLAGS@ @jose_CFLAGS@ @jose_openssl_CFLAGS@ -fvisibility=hidden
AM_CFLAGS = @TANG_CFLAGS@ @jose_zlib_CFLAGS@ @jose_openssl_CFLAGS@

tangdbdir = $(localstatedir)/db/$(PACKAGE_NAME)
plugindir = $(libdir)/tang/plugins
jwkdir = $(localstatedir)/db/$(PACKAGE_NAME)
cachedir = $(localstatedir)/cache/$(PACKAGE_NAME)

systemdsystemunit_DATA = tangd.socket tangd.service
EXTRA_DIST = tangd.socket.in tangd.service.in
CLEANFILES = tangd.socket tangd.service

libexec_PROGRAMS = tangd
tangd_SOURCES = db.c db.h plugin.c plugin.h tangd.c adv.c rec.c
tangd_CFLAGS = $(AM_CFLAGS) -rdynamic
tangd_LDADD = @jose_LIBS@ @http_parser_LIBS@ @dl_LIBS@

plugin_LTLIBRARIES = dbdir.la
dbdir_la_LIBADD = @jose_openssl_LIBS@ @jose_zlib_LIBS@
dbdir_la_LDFLAGS = -module -avoid-version
tangd_SOURCES = http.c http.h tangd.c
tangd_LDADD = @jose_zlib_LIBS@ @jose_openssl_LIBS@ @http_parser_LIBS@

install-data-hook:
mkdir -p $(DESTDIR)$(tangdbdir)/keys
mkdir -p $(DESTDIR)$(tangdbdir)/blst
mkdir -p $(DESTDIR)$(jwkdir)
mkdir -p $(DESTDIR)$(cachedir)

%: %.in
$(AM_V_GEN)$(SED) \
-e 's,@libexecdir\@,$(libexecdir),g' \
-e 's,@tangdbdir\@,$(tangdbdir),g' \
-e 's,@plugindir\@,$(plugindir),g' \
-e 's,@jwkdir\@,$(jwkdir),g' \
-e 's,@cachedir\@,$(cachedir),g' \
$(srcdir)/$@.in > $@
Loading

0 comments on commit 71b093b

Please sign in to comment.