From: Stefan Sperling Subject: Re: gotwebd website support To: Omar Polo , gameoftrees@openbsd.org Date: Fri, 12 Dec 2025 11:56:55 +0100 On Tue, Dec 09, 2025 at 01:27:39PM +0100, Stefan Sperling wrote: > Thinking about this some more, I think you are right that we should > allow authentication for websites to be configured by the user. > > The website and authentication features are orthogonal, i.e. there are > no bad side effects from flipping them on/off independently. > > When authentication settings are inherited from global or server scope, > not having them apply inside a website block might be surprising. When > authentication is enabled globally, we will probably be going to meet > user expectations better when they have to type 'disable authentication' > explicitly before a website is exposed. > > And there will probably be use cases for having authentication enabled > for websites. E.g. blocking crawlers temporarily, reviewing websites > that are still in draft stage among a private group of people, and > restricting access to private information related to a project where > this information is displayed on a website for convenience. > > There would be little maintenance effort for us to support this. > We could make website blocks embed 'enable authentication' and 'disable > authentication' statements much like repository config blocks do, using > the same inheritance semantics. Diff for the above. ok? M gotwebd/auth.c | 26+ 16- M gotwebd/gotweb.c | 19+ 18- M gotwebd/gotwebd.c | 6+ 1- M gotwebd/gotwebd.conf.5 | 47+ 4- M gotwebd/gotwebd.h | 3+ 0- M gotwebd/parse.y | 40+ 0- M regress/gotsysd/Makefile | 5+ 3- M regress/gotsysd/test_gotwebd_repos_www.sh | 1+ 1- M regress/gotsysd/test_gotwebd_www.sh | 1+ 1- 9 files changed, 148 insertions(+), 44 deletions(-) commit - e3a6b29ab4d1ac8c2af797c491197fa1761414d6 commit + a6047d4198967b18351298ba2c54d7274f2f5cbe blob - 344a34affb3db35788b15ab660e56f5113996bf7 blob + 7a7f27f7ca9352028b2f3268554e61dbcd06b73e --- gotwebd/auth.c +++ gotwebd/auth.c @@ -451,6 +451,7 @@ process_request(struct request *c) struct website *site; struct gotwebd_repo *repo = NULL; enum gotwebd_auth_config auth_config; + struct gotwebd_access_rule_list *access_rules = NULL; char *hostname = NULL; const char *identifier = NULL; char *request_path = NULL; @@ -478,16 +479,6 @@ process_request(struct request *c) return; } - /* Web site content is not protected by authentication either. */ - if (site) { - /* Ignore the querystring while serving web sites. */ - fcgi_init_querystring(&c->fcgi_params.qs); - - forward_request(c); - free(request_path); - return; - } - free(request_path); request_path = NULL; @@ -496,9 +487,17 @@ process_request(struct request *c) if (c->fcgi_params.qs.path[0] != '\0') repo = gotweb_get_repository(srv, c->fcgi_params.qs.path); - if (repo) + if (repo) { auth_config = repo->auth_config; + access_rules = &repo->access_rules; + } else if (site) { + auth_config = site->auth_config; + access_rules = &site->access_rules; + /* Ignore the querystring while serving web sites. */ + fcgi_init_querystring(&c->fcgi_params.qs); + } + switch (auth_config) { case GOTWEBD_AUTH_SECURE: case GOTWEBD_AUTH_INSECURE: @@ -533,8 +532,8 @@ process_request(struct request *c) goto done; } - if (repo) { - switch (auth_check(&identifier, uid, &repo->access_rules)) { + if (access_rules) { + switch (auth_check(&identifier, uid, access_rules)) { case GOTWEBD_ACCESS_DENIED: error = got_error_msg(GOT_ERR_LOGIN_FAILED, "permission denied"); @@ -920,6 +919,8 @@ auth_dispatch_main(int fd, short event, void *arg) struct gotwebd *env = gotwebd_env; struct server *srv; struct gotwebd_repo *repo; + struct got_pathlist_entry *pe; + struct website *site; ssize_t n; int shut = 0; @@ -951,9 +952,18 @@ auth_dispatch_main(int fd, short event, void *arg) } else { srv = TAILQ_LAST(&env->servers, serverlist); if (TAILQ_EMPTY(&srv->repos)) { - /* per-server access rule */ - config_get_access_rule( - &srv->access_rules, &imsg); + if (RB_EMPTY(&srv->websites)) { + /* per-server access rule */ + config_get_access_rule( + &srv->access_rules, &imsg); + } else { + pe = RB_MAX(got_pathlist_head, + &srv->websites); + site = pe->data; + /* per-website access rule */ + config_get_access_rule( + &site->access_rules, &imsg); + } } else { /* per-repository access rule */ repo = TAILQ_LAST(&srv->repos, blob - 4811204b8543b0f9a67166dafc4273eb9a04a90b blob + d6636988f10f93783a22fe701285ef4471fc9a80 --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -57,7 +57,7 @@ static int gotweb_render_index(struct template *); static const struct got_error *gotweb_load_got_path(struct repo_dir **, - const char *, struct request *, int); + const char *, struct request *, struct website *); static const struct got_error *gotweb_load_file(char **, const char *, const char *, int); static const struct got_error *gotweb_get_repo_description(char **, @@ -751,7 +751,8 @@ gotweb_process_request(struct request *c) } if (site) { - error = gotweb_load_got_path(&repo_dir, site->repo_name, c, -1); + error = gotweb_load_got_path(&repo_dir, site->repo_name, c, + site); c->t->repo_dir = repo_dir; if (error) goto err; @@ -785,7 +786,7 @@ gotweb_process_request(struct request *c) goto err; } - error = gotweb_load_got_path(&repo_dir, qs->path, c, 0); + error = gotweb_load_got_path(&repo_dir, qs->path, c, NULL); c->t->repo_dir = repo_dir; if (error) goto err; @@ -1243,7 +1244,7 @@ gotweb_render_index(struct template *tp) } error = gotweb_load_got_path(&repo_dir, sd_dent[d_i]->d_name, - c, 0); + c, NULL); if (error) { if (error->code != GOT_ERR_NOT_GIT_REPO) log_warnx("%s: %s: %s", __func__, @@ -1541,7 +1542,7 @@ auth_check(struct request *c, struct gotwebd_access_ru static const struct got_error * gotweb_load_got_path(struct repo_dir **rp, const char *dir, - struct request *c, int requesting_website) + struct request *c, struct website *site) { const struct got_error *error = NULL; struct gotwebd *env = gotwebd_env; @@ -1552,6 +1553,7 @@ gotweb_load_got_path(struct repo_dir **rp, const char char *dir_test; struct gotwebd_repo *repo; enum gotwebd_auth_config auth_config = 0; + struct gotwebd_access_rule_list *access_rules = NULL; enum gotwebd_access access = GOTWEBD_ACCESS_DENIED; int repo_is_hidden = 0; @@ -1598,20 +1600,23 @@ gotweb_load_got_path(struct repo_dir **rp, const char goto err; repo = gotweb_get_repository(srv, repo_dir->name); - if (repo) { - repo_is_hidden = repo->hidden; - auth_config = repo->auth_config; + if (repo || site) { + if (repo) { + repo_is_hidden = repo->hidden; + auth_config = repo->auth_config; + access_rules = &repo->access_rules; + } else { + auth_config = site->auth_config; + access_rules = &site->access_rules; + } + switch (auth_config) { case GOTWEBD_AUTH_DISABLED: access = GOTWEBD_ACCESS_PERMITTED; break; case GOTWEBD_AUTH_SECURE: case GOTWEBD_AUTH_INSECURE: - if (requesting_website) { - access = GOTWEBD_ACCESS_PERMITTED; - break; - } - access = auth_check(c, &repo->access_rules); + access = auth_check(c, access_rules); if (access == GOTWEBD_ACCESS_NO_MATCH) access = auth_check(c, &srv->access_rules); if (access == GOTWEBD_ACCESS_NO_MATCH) @@ -1629,10 +1634,6 @@ gotweb_load_got_path(struct repo_dir **rp, const char break; case GOTWEBD_AUTH_SECURE: case GOTWEBD_AUTH_INSECURE: - if (requesting_website) { - access = GOTWEBD_ACCESS_PERMITTED; - break; - } access = auth_check(c, &srv->access_rules); if (access == GOTWEBD_ACCESS_NO_MATCH) access = auth_check(c, &env->access_rules); @@ -1651,7 +1652,7 @@ gotweb_load_got_path(struct repo_dir **rp, const char goto err; } - if (!requesting_website) { + if (site == NULL) { if (repo_is_hidden) { error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO); blob - eee62782b99ef152f6e3db7f99dd9247492c98e1 blob + 38dae76d2223e3d613a2588c2d45c1016115083e --- gotwebd/gotwebd.c +++ gotwebd/gotwebd.c @@ -967,13 +967,18 @@ gotwebd_configure(struct gotwebd *env, uid_t uid, gid_ &srv->access_rules); } - /* send web sites */ + /* send web sites and per-site access rules */ RB_FOREACH(pe, got_pathlist_head, &srv->websites) { struct website *site = pe->data; for (i = 0; i < env->prefork; i++) { config_set_website(&env->iev_auth[i], site); config_set_website(&env->iev_gotweb[i], site); + + config_set_access_rules(&env->iev_auth[i], + &site->access_rules); + config_set_access_rules(&env->iev_gotweb[i], + &site->access_rules); } } blob - 85e18131453ca83c15c732cf89f830606430976c blob + 676bee98eaad27e0abe2707202e6bca2e745d204 --- gotwebd/gotwebd.conf.5 +++ gotwebd/gotwebd.conf.5 @@ -516,20 +516,63 @@ will display the repository. .It Ic website Ar url-path Brq ... Show a web site when the browser visits the given .Ar url-path . -The web site's content is composed of files in a Git repository. .Pp -While the underlying repository is subject to authentication as usual, -web site content is always public, and cannot be hidden via the +For each web site, access rules can be configured using the +.Ic permit +and +.Ic deny +configuration directives. +Multiple access rules can be specified, and the last matching rule +determines the action taken. +.Pp +If no access rules are set in a web site context then the access rules set +in the server and global configuration contexts apply. +If no rule matches then the web site will be inaccessible if authentication +is enabled. +.Pp +The .Ic hide repository or .Ic respect_exportok -directives. +directives do not apply to web site content. .Pp The available .Ic website configuration parameters are as follows: .Pp .Bl -tag -width Ds +.It Ic deny Ar identity +Deny web site access to users with the username +.Ar identity . +Group names may be matched by prepending a colon +.Pq Sq \&: +to +.Ar identity . +Numeric IDs are also accepted. +.It Ic permit Ar identity +Permit web site access to users with the username +.Ar identity . +Group names may be matched by prepending a colon +.Pq Sq \&: +to +.Ar identity . +Numeric IDs are also accepted. +.It Ic disable authentication +Disable authentication, allowing any browser to view the web site. +Any access rules configured with +.Ic permit +or +.Ic deny +directives for this web site will be ignored. +.Pp +If not specified, the server context or global context determines +whether authentication is disabled. +.It Ic enable authentication Oo Ic insecure Oc +Enable authentication, requiring browsers to present a login token cookie +before access to the web site is granted. +.Pp +If not specified, the server context or global context determines +whether authentication is enabled. .It Ic repository Ar name Serve web site content from the specified Git repository. The repository will be looked up within the server's blob - e02b676e4c9fdf7121d4785fc07e8786af9faad0 blob + c399ba4aab08a042e90227fb6d4930124e49edd7 --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -400,6 +400,9 @@ struct website { char url_path[MAX_DOCUMENT_URI]; char branch_name[MAX_BRANCH_NAME]; char path[PATH_MAX]; + + enum gotwebd_auth_config auth_config; + struct gotwebd_access_rule_list access_rules; }; struct server { blob - df637ac44946ad4e5306f5f14b0ff878187836a4 blob + 57beb717a572f78e1e1ac9452a9bcfd38e2bf1cd --- gotwebd/parse.y +++ gotwebd/parse.y @@ -837,6 +837,41 @@ websiteopts1 : REPOSITORY STRING { } free($2); } + | DISABLE AUTHENTICATION { + if (new_website->auth_config != 0) { + yyerror("ambiguous authentication " + "setting for website %s", + new_website->path); + YYERROR; + } + new_website->auth_config = GOTWEBD_AUTH_DISABLED; + } + | ENABLE AUTHENTICATION { + if (new_website->auth_config != 0) { + yyerror("ambiguous authentication " + "setting for website %s", + new_website->path); + YYERROR; + } + new_website->auth_config = GOTWEBD_AUTH_SECURE; + } + | ENABLE AUTHENTICATION INSECURE { + if (new_website->auth_config != 0) { + yyerror("ambiguous authentication " + "setting for website %s", + new_website->path); + YYERROR; + } + new_website->auth_config = GOTWEBD_AUTH_INSECURE; + } + | PERMIT numberstring { + conf_new_access_rule(&new_website->access_rules, + GOTWEBD_ACCESS_PERMITTED, $2); + } + | DENY numberstring { + conf_new_access_rule(&new_website->access_rules, + GOTWEBD_ACCESS_DENIED, $2); + } ; website : WEBSITE STRING { @@ -1697,6 +1732,9 @@ parse_config(const char *filename, struct gotwebd *env const char *url_path = pe->path; struct website *site = pe->data; + if (site->auth_config == 0) + site->auth_config = srv->auth_config; + if (site->repo_name[0] == '\0') { yyerror("no repository defined for website " "'%s' on server %s", url_path, srv->name); @@ -1986,6 +2024,8 @@ conf_new_website(struct server *server, const char *ur if (site == NULL) fatal("calloc"); + STAILQ_INIT(&site->access_rules); + if (!got_path_is_absolute(url_path)) { int ret; blob - 52abf86340197bda89a7e44126522b65004402f8 blob + d47bc250f8f0ef47d5cb6931a419568b58558e14 --- regress/gotsysd/Makefile +++ regress/gotsysd/Makefile @@ -230,7 +230,7 @@ $(GOTWEBD_CONF): @${UNPRIV} 'echo server \"VMIP\" { >> $@' @${UNPRIV} 'echo \ \ repos_path \"/git\" >> $@' @${UNPRIV} 'echo \ \ hide repositories on >> $@' - @${UNPRIV} 'echo \ \ login hint user anonymous >> $@' + @${UNPRIV} 'echo \ \ login hint user ${GOTSYSD_TEST_USER} >> $@' @${UNPRIV} 'echo \ \ show_repo_age off >> $@' @${UNPRIV} 'echo \ \ show_repo_description off >> $@' @${UNPRIV} 'echo \ \ show_repo_owner off >> $@' @@ -267,7 +267,7 @@ $(GOTWEBD_WWW_CONF): @${UNPRIV} 'echo server \"VMIP\" { >> $@' @${UNPRIV} 'echo \ \ repos_path \"/git\" >> $@' @${UNPRIV} 'echo \ \ hide repositories on >> $@' - @${UNPRIV} 'echo \ \ login hint user anonymous >> $@' + @${UNPRIV} 'echo \ \ login hint user ${GOTSYSD_TEST_USER} >> $@' @${UNPRIV} 'echo \ \ show_repo_age off >> $@' @${UNPRIV} 'echo \ \ show_repo_description off >> $@' @${UNPRIV} 'echo \ \ show_repo_owner off >> $@' @@ -275,6 +275,7 @@ $(GOTWEBD_WWW_CONF): @${UNPRIV} 'echo \ \ repos_url_path \"/repos\" >> $@' @${UNPRIV} 'echo \ \ website \"/\" { >> $@' @${UNPRIV} 'echo \ \ \ \ repository \"www\" >> $@' + @${UNPRIV} 'echo \ \ \ \ disable authentication >> $@' @${UNPRIV} 'echo \ \ } >> $@' @${UNPRIV} 'echo } >> $@' @@ -288,7 +289,7 @@ $(GOTWEBD_REPOS_WWW_CONF): @${UNPRIV} 'echo \ \ repository \"gotsys\" { >> $@' @${UNPRIV} 'echo \ \ \ \ hide repository on >> $@' @${UNPRIV} 'echo \ \ } >> $@' - @${UNPRIV} 'echo \ \ login hint user anonymous >> $@' + @${UNPRIV} 'echo \ \ login hint user ${GOTSYSD_TEST_USER} >> $@' @${UNPRIV} 'echo \ \ show_repo_age off >> $@' @${UNPRIV} 'echo \ \ show_repo_description off >> $@' @${UNPRIV} 'echo \ \ show_repo_owner off >> $@' @@ -296,6 +297,7 @@ $(GOTWEBD_REPOS_WWW_CONF): @${UNPRIV} 'echo \ \ repos_url_path \"/\" >> $@' @${UNPRIV} 'echo \ \ website \"/website\" { >> $@' @${UNPRIV} 'echo \ \ \ \ repository \"www\" >> $@' + @${UNPRIV} 'echo \ \ \ \ disable authentication >> $@' @${UNPRIV} 'echo \ \ } >> $@' @${UNPRIV} 'echo } >> $@' blob - cb2da971e563e78a73126214458997e020722312 blob + b06c59dc38cab81acce2ebdbee93ad031bbb5307 --- regress/gotsysd/test_gotwebd_repos_www.sh +++ regress/gotsysd/test_gotwebd_repos_www.sh @@ -154,7 +154,7 @@ EOF cat > $testroot/stdout.expected < $testroot/stdout.expected <