commit 6e1b28b643862111cb5b8b916fef8249b1b36c97 from: Omar Polo date: Mon Sep 09 10:32:58 2024 UTC improve the gotd-secrets.conf syntax don't reuse the username as the label since it makes impossible to have multiple entries with the same username. ok stsp@ commit - 4d982e99055cf01430da8b639924c8b13726090e commit + 6e1b28b643862111cb5b8b916fef8249b1b36c97 blob - f64b1024e378845bb310d66748b3ffb736d8022a blob + 211ce127da138b7f0ae6451335215737fabc1c1a --- gitwrapper/gitwrapper.c +++ gitwrapper/gitwrapper.c @@ -55,9 +55,9 @@ #endif /* only needed to satisfy the linker */ -const char * +struct gotd_secret * gotd_secrets_get(struct gotd_secrets *secrets, enum gotd_secret_type t, - const char *key) + const char *label) { return NULL; } blob - 6b87a0e60a950f2a87d40202550a866c319f4d9d blob + 78e45385e723721f6e996d5f99378d14263aee4f --- gotd/gotd-secrets.conf.5 +++ gotd/gotd-secrets.conf.5 @@ -31,41 +31,31 @@ 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. +Empty lines are also ignored. .Pp -The entries have the following syntax: +Each entry is made by blanks-separated words. +Arguments containing whitespaces should be surrounded by single or double +quotes. .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: +The supported entries are: .Bl -tag -width Ds -.It Ic auth +.It Ic auth Ar label Ic user Ar user Ic password Ar password 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 +.It Ic hmac Ar label Ar secret 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 +A suitable +.Ar secret +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. +The +.Ar label +must be unique between entries with the same type +.Pq i.e. Ic auth No or Ic hmac . .Sh FILES .Bl -tag -width Ds -compact .It Pa /etc/gotd-secrets.conf @@ -78,7 +68,7 @@ This example configuration defines two secrets, the fi HTTP authentication and the second for HMAC signing. .Bd -literal -offset indent # /etc/gotd-secrets.conf -auth flan super-strong-password! +auth mochi user "flan" password "super-strong-password!" hmac hacker q0tcl8QhjYs7U75MW/2rwB30CpdbAhONkfLGxFHm/+8= .Ed .Pp @@ -87,14 +77,12 @@ These values can be referenced in as: .Bd -literal -offset indent # /etc/gotd.conf -repository "openbsd/ports" { - path "/var/git/ports.git" - permit rw :porters - permit ro anonymous +repository "openbsd/src" { + path "/var/git/src.git" + permit rw :hackers notify { - url https://flan.com/notify/ auth flan - url https://hacker.com/notify/ hmac hacker + url https://flan.com/ci/ auth mochi hmac hacker } } .El blob - f5488c6250b396af6322ec60a1446b0adc75e0a0 blob + dae76c4625082615a1525efca313529fb5221c27 --- gotd/gotd.c +++ gotd/gotd.c @@ -2411,27 +2411,23 @@ main(int argc, char **argv) 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[1].iov_base = s->label; + iov[1].iov_len = strlen(s->label) + 1; - iov[2].iov_base = &vallen; - iov[2].iov_len = sizeof(vallen); + iov[2].iov_base = s->user; + iov[2].iov_len = s->user ? strlen(s->user) + 1 : 0 ; - iov[3].iov_base = s->key; - iov[3].iov_len = keylen; + iov[3].iov_base = s->pass; + iov[3].iov_len = s->pass ? strlen(s->pass) + 1 : 0 ; - iov[4].iov_base = s->val; - iov[4].iov_len = vallen; + iov[4].iov_base = s->hmac; + iov[4].iov_len = s->hmac ? strlen(s->hmac) + 1 : 0 ; if (imsg_composev(imsgbuf, GOTD_IMSG_SECRET, 0, 0, -1, iov, 5) == -1) blob - 08fee725f18dbcca82f68aba86a3132f5368fda6 blob + 6c4f25f2f3c6e6277708d4483155e5783e9a8b49 --- gotd/notify.c +++ gotd/notify.c @@ -276,6 +276,7 @@ static void notify_http(struct gotd_notification_target *target, const char *repo, const char *username, int fd) { + struct gotd_secret *secret; const char *http_user = NULL, *http_pass = NULL, *hmac = NULL; const char *argv[12]; int argc = 0; @@ -298,13 +299,15 @@ notify_http(struct gotd_notification_target *target, c 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); + secret = gotd_secrets_get(&secrets, GOTD_SECRET_AUTH, + target->conf.http.auth); + http_user = secret->user; + http_pass = secret->pass; } if (target->conf.http.hmac) { - hmac = gotd_secrets_get(&secrets, GOTD_SECRET_HMAC, + secret = gotd_secrets_get(&secrets, GOTD_SECRET_HMAC, target->conf.http.hmac); + hmac = secret->hmac; } run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd, @@ -466,7 +469,34 @@ recv_session(struct imsg *imsg) return NULL; } + +static const struct got_error * +notify_ibuf_get_str(char **ret, struct ibuf *ibuf) +{ + const char *str, *end; + size_t len; + + *ret = NULL; + str = ibuf_data(ibuf); + len = ibuf_size(ibuf); + + end = memchr(str, '\0', len); + if (end == NULL) + return got_error(GOT_ERR_PRIVSEP_LEN); + *ret = strdup(str); + if (*ret == NULL) + return got_error_from_errno("strdup"); + + if (ibuf_skip(ibuf, end - str + 1) == -1) { + free(*ret); + *ret = NULL; + return got_error(GOT_ERR_PRIVSEP_LEN); + } + + return NULL; +} + static void notify_dispatch(int fd, short event, void *arg) { @@ -477,8 +507,6 @@ notify_dispatch(int fd, short event, void *arg) 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) @@ -532,19 +560,26 @@ notify_dispatch(int fd, short event, void *arg) 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') + if (ibuf_get(&ibuf, &s->type, sizeof(s->type)) == -1) fatalx("corrupted GOTD_IMSG_SECRET"); - s->key = strdup(key); - s->val = strdup(val); - if (s->key == NULL || s->val == NULL) - fatal("strdup"); + err = notify_ibuf_get_str(&s->label, &ibuf); + if (err) + break; + if (s->type == GOTD_SECRET_AUTH) { + err = notify_ibuf_get_str(&s->user, &ibuf); + if (err) + break; + err = notify_ibuf_get_str(&s->pass, &ibuf); + if (err) + break; + } else { + err = notify_ibuf_get_str(&s->hmac, &ibuf); + if (err) + break; + } + if (ibuf_size(&ibuf) != 0) + fatalx("unexpected extra data in " + "GOTD_IMSG_SECRET"); break; default: log_debug("unexpected imsg %d", imsg.hdr.type); blob - 3645645467b19a36a03d4f527df5743da2de2d17 blob + 3407009d5a8a939edea8ac031e24af4b599e567c --- gotd/secrets.c +++ gotd/secrets.c @@ -25,8 +25,8 @@ #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) +push(struct gotd_secrets *s, enum gotd_secret_type type, const char *label, + const char *user, const char *pass, const char *hmac) { size_t newcap, i; void *t; @@ -41,30 +41,175 @@ push(struct gotd_secrets *s, const char *path, int lin } 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; + memset(&s->secrets[i], 0, sizeof(s->secrets[i])); + s->secrets[i].type = type; + s->secrets[i].label = strdup(label); + if (s->secrets[i].label == NULL) + return got_error_from_errno("strdup"); + + if (type == GOTD_SECRET_AUTH) { + s->secrets[i].user = strdup(user); + if (s->secrets[i].user == NULL) + return got_error_from_errno("strdup"); + s->secrets[i].pass = strdup(pass); + if (s->secrets[i].pass == NULL) + return got_error_from_errno("strdup"); + } else { + s->secrets[i].hmac = strdup(hmac); + if (s->secrets[i].hmac == NULL) + return got_error_from_errno("strdup"); + } + + s->len++; + return NULL; +} + +static char * +read_word(char **word, const char *path, int lineno, char *s) +{ + char *p, quote = 0; + int escape = 0; + + s += strspn(s, " \t"); + if (*s == '\0') { + log_warnx("%s:%d syntax error", path, lineno); + return NULL; + } + *word = s; + + p = s; + while (*s) { + if (escape) { + escape = 0; + *p++ = *s++; + continue; + } + + if (*s == '\\') { + escape = 1; + s++; + continue; + } + + if (*s == quote) { + quote = 0; + s++; + continue; + } + + if (*s == '\'' || *s == '\"') { + quote = *s; + s++; + continue; + } + + if (!quote && (*s == ' ' || *s == '\t')) { + *p = '\0'; + return s + 1; + } + + *p++ = *s++; + } + + if (quote) { + log_warnx("%s:%d no closing quote", path, lineno); + return NULL; + } + + if (escape) { + log_warnx("%s:%d unterminated escape at end of line", + path, lineno); + return NULL; + } + + *p = '\0'; + return s; +} + +static char * +read_keyword(char **kw, const char *path, int lineno, char *s) +{ + s += strspn(s, " \t"); + if (*s == '\0') { + log_warnx("%s:%d syntax error", path, lineno); + return NULL; + } + *kw = s; + + s += strcspn(s, " \t"); + if (*s != '\0') + *s++ = '\0'; + return s; +} + +static const struct got_error * +parse_line(struct gotd_secrets *secrets, const char *path, int lineno, + char *line) +{ + char *kw, *label; + char *user = NULL, *pass = NULL, *hmac = NULL; + enum gotd_secret_type type; + + line = read_keyword(&kw, path, lineno, line); + if (line == NULL) + return got_error(GOT_ERR_PARSE_CONFIG); + + if (!strcmp(kw, "auth")) + type = GOTD_SECRET_AUTH; + else if (!strcmp(kw, "hmac")) + type = GOTD_SECRET_HMAC; else { - log_warnx("%s:%d invalid type %s", path, lineno, type); + log_warnx("%s:%d syntax error", path, lineno); 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); + line = read_word(&label, path, lineno, line); + if (line == NULL) return got_error(GOT_ERR_PARSE_CONFIG); + + if (type == GOTD_SECRET_AUTH) { + line = read_keyword(&kw, path, lineno, line); + if (line == NULL) + return got_error(GOT_ERR_PARSE_CONFIG); + if (strcmp(kw, "user") != 0) { + log_warnx("%s:%d syntax error", path, lineno); + return got_error(GOT_ERR_PARSE_CONFIG); + } + + line = read_word(&user, path, lineno, line); + if (line == NULL) + return got_error(GOT_ERR_PARSE_CONFIG); + + line = read_keyword(&kw, path, lineno, line); + if (line == NULL) + return got_error(GOT_ERR_PARSE_CONFIG); + if (strcmp(kw, "password") != 0) { + log_warnx("%s:%d syntax error", path, lineno); + return got_error(GOT_ERR_PARSE_CONFIG); + } + + line = read_word(&pass, path, lineno, line); + if (line == NULL) + return got_error(GOT_ERR_PARSE_CONFIG); + } else { + line = read_word(&hmac, path, lineno, line); + if (line == NULL) + 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"); + line += strspn(line, " \t"); + if (*line != '\0') { + log_warnx("%s:%d syntax error", path, lineno); + return got_error(GOT_ERR_PARSE_CONFIG); + } - s->len++; - return NULL; + if (gotd_secrets_get(secrets, type, label) != NULL) { + log_warnx("%s:%d duplicate %s entry %s", path, lineno, + type == GOTD_SECRET_AUTH ? "auth" : "hmac", label); + return got_error(GOT_ERR_PARSE_CONFIG); + } + + return push(secrets, type, label, user, pass, hmac); } const struct got_error * @@ -75,7 +220,7 @@ gotd_secrets_parse(const char *path, FILE *fp, struct char *line = NULL; size_t linesize = 0; ssize_t linelen; - char *type, *key, *val, *t; + char *t; struct gotd_secrets *secrets; *s = NULL; @@ -89,27 +234,13 @@ gotd_secrets_parse(const char *path, FILE *fp, struct if (line[linelen - 1] == '\n') line[--linelen] = '\0'; - if (*line == '\0' || *line == '#') + for (t = line; *t == ' ' || *t == '\t'; ++t) + /* nop */ ; + + if (*t == '\0' || *t == '#') 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); + err = parse_line(secrets, path, lineno, t); if (err) break; } @@ -126,18 +257,18 @@ gotd_secrets_parse(const char *path, FILE *fp, struct return err; } -const char * +struct gotd_secret * gotd_secrets_get(struct gotd_secrets *s, enum gotd_secret_type type, - const char *key) + const char *label) { 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) + if (strcmp(s->secrets[i].label, label) != 0) continue; - return s->secrets[i].val; + return &s->secrets[i]; } return NULL; @@ -152,8 +283,10 @@ gotd_secrets_free(struct gotd_secrets *s) return; for (i = 0; i < s->len; ++i) { - free(s->secrets[i].key); - free(s->secrets[i].val); + free(s->secrets[i].label); + free(s->secrets[i].user); + free(s->secrets[i].pass); + free(s->secrets[i].hmac); } free(s); blob - 5fd139e32a18178b1d1232c954b80219a532d5ce blob + c7aae88890d730e3497720296c513e7c2e639c1f --- gotd/secrets.h +++ gotd/secrets.h @@ -21,8 +21,10 @@ enum gotd_secret_type { struct gotd_secret { enum gotd_secret_type type; - char *key; /* label or username */ - char *val; /* hmac secret or password */ + char *label; + char *user; + char *pass; + char *hmac; }; struct gotd_secrets { @@ -33,6 +35,6 @@ struct gotd_secrets { 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 *); +struct gotd_secret *gotd_secrets_get(struct gotd_secrets *, + enum gotd_secret_type, const char *); void gotd_secrets_free(struct gotd_secrets *); blob - c3b16b4b439df9cf2fee1a80ae0caffffa7ab0de blob + 667368ea6d53f44cbd7bd376394d98cf5d3c8ad0 --- regress/gotd/Makefile +++ regress/gotd/Makefile @@ -75,7 +75,7 @@ ensure_root: fi ensure_secrets: - @echo 'auth flan password' > $(PWD)/gotd-secrets.conf + @echo 'auth flan user flan password password' > $(PWD)/gotd-secrets.conf @echo 'hmac flan ${GOTD_TEST_HMAC_SECRET}' >> $(PWD)/gotd-secrets.conf @chown root:0 $(PWD)/gotd-secrets.conf @chmod 600 $(PWD)/gotd-secrets.conf blob - /dev/null blob + fa38aa565b73c665735383b9d5e89ab892024296 (mode 644) --- /dev/null +++ regress/secrets/01.conf @@ -0,0 +1,7 @@ +# comment + # another comment + +hmac mochi MWZJblv6BNcmq4maua3XxV0VhsiVzNPXy4NdOdliD/o= + +auth mochi user "flan" password s3cr37! + blob - /dev/null blob + e2bb78be82355cb00eafe6ba3b0e84c160ae71a5 (mode 644) --- /dev/null +++ regress/secrets/01.exp @@ -0,0 +1,2 @@ +hmac mochi MWZJblv6BNcmq4maua3XxV0VhsiVzNPXy4NdOdliD/o= +auth mochi user flan password s3cr37! blob - /dev/null blob + 022028b8dd8473cef44adda54fb260d5f9fc9636 (mode 644) --- /dev/null +++ regress/secrets/02.conf @@ -0,0 +1,2 @@ +auth "mochi" user fl"a"'n' password "hello world"' !' +auth mochi2 user flan password hello\ world\ \"! blob - /dev/null blob + 1afaa89044652a0e501b2126a22ade9a12df920e (mode 644) --- /dev/null +++ regress/secrets/02.exp @@ -0,0 +1,2 @@ +auth mochi user flan password hello world ! +auth mochi2 user flan password hello world "! blob - /dev/null blob + 6d6bea38ab10b382b0a4f3edb21447810face661 (mode 644) --- /dev/null +++ regress/secrets/03.conf @@ -0,0 +1 @@ +auth mochi user flan password "foo blob - /dev/null blob + 4283ca13d5e67b815966c80be3bef7c230110e8e (mode 644) --- /dev/null +++ regress/secrets/04.conf @@ -0,0 +1 @@ +hmac mochi foo\ blob - /dev/null blob + 4283ca13d5e67b815966c80be3bef7c230110e8e (mode 644) --- /dev/null +++ regress/secrets/05.conf @@ -0,0 +1 @@ +hmac mochi foo\ blob - /dev/null blob + a7171f20953ee46932809c2cdb7deac376f5d7ee (mode 644) --- /dev/null +++ regress/secrets/Makefile @@ -0,0 +1,33 @@ +.PATH:${.CURDIR}/../../gotd/ +.PATH:${.CURDIR}/../../lib/ + +PROG = secrets +SRCS = secrets-parser.c error.c hash.c log.c secrets.c + +CPPFLAGS += -I${.CURDIR}/../../include -I${.CURDIR}/../../lib +CPPFLAGS += -I${.CURDIR}/../../gotd + +REGRESS_TARGETS = empty comments quotes unclosed invalid-escape syntax + +empty: + ./secrets /dev/null | diff -u /dev/null - + +comments: + ./secrets ${.CURDIR}/01.conf | diff -u ${.CURDIR}/01.exp - + +quotes: + ./secrets ${.CURDIR}/02.conf | diff -u ${.CURDIR}/02.exp - + +unclosed: + ! ./secrets ${.CURDIR}/03.conf + @echo "expected failure; it's OK" + +invalid-escape: + ! ./secrets ${.CURDIR}/04.conf + @echo "expected failure; it's OK" + +syntax: + ! ./secrets ${.CURDIR}/05.conf + @echo "expected failure; it's OK" + +.include blob - /dev/null blob + 94fc117c61f36b5b7a021f2bf1c537b13e3b2555 (mode 644) --- /dev/null +++ regress/secrets/secrets-parser.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 Omar Polo + * + * 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 +#include +#include +#include +#include +#include + +#include "got_error.h" + +#include "log.h" +#include "secrets.h" + +static void __dead +usage(void) +{ + fprintf(stderr, "usage: %s [-v] file\n", getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + const struct got_error *error; + struct gotd_secrets *secrets; + struct gotd_secret *secret; + FILE *fp; + size_t i; + int ch, verbose = 0; + + if (pledge("stdio rpath", NULL) == -1) + err(1, "pledge"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + verbose = 1; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + log_init(1, LOG_USER); + log_procinit("secrets"); + log_setverbose(verbose); + + if ((fp = fopen(argv[0], "r")) == NULL) + err(1, "can't open %s", argv[0]); + + if ((error = gotd_secrets_parse(argv[0], fp, &secrets)) != NULL) + errx(1, "failed to parse %s: %s", argv[0], error->msg); + + for (i = 0; i < secrets->len; ++i) { + secret = &secrets->secrets[i]; + + if (secret->type == GOTD_SECRET_AUTH) { + printf("auth %s user %s password %s\n", + secret->label, secret->user, secret->pass); + } else { + printf("hmac %s %s\n", secret->label, secret->hmac); + } + } + + gotd_secrets_free(secrets); + return 0; +}