commit - 477e0bbd518b16f53d572bd7e09c0392655c73e8
commit + 5fb267cb9e3ad437bb94f4fd9ecbf399028c76f7
blob - f1443d0b0dc69d389f59208ecb4ce2354a246f6d
blob + a09ef25ef79277b04b90ae84b72aed522da5a4c1
--- gitwrapper/Makefile
+++ gitwrapper/Makefile
PROG= gitwrapper
SRCS= gitwrapper.c parse.y log.c dial.c path.c error.c \
- reference_parse.c hash.c object_qid.c
+ reference_parse.c hash.c object_qid.c secrets.c
CLEANFILES = parse.h
blob - 0c22d1dced690c764d8a882746e76ec45a9c7226
blob + 2017ec526d2f1120654078766a5c1dd7b5a0723b
--- gitwrapper/gitwrapper.c
+++ gitwrapper/gitwrapper.c
confpath = getenv("GOTD_CONF_PATH");
if (confpath == NULL)
confpath = GOTD_CONF_PATH;
- parse_config(confpath, PROC_GITWRAPPER, &gotd);
+ parse_config(confpath, PROC_GITWRAPPER, NULL, &gotd);
error = apply_unveil(myserver);
if (error)
blob - 40093462c835f76308db6ab0dc60b0735412ab92
blob + 9040f98a6fc8eb6e53174a016f48165d108d8ec9
--- gotd/Makefile
+++ gotd/Makefile
PROG= gotd
SRCS= gotd.c auth.c repo_read.c repo_write.c log.c privsep_stub.c \
- listen.c imsg.c parse.y pack_create.c ratelimit.c deltify.c \
+ listen.c imsg.c parse.y secrets.c pack_create.c ratelimit.c \
+ deltify.c \
bloom.c buf.c date.c deflate.c delta.c delta_cache.c error.c \
gitconfig.c gotconfig.c inflate.c lockfile.c murmurhash2.c \
object.c object_cache.c object_create.c object_idset.c \
CLEANFILES = parse.h
-MAN = ${PROG}.conf.5 ${PROG}.8
+MAN = ${PROG}.conf.5 ${PROG}-secrets.conf.5 ${PROG}.8
CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}
YFLAGS =
blob - 38567d159047ce5179b9982fcefdf2bff0d21e9b
blob + 2eaa6f893d71aee3fc293fbaef9932969dbf0923
--- gotd/gotd.8
+++ gotd/gotd.8
.Nm
.Op Fl dnv
.Op Fl f Ar config-file
+.Op Fl s Ar secrets
.Sh DESCRIPTION
.Nm
is a Git repository server which listens on a
.It Fl n
Configtest mode.
Only check the configuration file for validity.
+.It Fl s Ar secrets
+Set the path to the secrets file.
+If not specified, the file
+.Pa /etc/gotd-secrets.conf
+will be used if it exists.
.It Fl v
Verbose mode.
Verbosity increases if this option is used multiple times.
.Xr gotsh 1 ,
.Xr git-repository 5 ,
.Xr gotd.conf 5
+.Xr gotd-secrets.conf 5
.Sh AUTHORS
.An Stefan Sperling Aq Mt stsp@openbsd.org
.Sh CAVEATS
blob - /dev/null
blob + 9c0a9efd1ed96a42a798c7f69a70c8645bdf0ead (mode 644)
--- /dev/null
+++ gotd/gotd-secrets.conf.5
+.\"
+.\" Copyright (c) 2024 Omar Polo <op@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt GOTD-SECRETS.CONF 5
+.Os
+.Sh NAME
+.Nm gotd-secrets.conf
+.Nd gotd secrets file
+.Sh DESCRIPTION
+.Nm
+holds the authentication data and HMAC secrets for
+.Xr gotd 8
+notifications.
+.Pp
+The file format is line-based, with one entry per line.
+Comments can be put at the start of the line using a hash mark
+.Pq Sq # ,
+and extend to the end of it.
+Blank lines are also ignored.
+.Pp
+The entries have the following syntax:
+.Pp
+.Dl type key value
+.Pp
+with spaces or tabs to separate the fields.
+No quoting is supported, so a space or a tab can't appear as part of
+any field.
+.Pp
+The type is one of:
+.Bl -tag -width Ds
+.It Ic auth
+The entry is for HTTP Basic Authentication.
+.Ar key
+is the username and
+.ar value
+the password.
+The username is also used to identify this secret.
+.It Ic hmac
+The entry is for signing the notification HTTP payload with HMAC.
+The
+.Ar key
+is a label to identify this secret and
+.Ar value
+is the HMAC secret.
+.Pp
+Suitable secrets can be generated with
+.Xr openssl 1
+as follows:
+.Pp
+.Dl $ openssl rand -base64 32
+.El
+.Pp
+The key must be unique between entries with the same type.
+.Sh FILES
+.Bl -tag -width Ds -compact
+.It Pa /etc/gotd-secrets.conf
+Location of the
+.Nm
+configuration file.
+.El
+.Sh EXAMPLES
+This example configuration defines two secrets, the first for
+HTTP authentication and the second for HMAC signign.
+.Bd -literal -offset indent
+# /etc/gotd-secrets.conf
+auth flan super-strong-password!
+hmac hacker q0tcl8QhjYs7U75MW/2rwB30CpdbAhONkfLGxFHm/+8=
+.Ed
+.Pp
+These values can be referenced in
+.Xr gotd.conf 5
+as:
+.Bd -literal -offset indent
+# /etc/gotd.conf
+repository "openbsd/ports" {
+ path "/var/git/ports.git"
+ permit rw :porters
+ permit ro anonymous
+
+ notify {
+ url https://flan.com/notify/ auth flan
+ url https://hacker.com/notify/ hmac hacker
+ }
+}
+.El
+.Sh SEE ALSO
+.Xr got 1 ,
+.Xr gotsh 1 ,
+.Xr gotd.conf 5 ,
+.Xr gotd 8
blob - 5830c85ab86441288df899ce965b061f45d21f62
blob + 7eb70e9624de8eb3893a1a5803d608e62d5bf3f1
--- gotd/gotd.c
+++ gotd/gotd.c
#include "repo_read.h"
#include "repo_write.h"
#include "notify.h"
+#include "secrets.h"
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
__dead static void
usage(void)
{
- fprintf(stderr, "usage: %s [-dnv] [-f config-file]\n", getprogname());
+ fprintf(stderr, "usage: %s [-dnv] [-f config-file] [-s secrets]\n",
+ getprogname());
exit(1);
}
main(int argc, char **argv)
{
const struct got_error *error = NULL;
+ struct gotd_secrets *secrets = NULL;
int ch, fd = -1, daemonize = 1, verbosity = 0, noaction = 0;
const char *confpath = GOTD_CONF_PATH;
+ const char *secretspath = NULL;
char *argv0 = argv[0];
char title[2048];
struct passwd *pw = NULL;
struct gotd_repo *repo = NULL;
char *default_sender = NULL;
char hostname[HOST_NAME_MAX + 1];
+ FILE *fp;
FILE *diff_f1 = NULL, *diff_f2 = NULL;
int diff_fd1 = -1, diff_fd2 = -1;
const char *errstr;
log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
- while ((ch = getopt(argc, argv, "df:nP:T:v")) != -1) {
+ while ((ch = getopt(argc, argv, "df:nP:s:T:v")) != -1) {
switch (ch) {
case 'd':
daemonize = 0;
if (repo_path == NULL)
fatal("realpath '%s'", optarg);
break;
+ case 's':
+ secretspath = optarg;
+ break;
case 'T':
switch (*optarg) {
case 'A':
if (geteuid() && (proc_id == PROC_GOTD || proc_id == PROC_LISTEN))
fatalx("need root privileges");
- if (parse_config(confpath, proc_id, &gotd) != 0)
+ if (proc_id == PROC_GOTD) {
+ const char *p = secretspath ? secretspath : GOTD_SECRETS_PATH;
+
+ fp = fopen(p, "r");
+ if (fp == NULL && (secretspath != NULL || errno != ENOENT))
+ fatal("can't open secret file %s", p);
+
+ if (fp != NULL) {
+ error = gotd_secrets_parse(p, fp, &secrets);
+ fclose(fp);
+ if (error)
+ fatalx("failed to parse secrets file %s: %s",
+ p, error->msg);
+ }
+ }
+
+ if (parse_config(confpath, proc_id, secrets, &gotd) != 0)
return 1;
pw = getpwnam(gotd.user_name);
signal_add(&evsigchld, NULL);
gotd_imsg_event_add(&gotd.listen_proc->iev);
- if (gotd.notify_proc)
+ if (gotd.notify_proc) {
+ struct imsgbuf *imsgbuf = &gotd.notify_proc->iev.ibuf;
+ struct gotd_secret *s;
+ size_t i, n = 0;
+
gotd_imsg_event_add(&gotd.notify_proc->iev);
+
+ if (gotd.secrets)
+ n = gotd.secrets->len;
+
+ if (imsg_compose(imsgbuf, GOTD_IMSG_SECRETS, 0, 0, -1,
+ &n, sizeof(n)) == -1)
+ fatal("imsg_compose GOTD_IMSG_SECRETS");
+ if (imsg_flush(imsgbuf))
+ fatal("imsg_flush");
+ for (i = 0; i < n; ++i) {
+ struct iovec iov[5];
+ int keylen, vallen;
+
+ s = &gotd.secrets->secrets[i];
+
+ keylen = strlen(s->key) + 1;
+ vallen = strlen(s->val) + 1;
+
+ iov[0].iov_base = &s->type;
+ iov[0].iov_len = sizeof(s->type);
+
+ iov[1].iov_base = &keylen;
+ iov[1].iov_len = sizeof(keylen);
+
+ iov[2].iov_base = &vallen;
+ iov[2].iov_len = sizeof(vallen);
+
+ iov[3].iov_base = s->key;
+ iov[3].iov_len = keylen;
+
+ iov[4].iov_base = s->val;
+ iov[4].iov_len = vallen;
+
+ if (imsg_composev(imsgbuf, GOTD_IMSG_SECRET,
+ 0, 0, -1, iov, 5) == -1)
+ fatal("imsg_composev GOTD_IMSG_SECRET");
+ if (imsg_flush(imsgbuf))
+ fatal("imsg_flush");
+ }
+
+ gotd_secrets_free(gotd.secrets);
+ gotd.secrets = NULL;
+ }
+
event_dispatch();
free(repo_path);
blob - 966c6008573b577e99cc85b6d7eada163453396c
blob + 54878c9402ad6203ea5e8dd27dea510086c07f1e
--- gotd/gotd.conf.5
+++ gotd/gotd.conf.5
and
.Ic port
directives can be used to specify a different SMTP server address and port.
-.It Ic url Ar URL Oo Ic user Ar user Ic password Ar password Oo Ic insecure Oc Oc Oo Ic hmac Ar secret Oc
+.It Ic url Ar URL Oo Ic auth Ar auth Oo Ic insecure Oc Oc Oo Ic hmac Ar label Oc
Send notifications via HTTP.
This directive may be specified multiple times to build a list of
HTTP servers to send notifications to.
no TLS errors occur.
.Pp
The optional
-.Ic user
-and
-.Ic password
-directives enable HTTP Basic authentication.
-If used, both a
-.Ar user
-and a
-.Ar password
-must be specified.
-The
-.Ar password
-must not be an empty string.
+.Ic auth
+directive enables HTTP Basic authentication.
Unless the
.Ic insecure
option is specified the notification target
.Pp
If a
.Ic hmac
-.Ar secret
+.Ar label
is provided, the request body will be signed using HMAC, allowing the
receiver to verify the notification message's authenticity and integrity.
The signature uses HMAC-SHA256 and will be sent in the HTTP header
.Dq X-Gotd-Signature .
-Suitable secrets can be generated with
-.Xr openssl 1
-as follows:
.Pp
-.Dl $ openssl rand -base64 32
+If provided,
+the authentication data
+.Ar auth
+and the HMAC secret
+.Ar label
+are resolved using the
+.Xr gotd-secrets.conf 5
+file.
.Pp
The request body contains a JSON object with a
.Dq notifications
.Sh SEE ALSO
.Xr got 1 ,
.Xr gotsh 1 ,
+.Xr gotd-secrets.conf 5 ,
.Xr gotd 8
blob - 4c8e79a9beb60c52b4a922d424c3d7db0000dfb5
blob + dfd757252c16a2176bebb823f0fe310ab20cc955
--- gotd/gotd.h
+++ gotd/gotd.h
#define GOTD_UNIX_SOCKET_BACKLOG 10
#define GOTD_USER "_gotd"
#define GOTD_CONF_PATH "/etc/gotd.conf"
+#define GOTD_SECRETS_PATH "/etc/gotd-secrets.conf"
#define GOTD_EMPTY_PATH "/var/empty"
#ifndef GOT_LIBEXECDIR
char *hostname;
char *port;
char *path;
- char *user;
- char *password;
- char *hmac_secret;
+ char *auth;
+ char *hmac;
} http;
} conf;
};
struct gotd_child_proc;
+struct gotd_secrets;
struct gotd {
pid_t pid;
char unix_socket_path[PATH_MAX];
struct timeval auth_timeout;
struct gotd_uid_connection_limit *connection_limits;
size_t nconnection_limits;
+ struct gotd_secrets *secrets;
char *argv0;
const char *confpath;
GOTD_IMSG_CONNECT_NOTIFIER,
GOTD_IMSG_CONNECT_SESSION,
GOTD_IMSG_NOTIFY,
- GOTD_IMSG_NOTIFICATION_SENT
+ GOTD_IMSG_NOTIFICATION_SENT,
+
+ /* Secrets. */
+ GOTD_IMSG_SECRETS, /* number of secrets */
+ GOTD_IMSG_SECRET,
};
/* Structure for GOTD_IMSG_ERROR. */
/* Followed by username_len data bytes. */
};
-int parse_config(const char *, enum gotd_procid, struct gotd *);
+int parse_config(const char *, enum gotd_procid, struct gotd_secrets *,
+ struct gotd *);
struct gotd_repo *gotd_find_repo_by_name(const char *, struct gotd_repolist *);
struct gotd_repo *gotd_find_repo_by_path(const char *, struct gotd *);
struct gotd_uid_connection_limit *gotd_find_uid_connection_limit(
blob - 01439b9e598a7bf45e46c1328da61d697115f13a
blob + 08fee725f18dbcca82f68aba86a3132f5368fda6
--- gotd/notify.c
+++ gotd/notify.c
#include "gotd.h"
#include "log.h"
#include "notify.h"
+#include "secrets.h"
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
#endif
+
+static struct gotd_secrets secrets;
static struct gotd_notify {
pid_t pid;
notify_http(struct gotd_notification_target *target, const char *repo,
const char *username, int fd)
{
+ const char *http_user = NULL, *http_pass = NULL, *hmac = NULL;
const char *argv[12];
int argc = 0;
argv[argc++] = target->conf.http.path;
argv[argc] = NULL;
+
+ if (target->conf.http.auth) {
+ http_user = target->conf.http.auth;
+ http_pass = gotd_secrets_get(&secrets, GOTD_SECRET_AUTH,
+ http_user);
+ }
+ if (target->conf.http.hmac) {
+ hmac = gotd_secrets_get(&secrets, GOTD_SECRET_HMAC,
+ target->conf.http.hmac);
+ }
run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd,
- target->conf.http.user, target->conf.http.password,
- target->conf.http.hmac_secret);
+ http_user, http_pass, hmac);
}
static const struct got_error *
ssize_t n;
int shut = 0;
struct imsg imsg;
+ struct ibuf ibuf;
+ struct gotd_secret *s;
+ int keylen, vallen;
+ char *key, *val;
if (event & EV_READ) {
if ((n = imsg_read(imsgbuf)) == -1 && errno != EAGAIN)
switch (imsg.hdr.type) {
case GOTD_IMSG_CONNECT_SESSION:
err = recv_session(&imsg);
+ break;
+ case GOTD_IMSG_SECRETS:
+ if (secrets.cap != 0)
+ fatal("unexpected GOTD_IMSG_SECRETS");
+ if (imsg_get_data(&imsg, &secrets.cap,
+ sizeof(secrets.cap)) == -1)
+ fatalx("corrupted GOTD_IMSG_SECRETS");
+ if (secrets.cap == 0)
+ break;
+ secrets.secrets = calloc(secrets.cap,
+ sizeof(*secrets.secrets));
+ if (secrets.secrets == NULL)
+ fatal("calloc");
break;
+ case GOTD_IMSG_SECRET:
+ if (secrets.len == secrets.cap)
+ fatalx("unexpected GOTD_SECRET_AUTH");
+ s = &secrets.secrets[secrets.len++];
+ if (imsg_get_ibuf(&imsg, &ibuf) == -1)
+ fatal("imsg_get_ibuf");
+ if (ibuf_get(&ibuf, &s->type, sizeof(s->type)) == -1 ||
+ ibuf_get(&ibuf, &keylen, sizeof(keylen)) == -1 ||
+ ibuf_get(&ibuf, &vallen, sizeof(vallen)) == -1 ||
+ keylen <= 0 || vallen <= 0 ||
+ ibuf_size(&ibuf) != (keylen + vallen) ||
+ (key = ibuf_data(&ibuf)) == NULL ||
+ (val = ibuf_seek(&ibuf, keylen, vallen)) == NULL ||
+ key[keylen - 1] != '\0' || val[vallen - 1] != '\0')
+ fatalx("corrupted GOTD_IMSG_SECRET");
+ s->key = strdup(key);
+ s->val = strdup(val);
+ if (s->key == NULL || s->val == NULL)
+ fatal("strdup");
+ break;
default:
log_debug("unexpected imsg %d", imsg.hdr.type);
break;
blob - da033ac49c81846361825b0bae646e833d0f11bb
blob + b48a3fe7e6a07d8747d390f34683097289a4d6c2
--- gotd/parse.y
+++ gotd/parse.y
#include "gotd.h"
#include "auth.h"
#include "listen.h"
+#include "secrets.h"
TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
static int conf_notify_email(struct gotd_repo *,
char *, char *, char *, char *, char *);
static int conf_notify_http(struct gotd_repo *,
- char *, char *, char *, int, char *);
+ char *, char *, char *, int);
static enum gotd_procid gotd_proc_id;
typedef struct {
%token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
%token RO RW CONNECTION LIMIT REQUEST TIMEOUT
%token PROTECT NAMESPACE BRANCH TAG REFERENCE RELAY PORT
-%token NOTIFY EMAIL FROM REPLY TO URL PASSWORD INSECURE HMAC
+%token NOTIFY EMAIL FROM REPLY TO URL INSECURE HMAC AUTH
%token <v.string> STRING
%token <v.number> NUMBER
gotd_proc_id == PROC_SESSION_WRITE ||
gotd_proc_id == PROC_NOTIFY) {
if (conf_notify_http(new_repo, $2, NULL,
- NULL, 0, NULL)) {
+ NULL, 0)) {
free($2);
YYERROR;
}
}
free($2);
}
- | URL STRING USER STRING PASSWORD STRING {
+ | URL STRING AUTH STRING {
if (gotd_proc_id == PROC_GOTD ||
gotd_proc_id == PROC_SESSION_WRITE ||
gotd_proc_id == PROC_NOTIFY) {
- if (conf_notify_http(new_repo, $2, $4, $6, 0,
- NULL)) {
+ if (conf_notify_http(new_repo, $2, $4, NULL,
+ 0)) {
free($2);
free($4);
- free($6);
YYERROR;
}
}
free($2);
free($4);
- free($6);
}
- | URL STRING USER STRING PASSWORD STRING INSECURE {
+ | URL STRING AUTH STRING INSECURE {
if (gotd_proc_id == PROC_GOTD ||
gotd_proc_id == PROC_SESSION_WRITE ||
gotd_proc_id == PROC_NOTIFY) {
- if (conf_notify_http(new_repo, $2, $4, $6, 1,
- NULL)) {
+ if (conf_notify_http(new_repo, $2, $4, NULL,
+ 1)) {
free($2);
free($4);
- free($6);
YYERROR;
}
}
free($2);
free($4);
- free($6);
}
| URL STRING HMAC STRING {
if (gotd_proc_id == PROC_GOTD ||
gotd_proc_id == PROC_SESSION_WRITE ||
gotd_proc_id == PROC_NOTIFY) {
- if (conf_notify_http(new_repo, $2, NULL,
- NULL, 0, $4)) {
+ if (conf_notify_http(new_repo, $2, NULL, $4,
+ 0)) {
free($2);
free($4);
YYERROR;
free($2);
free($4);
}
- | URL STRING USER STRING PASSWORD STRING HMAC STRING {
+ | URL STRING AUTH STRING HMAC STRING {
if (gotd_proc_id == PROC_GOTD ||
gotd_proc_id == PROC_SESSION_WRITE ||
gotd_proc_id == PROC_NOTIFY) {
- if (conf_notify_http(new_repo, $2, $4, $6, 0,
- $8)) {
+ if (conf_notify_http(new_repo, $2, $4, $6,
+ 0)) {
free($2);
free($4);
free($6);
- free($8);
YYERROR;
}
}
free($2);
free($4);
free($6);
- free($8);
}
- | URL STRING USER STRING PASSWORD STRING INSECURE HMAC STRING {
+ | URL STRING AUTH STRING INSECURE HMAC STRING {
if (gotd_proc_id == PROC_GOTD ||
gotd_proc_id == PROC_SESSION_WRITE ||
gotd_proc_id == PROC_NOTIFY) {
- if (conf_notify_http(new_repo, $2, $4, $6, 1,
- $9)) {
+ if (conf_notify_http(new_repo, $2, $4, $7,
+ 1)) {
free($2);
free($4);
- free($6);
- free($9);
+ free($7);
YYERROR;
}
}
free($2);
free($4);
- free($6);
- free($9);
+ free($7);
}
;
{
/* This has to be sorted always. */
static const struct keywords keywords[] = {
+ { "auth", AUTH },
{ "branch", BRANCH },
{ "connection", CONNECTION },
{ "deny", DENY },
{ "namespace", NAMESPACE },
{ "notify", NOTIFY },
{ "on", ON },
- { "password", PASSWORD },
{ "path", PATH },
{ "permit", PERMIT },
{ "port", PORT },
int
parse_config(const char *filename, enum gotd_procid proc_id,
- struct gotd *env)
+ struct gotd_secrets *secrets, struct gotd *env)
{
struct sym *sym, *next;
struct gotd_repo *repo;
gotd = env;
gotd_proc_id = proc_id;
+ gotd->secrets = secrets;
TAILQ_INIT(&gotd->repos);
/* Apply default values. */
}
static int
-conf_notify_http(struct gotd_repo *repo, char *url, char *user, char *password,
- int insecure, char *hmac_secret)
+conf_notify_http(struct gotd_repo *repo, char *url, char *auth, char *hmac,
+ int insecure)
{
const struct got_error *error;
struct gotd_notification_target *target;
}
}
- if ((user != NULL && password == NULL) ||
- (user == NULL && password != NULL)) {
- yyerror("missing username or password");
+ if (auth != NULL && gotd_proc_id == PROC_GOTD &&
+ (gotd->secrets == NULL || gotd_secrets_get(gotd->secrets,
+ GOTD_SECRET_AUTH, auth) == NULL)) {
+ yyerror("no auth secret `%s' defined", auth);
ret = -1;
goto done;
}
- if (!insecure && strcmp(proto, "http") == 0 &&
- (user != NULL || password != NULL)) {
+ if (hmac != NULL && gotd_proc_id == PROC_GOTD &&
+ (gotd->secrets == NULL && gotd_secrets_get(gotd->secrets,
+ GOTD_SECRET_HMAC, hmac) == NULL)) {
+ yyerror("no hmac secret `%s' defined", hmac);
+ ret = -1;
+ goto done;
+ }
+
+ if (!insecure && strcmp(proto, "http") == 0 && auth) {
yyerror("%s: HTTP notifications with basic authentication "
"over plaintext HTTP will leak credentials; add the "
"'insecure' config keyword if this is intentional", url);
target->conf.http.path = path;
hostname = port = path = NULL;
- if (user) {
- target->conf.http.user = strdup(user);
- if (target->conf.http.user == NULL)
- fatal("strdup");
- target->conf.http.password = strdup(password);
- if (target->conf.http.password == NULL)
+ if (auth) {
+ target->conf.http.auth = strdup(auth);
+ if (target->conf.http.auth == NULL)
fatal("strdup");
}
- if (hmac_secret) {
- target->conf.http.hmac_secret = strdup(hmac_secret);
- if (target->conf.http.hmac_secret == NULL)
+ if (hmac) {
+ target->conf.http.hmac = strdup(hmac);
+ if (target->conf.http.hmac == NULL)
fatal("strdup");
}
blob - /dev/null
blob + ed01d9084a794a49fa7bb2649a4a635bba6c744f (mode 644)
--- /dev/null
+++ gotd/secrets.c
+/*
+ * Copyright (c) 2024 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "got_error.h"
+
+#include "log.h"
+#include "secrets.h"
+
+static const struct got_error *
+push(struct gotd_secrets *s, const char *path, int lineno,
+ const char *type, const char *key, const char *val)
+{
+ size_t newcap, i;
+ void *t;
+
+ if (s->len == s->cap) {
+ newcap = s->cap + 16;
+ t = reallocarray(s->secrets, newcap, sizeof(*s->secrets));
+ if (t == NULL)
+ return got_error_from_errno("reallocarray");
+ s->secrets = t;
+ s->cap = newcap;
+ }
+
+ i = s->len;
+ if (!strcmp(type, "auth"))
+ s->secrets[i].type = GOTD_SECRET_AUTH;
+ else if (!strcmp(type, "hmac"))
+ s->secrets[i].type = GOTD_SECRET_HMAC;
+ else {
+ log_warnx("%s:%d invalid type %s", path, lineno, type);
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ }
+
+ if (gotd_secrets_get(s, s->secrets[i].type, key) != NULL) {
+ log_warnx("%s:%d duplicate %s entry %s", path, lineno,
+ type, key);
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ }
+
+ s->secrets[i].key = strdup(key);
+ if (s->secrets[i].key == NULL)
+ return got_error_from_errno("strdup");
+ s->secrets[i].val = strdup(val);
+ if (s->secrets[i].val == NULL)
+ return got_error_from_errno("strdup");
+
+ s->len++;
+ return NULL;
+}
+
+const struct got_error *
+gotd_secrets_parse(const char *path, FILE *fp, struct gotd_secrets **s)
+{
+ const struct got_error *err = NULL;
+ int lineno = 0;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t linelen;
+ char *type, *key, *val, *t;
+ struct gotd_secrets *secrets;
+
+ *s = NULL;
+
+ secrets = calloc(1, sizeof(*secrets));
+ if (secrets == NULL)
+ return got_error_from_errno("calloc");
+
+ while ((linelen = getline(&line, &linesize, fp)) != -1) {
+ lineno++;
+ if (line[linelen - 1] == '\n')
+ line[--linelen] = '\0';
+
+ if (*line == '\0' || *line == '#')
+ continue;
+
+ type = line;
+
+ key = type + strcspn(type, " \t");
+ *key++ = '\0';
+ key += strspn(key, " \t");
+
+ val = key + strcspn(key, " \t");
+ *val++ = '\0';
+ val += strspn(val, " \t");
+
+ t = val + strcspn(val, " \t");
+ if (*t != '\0') {
+ log_warnx("%s:%d malformed entry\n", path, lineno);
+ err = got_error(GOT_ERR_PARSE_CONFIG);
+ break;
+ }
+
+ err = push(secrets, path, lineno, type, key, val);
+ if (err)
+ break;
+ }
+ free(line);
+ if (ferror(fp) && err == NULL)
+ err = got_error_from_errno("getline");
+
+ if (err) {
+ gotd_secrets_free(secrets);
+ secrets = NULL;
+ }
+
+ *s = secrets;
+ return err;
+}
+
+const char *
+gotd_secrets_get(struct gotd_secrets *s, enum gotd_secret_type type,
+ const char *key)
+{
+ size_t i;
+
+ for (i = 0; i < s->len; ++i) {
+ if (s->secrets[i].type != type)
+ continue;
+ if (strcmp(s->secrets[i].key, key) != 0)
+ continue;
+ return s->secrets[i].val;
+ }
+
+ return NULL;
+}
+
+void
+gotd_secrets_free(struct gotd_secrets *s)
+{
+ size_t i;
+
+ for (i = 0; i < s->len; ++i) {
+ free(s->secrets[i].key);
+ free(s->secrets[i].val);
+ }
+
+ free(s);
+}
blob - /dev/null
blob + 5fd139e32a18178b1d1232c954b80219a532d5ce (mode 644)
--- /dev/null
+++ gotd/secrets.h
+/*
+ * Copyright (c) 2024 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+enum gotd_secret_type {
+ GOTD_SECRET_AUTH,
+ GOTD_SECRET_HMAC,
+};
+
+struct gotd_secret {
+ enum gotd_secret_type type;
+ char *key; /* label or username */
+ char *val; /* hmac secret or password */
+};
+
+struct gotd_secrets {
+ struct gotd_secret *secrets;
+ size_t len;
+ size_t cap;
+};
+
+const struct got_error *gotd_secrets_parse(const char *, FILE *,
+ struct gotd_secrets **);
+const char *gotd_secrets_get(struct gotd_secrets *, enum gotd_secret_type,
+ const char *);
+void gotd_secrets_free(struct gotd_secrets *);
blob - a460654e422802961de72d85be843313f25dc003
blob + 1efc9a36789275292bd8b1a0612e4d1844ceec7c
--- regress/gotd/.gitignore
+++ regress/gotd/.gitignore
gotd.conf
+gotd-secrets.conf
blob - 5bcb3d151e4aa620cbbc366ecb2783c7b851d6fa
blob + d2f5c834b32eb56747464e08d55c728712cec941
--- regress/gotd/Makefile
+++ regress/gotd/Makefile
@$(GOTD_TRAP); sleep .5
start_gotd_http_notification: ensure_root
+ @echo 'auth flan password' > $(PWD)/gotd-secrets.conf
@echo 'listen on "$(GOTD_SOCK)"' > $(PWD)/gotd.conf
@echo "user $(GOTD_USER)" >> $(PWD)/gotd.conf
@echo 'repository "test-repo" {' >> $(PWD)/gotd.conf
@echo ' path "$(GOTD_TEST_REPO)"' >> $(PWD)/gotd.conf
@echo ' permit rw $(GOTD_DEVUSER)' >> $(PWD)/gotd.conf
@echo ' notify {' >> $(PWD)/gotd.conf
- @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" user flan password "password" insecure' >> $(PWD)/gotd.conf
+ @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" auth flan insecure' >> $(PWD)/gotd.conf
@echo " }" >> $(PWD)/gotd.conf
@echo "}" >> $(PWD)/gotd.conf
- @$(GOTD_TRAP); $(GOTD_START_CMD)
+ @$(GOTD_TRAP); $(GOTD_START_CMD) -s $(PWD)/gotd-secrets.conf
@$(GOTD_TRAP); sleep .5
start_gotd_email_and_http_notification: ensure_root
+ @echo 'auth flan password' > $(PWD)/gotd-secrets.conf
@echo 'listen on "$(GOTD_SOCK)"' > $(PWD)/gotd.conf
@echo "user $(GOTD_USER)" >> $(PWD)/gotd.conf
@echo 'repository "test-repo" {' >> $(PWD)/gotd.conf
@echo ' path "$(GOTD_TEST_REPO)"' >> $(PWD)/gotd.conf
@echo ' permit rw $(GOTD_DEVUSER)' >> $(PWD)/gotd.conf
@echo ' notify {' >> $(PWD)/gotd.conf
- @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" user flan password "password" insecure' >> $(PWD)/gotd.conf
+ @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" auth flan insecure' >> $(PWD)/gotd.conf
@echo -n ' email to ${GOTD_DEVUSER}' >> $(PWD)/gotd.conf
@echo ' relay 127.0.0.1 port ${GOTD_TEST_SMTP_PORT}' >> $(PWD)/gotd.conf
@echo " }" >> $(PWD)/gotd.conf
@echo "}" >> $(PWD)/gotd.conf
- @$(GOTD_TRAP); $(GOTD_START_CMD)
+ @$(GOTD_TRAP); $(GOTD_START_CMD) -s $(PWD)/gotd-secrets.conf
@$(GOTD_TRAP); sleep .5
start_gotd_http_notification_hmac: ensure_root
+ @echo 'auth flan password' > $(PWD)/gotd-secrets.conf
+ @echo 'hmac flan ${GOTD_TEST_HMAC_SECRET}' >> $(PWD)/gotd-secrets.conf
@echo 'listen on "$(GOTD_SOCK)"' > $(PWD)/gotd.conf
@echo "user $(GOTD_USER)" >> $(PWD)/gotd.conf
@echo 'repository "test-repo" {' >> $(PWD)/gotd.conf
@echo ' path "$(GOTD_TEST_REPO)"' >> $(PWD)/gotd.conf
@echo ' permit rw $(GOTD_DEVUSER)' >> $(PWD)/gotd.conf
@echo ' notify {' >> $(PWD)/gotd.conf
- @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" user flan password "password" insecure hmac "${GOTD_TEST_HMAC_SECRET}"' >> $(PWD)/gotd.conf
+ @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" auth flan insecure hmac flan' >> $(PWD)/gotd.conf
@echo " }" >> $(PWD)/gotd.conf
@echo "}" >> $(PWD)/gotd.conf
- @$(GOTD_TRAP); $(GOTD_START_CMD)
+ @$(GOTD_TRAP); $(GOTD_START_CMD) -s $(PWD)/gotd-secrets.conf
@$(GOTD_TRAP); sleep .5
prepare_test_repo: ensure_root