From: Stefan Sperling Subject: Re: got-notify-http: implement basic auth To: Omar Polo Cc: gameoftrees@openbsd.org Date: Tue, 16 Apr 2024 12:22:53 +0200 On Tue, Apr 16, 2024 at 10:11:53AM +0200, Omar Polo wrote: > On 2024/04/16 01:25:04 +0200, Omar Polo wrote: > > with this the authentication is properly implemented. I'm unsure how to > > plug it in the regress, so for the moment no tests. > > here's an improved diff without extra include and with the regress too. > It makes the log output more verbose as a drawback though: > > : gotd: gotd: WARNING: Using basic authentication over plaintext http:// > : will leak credentials; https:// is recommended for URL > : 'http://localhost:8000/' Looks good to me, ok stsp@ One thing I don't quite understand is how our regress verifies authentication. The HTTP server seems to compare the auth token to a known base64 encoded value? Could we use some Perl module to check whether a real web server will accept the token we send? > diff /home/op/w/got > commit - c1003102a22a77d068a14f9ffa7877f67c28e95d > path + /home/op/w/got > blob - 9c8a90e392c27e03eb1dd32a88414aa62a355012 > file + gotd/libexec/got-notify-http/got-notify-http.c > --- gotd/libexec/got-notify-http/got-notify-http.c > +++ gotd/libexec/got-notify-http/got-notify-http.c > @@ -730,17 +730,70 @@ jsonify(FILE *fp, const char *repo) > return 0; > } > > +static char > +sixet2ch(int c) > +{ > + c &= 0x3F; > + > + if (c < 26) > + return 'A' + c; > + c -= 26; > + if (c < 26) > + return 'a' + c; > + c -= 26; > + if (c < 10) > + return '0' + c; > + c -= 10; > + if (c == 0) > + return '+'; > + if (c == 1) > + return '/'; > + > + errx(1, "invalid sixet 0x%x", c); > +} > + > static char * > basic_auth(const char *username, const char *password) > { > - char *tmp; > - int len; > + char *str, *tmp, *end, *s, *p; > + char buf[3]; > + int len, i, r; > > - len = asprintf(&tmp, "%s:%s", username, password); > - if (len == -1) > + r = asprintf(&str, "%s:%s", username, password); > + if (r == -1) > err(1, "asprintf"); > > - /* XXX base64-ify */ > + /* > + * Will need 4 * r/3 bytes to encode the string, plus a > + * rounding to the next multiple of 4 for padding, plus NUL. > + */ > + len = 4 * r / 3; > + len = (len + 3) & ~3; > + len++; > + > + tmp = calloc(1, len); > + if (tmp == NULL) > + err(1, "malloc"); > + > + s = str; > + p = tmp; > + while (*s != '\0') { > + memset(buf, 0, sizeof(buf)); > + for (i = 0; i < 3 && *s != '\0'; ++i, ++s) > + buf[i] = *s; > + > + *p++ = sixet2ch(buf[0] >> 2); > + *p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4)); > + if (i > 1) > + *p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6)); > + if (i > 2) > + *p++ = sixet2ch(buf[2]); > + } > + > + for (end = tmp + len - 1; p < end; ++p) > + *p = '='; > + > + free(str); > return tmp; > } > > blob - 682368c09afd8f26371f3c0eb5b63f700ac39026 > file + gotd/notify.c > --- gotd/notify.c > +++ gotd/notify.c > @@ -161,7 +161,8 @@ gotd_notify_sighdlr(int sig, short event, void *arg) > } > > static void > -run_notification_helper(const char *prog, const char **argv, int fd) > +run_notification_helper(const char *prog, const char **argv, int fd, > + const char *user, const char *pass) > { > const struct got_error *err = NULL; > pid_t pid; > @@ -185,6 +186,11 @@ run_notification_helper(const char *prog, const char * > > closefrom(STDERR_FILENO + 1); > > + if (user != NULL && pass != NULL) { > + setenv("GOT_NOTIFY_HTTP_USER", user, 1); > + setenv("GOT_NOTIFY_HTTP_PASS", pass, 1); > + } > + > if (execv(prog, (char *const *)argv) == -1) { > fprintf(stderr, "%s: exec %s: %s\n", getprogname(), > prog, strerror(errno)); > @@ -249,7 +255,8 @@ notify_email(struct gotd_notification_target *target, > > argv[i] = NULL; > > - run_notification_helper(GOTD_PATH_PROG_NOTIFY_EMAIL, argv, fd); > + run_notification_helper(GOTD_PATH_PROG_NOTIFY_EMAIL, argv, fd, > + NULL, NULL); > } > > static void > @@ -273,7 +280,8 @@ notify_http(struct gotd_notification_target *target, c > > argv[argc] = NULL; > > - run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd); > + run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd, > + target->conf.http.user, target->conf.http.password); > } > > static const struct got_error * > blob - 0dee7475233829d5d5fc2cc3f028cfc7789ce428 > file + regress/gotd/Makefile > --- regress/gotd/Makefile > +++ regress/gotd/Makefile > @@ -188,7 +188,7 @@ start_gotd_http_notification: ensure_root > @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}/"' >> $(PWD)/gotd.conf > + @echo ' url "http://localhost:${GOTD_TEST_HTTP_PORT}/" user flan password "password"' >> $(PWD)/gotd.conf > @echo " }" >> $(PWD)/gotd.conf > @echo "}" >> $(PWD)/gotd.conf > @$(GOTD_TRAP); $(GOTD_START_CMD) > blob - 5cbff3264bfe341856058856515d41c34ed996bf > file + regress/gotd/http-server > --- regress/gotd/http-server > +++ regress/gotd/http-server > @@ -18,10 +18,11 @@ use v5.36; > use IPC::Open2; > use Getopt::Long qw(:config bundling); > > +my $auth; > my $port = 8000; > > -GetOptions("p:i" => \$port) > - or die("usage: $0 [-p port]\n"); > +GetOptions("a:s" => \$auth, "p:i" => \$port) > + or die("usage: $0 [-a auth] [-p port]\n"); > > my $pid = open2(my $out, my $in, 'nc', '-l', 'localhost', $port); > > @@ -61,6 +62,15 @@ while (<$out>) { > unless m/Connection: close$/; > next; > } > + > + if (m/Authorization/) { > + die "bad authorization header" > + unless m/Authorization: basic (.*)$/; > + my $t = $1; > + die "wrong authorization; got $t want $auth" > + if not defined($auth) or $auth ne $t; > + next; > + } > } > > die "no Content-Length header" unless defined $clen; > blob - 38661743a189d324f50db59d65ebfb278ed84eff > file + regress/gotd/http_notification.sh > --- regress/gotd/http_notification.sh > +++ regress/gotd/http_notification.sh > @@ -17,6 +17,9 @@ > . ../cmdline/common.sh > . ./common.sh > > +# flan:password encoded in base64 > +AUTH="ZmxhbjpwYXNzd29yZA==" > + > test_file_changed() { > local testroot=`test_init file_changed 1` > > @@ -41,7 +44,7 @@ test_file_changed() { > local commit_id=`git_show_head $testroot/repo-clone` > local author_time=`git_show_author_time $testroot/repo-clone` > > - timeout 5 ./http-server -p $GOTD_TEST_HTTP_PORT \ > + timeout 5 ./http-server -a $AUTH -p $GOTD_TEST_HTTP_PORT \ > > $testroot/stdout & > > got send -b main -q -r $testroot/repo-clone > @@ -134,7 +137,7 @@ test_bad_utf8() { > local commit_id=`git_show_head $testroot/repo-clone` > local author_time=`git_show_author_time $testroot/repo-clone` > > - timeout 5 ./http-server -p $GOTD_TEST_HTTP_PORT \ > + timeout 5 ./http-server -a $AUTH -p $GOTD_TEST_HTTP_PORT \ > > $testroot/stdout & > > got send -b main -q -r $testroot/repo-clone > @@ -229,7 +232,7 @@ test_many_commits_not_summarized() { > set -- "$@" "$commit_id $d" > done > > - timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \ > + timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \ > > $testroot/stdout & > > got send -b main -q -r $testroot/repo-clone > @@ -334,7 +337,7 @@ test_many_commits_summarized() { > set -- "$@" "$short_commit_id $d" > done > > - timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \ > + timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \ > > $testroot/stdout & > > got send -b main -q -r $testroot/repo-clone > @@ -414,7 +417,7 @@ test_branch_created() { > local commit_id=`git_show_branch_head $testroot/repo-clone newbranch` > local author_time=`git_show_author_time $testroot/repo-clone $commit_id` > > - timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \ > + timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \ > > $testroot/stdout & > > got send -b newbranch -q -r $testroot/repo-clone > @@ -501,7 +504,7 @@ test_branch_removed() { > return 1 > fi > > - timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \ > + timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \ > > $testroot/stdout & > > local commit_id=`git_show_branch_head $testroot/repo-clone newbranch` > @@ -556,7 +559,7 @@ test_tag_created() { > local commit_id=`git_show_head $testroot/repo-clone` > local tagger_time=`git_show_tagger_time $testroot/repo-clone 1.0` > > - timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \ > + timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \ > >$testroot/stdout & > > got send -t 1.0 -q -r $testroot/repo-clone > @@ -634,7 +637,7 @@ test_tag_changed() { > got tag -r $testroot/repo-clone -m "new tag" 1.0 > /dev/null > local tagger_time=`git_show_tagger_time $testroot/repo-clone 1.0` > > - timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \ > + timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \ > > $testroot/stdout & > > got send -f -t 1.0 -q -r $testroot/repo-clone > >