Download raw body.
gotwebd website support
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 <<EOF
[got]
Repos
-Log in by running: ssh anonymous@${VMIP} "weblogin ${VMIP}"
+Log in by running: ssh ${GOTSYSD_TEST_USER}@${VMIP} "weblogin ${VMIP}"
EOF
cmp -s $testroot/stdout.expected $testroot/stdout
blob - c6da01addd1f761ae9c1cf7fad8d0612b0aa0347
blob + 7506c40e00eb1198f1b6f2afd1cc14622ae0dc2e
--- regress/gotsysd/test_gotwebd_www.sh
+++ regress/gotsysd/test_gotwebd_www.sh
@@ -154,7 +154,7 @@ EOF
cat > $testroot/stdout.expected <<EOF
[got]
Repos
-Log in by running: ssh anonymous@${VMIP} "weblogin ${VMIP}"
+Log in by running: ssh ${GOTSYSD_TEST_USER}@${VMIP} "weblogin ${VMIP}"
EOF
cmp -s $testroot/stdout.expected $testroot/stdout
gotwebd website support