commit 78d5affcbbd1ba3e15aa9fe400e7d849d5840277 from: Stefan Sperling via: Thomas Adam date: Thu May 15 16:08:55 2025 UTC implement support for protected references in gotsys.conf and gotsysd commit - 3a9d1ac3ef2ebff9148c4d7dda9dd2d3d03c6a1c commit + 78d5affcbbd1ba3e15aa9fe400e7d849d5840277 blob - f2e9ba498f11eb6bad0c249cab63002a4f30e4b0 blob + 5a5be0c975f6dffd61ff64f378fc1bac1b05f212 --- gotsys/gotsys.conf.5 +++ gotsys/gotsys.conf.5 @@ -280,70 +280,70 @@ The special user can be used when public read-only access to repositories over SSH is desired. The anonymous user has an empty password, cannot use an SSH public key, and can only be granted read-only access. -.\".It Ic protect Brq Ar ... -.\"The -.\".Cm protect -.\"directive may be used to protect branches and tags in a repository -.\"from being overwritten by potentially destructive client-side commands, -.\"such as when -.\".Cm got send -f -.\"and -.\".Cm git push -f -.\"are used to change the history of a branch. -.\".Pp -.\"To build a set of protected branches and tags, multiple -.\".Ic protect -.\"directives may be specified per repository and -.\"multiple -.\".Ic protect -.\"directive parameters may be specified within curly braces. -.\".Pp -.\"The available -.\".Cm protect -.\"parameters are as follows: -.\".Bl -tag -width Ds -.\".It Ic branch Ar name -.\"Protect the named branch. -.\"The branch may be created if it does not exist yet. -.\"Attempts to delete the branch or change its history will be denied. -.\".Pp -.\"If the -.\".Ar name -.\"does not already begin with -.\".Dq refs/heads/ -.\"it will be looked up in the -.\".Dq refs/heads/ -.\"reference namespace. -.\".It Ic branch Ic namespace Ar namespace -.\"Protect the given reference namespace, assuming that references in -.\"this namespace represent branches. -.\"New branches may be created in the namespace. -.\"Attempts to change the history of branches or delete them will be denied. -.\".Pp -.\"The -.\".Ar namespace -.\"argument must be absolute, starting with -.\".Dq refs/ . -.\".It Ic tag Ic namespace Ar namespace -.\"Protect the given reference namespace, assuming that references in -.\"this namespace represent tags. -.\"New tags may be created in the namespace. -.\"Attempts to change or delete existing tags will be denied. -.\".Pp -.\"The -.\".Ar namespace -.\"argument must be absolute, starting with -.\".Dq refs/ . -.\".El -.\".Pp -.\"The special reference namespaces -.\".Dq refs/got/ -.\"and -.\".Dq refs/remotes/ -.\"do not need to be listed in -.\".Nm . -.\"These namespaces are always protected and even attempts to create new -.\"references in these namespaces will always be denied. +.It Ic protect Brq Ar ... +The +.Cm protect +directive may be used to protect branches and tags in a repository +from being overwritten by potentially destructive client-side commands, +such as when +.Cm got send -f +and +.Cm git push -f +are used to change the history of a branch. +.Pp +To build a set of protected branches and tags, multiple +.Ic protect +directives may be specified per repository and +multiple +.Ic protect +directive parameters may be specified within curly braces. +.Pp +The available +.Cm protect +parameters are as follows: +.Bl -tag -width Ds +.It Ic branch Ar name +Protect the named branch. +The branch may be created if it does not exist yet. +Attempts to delete the branch or change its history will be denied. +.Pp +If the +.Ar name +does not already begin with +.Dq refs/heads/ +it will be looked up in the +.Dq refs/heads/ +reference namespace. +.It Ic branch Ic namespace Ar namespace +Protect the given reference namespace, assuming that references in +this namespace represent branches. +New branches may be created in the namespace. +Attempts to change the history of branches or delete them will be denied. +.Pp +The +.Ar namespace +argument must be absolute, starting with +.Dq refs/ . +.It Ic tag Ic namespace Ar namespace +Protect the given reference namespace, assuming that references in +this namespace represent tags. +New tags may be created in the namespace. +Attempts to change or delete existing tags will be denied. +.Pp +The +.Ar namespace +argument must be absolute, starting with +.Dq refs/ . +.El +.Pp +The special reference namespaces +.Dq refs/got/ +and +.Dq refs/remotes/ +do not need to be listed in +.Nm . +These namespaces are always protected and even attempts to create new +references in these namespaces will always be denied. .\".It Ic notify Brq Ar ... .\"The .\".Ic notify blob - a902536f3326eb4b8fb5c450d12d842ed62b7c9d blob + 7b5191095a645e3f1b5c9ade40cbf3811d5c5f42 --- gotsys/gotsys.h +++ gotsys/gotsys.h @@ -98,8 +98,11 @@ struct gotsys_repo { struct gotsys_access_rule_list access_rules; struct got_pathlist_head protected_tag_namespaces; + size_t nprotected_tag_namespaces; struct got_pathlist_head protected_branch_namespaces; + size_t nprotected_branch_namespaces; struct got_pathlist_head protected_branches; + size_t nprotected_branches; struct got_pathlist_head notification_refs; struct got_pathlist_head notification_ref_namespaces; blob - f69319dfbc7554ce40536131df9eba7954eed253 blob + 6153517acb3341337bb0783e7ef55190ccbfaa3a --- gotsys/parse.y +++ gotsys/parse.y @@ -1274,6 +1274,7 @@ conf_protect_tag_namespace(struct gotsys_repo *repo, c if (conf_protect_ref_namespace(&new, &repo->protected_tag_namespaces, namespace) == -1) return -1; + repo->nprotected_tag_namespaces++; RB_FOREACH(pe, got_pathlist_head, &repo->protected_branch_namespaces) { if (strcmp(pe->path, new) == 0) { @@ -1294,6 +1295,7 @@ conf_protect_branch_namespace(struct gotsys_repo *repo if (conf_protect_ref_namespace(&new, &repo->protected_branch_namespaces, namespace) == -1) return -1; + repo->nprotected_branch_namespaces++; RB_FOREACH(pe, got_pathlist_head, &repo->protected_tag_namespaces) { if (strcmp(pe->path, new) == 0) { @@ -1340,6 +1342,7 @@ conf_protect_branch(struct gotsys_repo *repo, char *br yyerror("duplicate protect branch %s", branchname); return -1; } + repo->nprotected_branches++; return 0; } blob - c43bfa8cdc6ace8ebc66ca3bc06c640d48d08ecf blob + caa12c8536fbd38df85ccd0f423054319c1c2bf7 --- gotsysd/gotsysd.h +++ gotsysd/gotsysd.h @@ -178,6 +178,13 @@ enum gotsysd_imsg_type { GOTSYSD_IMSG_SYSCONF_REPOS_DONE, GOTSYSD_IMSG_SYSCONF_ACCESS_RULE, GOTSYSD_IMSG_SYSCONF_ACCESS_RULES_DONE, + GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES, + GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM, + GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE, GOTSYSD_IMSG_SYSCONF_PARSE_DONE, /* Addition of users and groups. */ @@ -395,7 +402,37 @@ struct gotsysd_imsg_sysconf_access_rule { /* Followed by identifier_len bytes. */ }; + +/* + * Structure for sending path lists over imsg. Used with: + * GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES + * GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES + * GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES + * GOTSYSD_IMSG_SYSCONF_NOTIFY_BRANCHES + * GOTSYSD_IMSG_SYSCONF_NOTIFY_REF_NAMESPACES + */ +struct gotsysd_imsg_pathlist { + size_t nelem; + /* Followed by nelem path list elements. */ +}; + +/* + * Structure for a path list element. Used with: + * GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM + * GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM + * GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM + * GOTSYSD_IMSG_SYSCONF_NOTIFY_BRANCHES_ELEM + * GOTSYSD_IMSG_SYSCONF_NOTIFY_REF_NAMESPACES_ELEM + */ +struct gotsysd_imsg_pathlist_elem { + size_t path_len; + size_t data_len; + + /* Followed by path_len bytes. */ + /* Followed by data_len bytes. */ +}; + #ifndef GOT_LIBEXECDIR #define GOT_LIBEXECDIR /usr/libexec #endif @@ -489,6 +526,7 @@ struct gotsys_authorized_keys_list; struct gotsys_repolist; struct gotsys_repo; struct gotsys_access_rule; +struct got_pathlist_head; const struct got_error *gotsys_imsg_send_users(struct gotsysd_imsgev *, struct gotsys_userlist *, int, int, int); @@ -513,6 +551,9 @@ const struct got_error *gotsys_imsg_recv_repository(st const struct got_error *gotsys_imsg_recv_access_rule( struct gotsys_access_rule **, struct imsg *, struct gotsys_userlist *, struct gotsys_grouplist *); +const struct got_error *gotsys_imsg_recv_pathlist(size_t *, struct imsg *); +const struct got_error *gotsys_imsg_recv_pathlist_elem(struct imsg *, + struct got_pathlist_head *); struct gotsys_uidset_element; struct gotsys_uidset; blob - 45491d7892cffae8f83ce3db7fe01350bbdaa863 blob + 156666bf66fc9c6047015be78b05d579669ba07e --- gotsysd/helpers.c +++ gotsysd/helpers.c @@ -579,6 +579,13 @@ dispatch_helper_child(int fd, short event, void *arg) case GOTSYSD_IMSG_SYSCONF_REPOS_DONE: case GOTSYSD_IMSG_SYSCONF_ACCESS_RULE: case GOTSYSD_IMSG_SYSCONF_ACCESS_RULES_DONE: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE: case GOTSYSD_IMSG_SYSCONF_PARSE_DONE: if (proc->type != GOTSYSD_IMSG_START_PROG_READ_CONF) { err = got_error_fmt(GOT_ERR_PRIVSEP_MSG, @@ -1005,6 +1012,13 @@ helpers_dispatch_sysconf(int fd, short event, void *ar case GOTSYSD_IMSG_SYSCONF_REPO: case GOTSYSD_IMSG_SYSCONF_ACCESS_RULE: case GOTSYSD_IMSG_SYSCONF_ACCESS_RULES_DONE: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE: case GOTSYSD_IMSG_SYSCONF_REPOS_DONE: proc = find_proc(GOTSYSD_IMSG_START_PROG_WRITE_CONF, 1); blob - bf8eba639405b64b6389a8f311c72d6404a7710e blob + 4cf2c6bad5710597487e40327b1c1b47a9e65ba2 --- gotsysd/libexec/gotsys-write-conf/Makefile +++ gotsysd/libexec/gotsys-write-conf/Makefile @@ -4,7 +4,8 @@ PROG= gotsys-write-conf SRCS= gotsys-write-conf.c error.c path.c hash.c pollfd.c \ - log.c imsg.c gotsys_conf.c gotsys_imsg.c opentemp.c + log.c imsg.c gotsys_conf.c gotsys_imsg.c opentemp.c \ + reference_parse.c CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib \ -I${.CURDIR}/../../../gotsys -I${.CURDIR}/../../ -I${.CURDIR} blob - 2aa43851a7ef5318fbc9caa2089ab0e57439948f blob + 3224ceb6ed71f00cb8c64518e60e0a64557c78f6 --- gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c +++ gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c @@ -36,6 +36,7 @@ #include "got_path.h" #include "got_opentemp.h" #include "got_object.h" +#include "got_reference.h" #include "gotsysd.h" #include "gotsys.h" @@ -43,6 +44,9 @@ static struct gotsys_conf gotsysconf; static struct gotsys_userlist *users_cur; static struct gotsys_repo *repo_cur; +static struct got_pathlist_head *protected_refs_cur; +static size_t nprotected_refs_needed; +static size_t nprotected_refs_received; static int gotd_conf_tmpfd = -1; static char *gotd_conf_tmppath; @@ -138,6 +142,119 @@ write_access_rules(struct gotsys_access_rule_list *rul } static const struct got_error * +refname_is_valid(const char *refname) +{ + if (strncmp(refname, "refs/", 5) != 0) { + return got_error_fmt( GOT_ERR_BAD_REF_NAME, + "reference name must begin with \"refs/\": %s", refname); + } + + if (!got_ref_name_is_valid(refname)) + return got_error_path(refname, GOT_ERR_BAD_REF_NAME); + + return NULL; +} + +static const struct got_error * +write_protected_refs(struct gotsys_repo *repo) +{ + const struct got_error *err = NULL; + struct got_pathlist_entry *pe; + int ret; + const char *opening = "protect {"; + const char *closing = "}"; + char *namespace = NULL; + + if (RB_EMPTY(&repo->protected_tag_namespaces) && + RB_EMPTY(&repo->protected_branch_namespaces) && + RB_EMPTY(&repo->protected_branches)) + return NULL; + + ret = dprintf(gotd_conf_tmpfd, "\t%s\n", opening); + if (ret == -1) + return got_error_from_errno2("dprintf", gotd_conf_tmppath); + if (ret != 2 + strlen(opening)) + return got_error_fmt(GOT_ERR_IO, "short write to %s", + gotd_conf_tmppath); + + RB_FOREACH(pe, got_pathlist_head, &repo->protected_tag_namespaces) { + namespace = strdup(pe->path); + if (namespace == NULL) + return got_error_from_errno("strdup"); + + got_path_strip_trailing_slashes(namespace); + err = refname_is_valid(namespace); + if (err) + goto done; + + ret = dprintf(gotd_conf_tmpfd, "\t\ttag namespace \"%s\"\n", + namespace); + if (ret == -1) { + err = got_error_from_errno2("dprintf", + gotd_conf_tmppath); + goto done; + } + if (ret != 19 + strlen(namespace)) { + err = got_error_fmt(GOT_ERR_IO, "short write to %s", + gotd_conf_tmppath); + goto done; + } + free(namespace); + namespace = NULL; + } + + RB_FOREACH(pe, got_pathlist_head, &repo->protected_branch_namespaces) { + namespace = strdup(pe->path); + if (namespace == NULL) + return got_error_from_errno("strdup"); + + got_path_strip_trailing_slashes(namespace); + err = refname_is_valid(namespace); + if (err) + goto done; + + ret = dprintf(gotd_conf_tmpfd, "\t\tbranch namespace \"%s\"\n", + namespace); + if (ret == -1) { + err = got_error_from_errno2("dprintf", + gotd_conf_tmppath); + goto done; + } + if (ret != 22 + strlen(namespace)) { + err = got_error_fmt(GOT_ERR_IO, "short write to %s", + gotd_conf_tmppath); + goto done; + } + free(namespace); + namespace = NULL; + } + + RB_FOREACH(pe, got_pathlist_head, &repo->protected_branches) { + err = refname_is_valid(pe->path); + if (err) + return err; + ret = dprintf(gotd_conf_tmpfd, "\t\tbranch \"%s\"\n", pe->path); + if (ret == -1) { + return got_error_from_errno2("dprintf", + gotd_conf_tmppath); + } + if (ret != 12 + strlen(pe->path)) + return got_error_fmt(GOT_ERR_IO, "short write to %s", + gotd_conf_tmppath); + } + + ret = dprintf(gotd_conf_tmpfd, "\t%s\n", closing); + if (ret == -1) + return got_error_from_errno2("dprintf", gotd_conf_tmppath); + if (ret != 2 + strlen(closing)) + return got_error_fmt(GOT_ERR_IO, "short write to %s", + gotd_conf_tmppath); +done: + free(namespace); + return NULL; +} + +static const struct got_error * write_gotd_conf(void) { const struct got_error *err = NULL; @@ -208,6 +325,10 @@ write_gotd_conf(void) } err = write_access_rules(&repo->access_rules); + if (err) + return err; + + err = write_protected_refs(repo); if (err) return err; @@ -244,6 +365,7 @@ dispatch_event(int fd, short event, void *arg) struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; ssize_t n; + size_t npaths; int shut = 0; static int flush_and_exit; @@ -392,8 +514,89 @@ dispatch_event(int fd, short event, void *arg) "received unexpected imsg %d while in " "state %d\n", imsg.hdr.type, writeconf_state); + break; + } + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES: + if (repo_cur == NULL || + writeconf_state != WRITECONF_STATE_EXPECT_REPOS || + protected_refs_cur != NULL || + nprotected_refs_needed != 0) { + err = got_error(GOT_ERR_PRIVSEP_MSG); break; } + err = gotsys_imsg_recv_pathlist(&npaths, &imsg); + if (err) + break; + protected_refs_cur = + &repo_cur->protected_tag_namespaces; + nprotected_refs_needed = npaths; + nprotected_refs_received = 0; + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES: + if (repo_cur == NULL || + writeconf_state != WRITECONF_STATE_EXPECT_REPOS || + protected_refs_cur != NULL || + nprotected_refs_needed != 0) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = gotsys_imsg_recv_pathlist(&npaths, &imsg); + if (err) + break; + protected_refs_cur = + &repo_cur->protected_branch_namespaces; + nprotected_refs_needed = npaths; + nprotected_refs_received = 0; + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES: + if (repo_cur == NULL || + writeconf_state != WRITECONF_STATE_EXPECT_REPOS || + protected_refs_cur != NULL || + nprotected_refs_needed != 0) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = gotsys_imsg_recv_pathlist(&npaths, &imsg); + if (err) + break; + protected_refs_cur = + &repo_cur->protected_branches; + nprotected_refs_needed = npaths; + nprotected_refs_received = 0; + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM: + if (protected_refs_cur == NULL || + writeconf_state != WRITECONF_STATE_EXPECT_REPOS || + nprotected_refs_needed == 0 || + nprotected_refs_received >= + nprotected_refs_needed) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + /* TODO: validate refname validity */ + err = gotsys_imsg_recv_pathlist_elem(&imsg, + protected_refs_cur); + if (err) + break; + if (++nprotected_refs_received >= + nprotected_refs_needed) { + protected_refs_cur = NULL; + nprotected_refs_needed = 0; + } + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE: + if (repo_cur == NULL || + nprotected_refs_needed != 0 || + writeconf_state != WRITECONF_STATE_EXPECT_REPOS) { + err = got_error_fmt(GOT_ERR_PRIVSEP_MSG, + "received unexpected imsg %d while in " + "state %d\n", imsg.hdr.type, + writeconf_state); + break; + } repo_cur = NULL; break; case GOTSYSD_IMSG_SYSCONF_REPOS_DONE: blob - 78ccc06a678d390e420f21f088df833d98df9d9f blob + 0ac5146bc4bf7d1eba912ee4a82e87b798289faf --- gotsysd/sysconf.c +++ gotsysd/sysconf.c @@ -75,6 +75,10 @@ static struct gotsysd_sysconf { uid_t uid_start; uid_t uid_end; int have_anonymous_user; + struct got_pathlist_head *protected_refs_cur; + size_t *nprotected_refs_cur; + size_t nprotected_refs_needed; + size_t nprotected_refs_received; } gotsysd_sysconf; static struct gotsys_conf gotsysconf; @@ -152,6 +156,7 @@ sysconf_dispatch_libexec(int fd, short event, void *ar struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; ssize_t n; + size_t npaths; int shut = 0; if (event & EV_READ) { @@ -390,6 +395,104 @@ sysconf_dispatch_libexec(int fd, short event, void *ar break; } log_debug("done receiving access rules"); + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES: + if (gotsysd_sysconf.repo_cur == NULL || + gotsysd_sysconf.protected_refs_cur != NULL || + gotsysd_sysconf.nprotected_refs_needed != 0 || + gotsysd_sysconf.state != + SYSCONF_STATE_EXPECT_REPOS) { + + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = gotsys_imsg_recv_pathlist(&npaths, &imsg); + if (err) + break; + gotsysd_sysconf.protected_refs_cur = + &gotsysd_sysconf.repo_cur->protected_tag_namespaces; + gotsysd_sysconf.nprotected_refs_cur = + &gotsysd_sysconf.repo_cur->nprotected_tag_namespaces; + gotsysd_sysconf.nprotected_refs_needed = npaths; + gotsysd_sysconf.nprotected_refs_received = 0; + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES: + if (gotsysd_sysconf.repo_cur == NULL || + gotsysd_sysconf.protected_refs_cur != NULL || + gotsysd_sysconf.nprotected_refs_needed != 0 || + gotsysd_sysconf.state != + SYSCONF_STATE_EXPECT_REPOS) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = gotsys_imsg_recv_pathlist(&npaths, &imsg); + if (err) + break; + gotsysd_sysconf.protected_refs_cur = + &gotsysd_sysconf.repo_cur->protected_branch_namespaces; + gotsysd_sysconf.nprotected_refs_cur = + &gotsysd_sysconf.repo_cur->nprotected_branch_namespaces; + gotsysd_sysconf.nprotected_refs_needed = npaths; + gotsysd_sysconf.nprotected_refs_received = 0; + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES: + if (gotsysd_sysconf.repo_cur == NULL || + gotsysd_sysconf.protected_refs_cur != NULL || + gotsysd_sysconf.nprotected_refs_needed != 0 || + gotsysd_sysconf.state != + SYSCONF_STATE_EXPECT_REPOS) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = gotsys_imsg_recv_pathlist(&npaths, &imsg); + if (err) + break; + gotsysd_sysconf.protected_refs_cur = + &gotsysd_sysconf.repo_cur->protected_branches; + gotsysd_sysconf.nprotected_refs_cur = + &gotsysd_sysconf.repo_cur->nprotected_branches; + gotsysd_sysconf.nprotected_refs_needed = npaths; + gotsysd_sysconf.nprotected_refs_received = 0; + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM: + case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM: + if (gotsysd_sysconf.protected_refs_cur == NULL || + gotsysd_sysconf.nprotected_refs_cur == NULL || + gotsysd_sysconf.nprotected_refs_needed == 0 || + gotsysd_sysconf.nprotected_refs_received >= + gotsysd_sysconf.nprotected_refs_needed || + gotsysd_sysconf.state != + SYSCONF_STATE_EXPECT_REPOS) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = gotsys_imsg_recv_pathlist_elem(&imsg, + gotsysd_sysconf.protected_refs_cur); + if (err) + break; + if (++gotsysd_sysconf.nprotected_refs_received >= + gotsysd_sysconf.nprotected_refs_needed) { + gotsysd_sysconf.protected_refs_cur = NULL; + *gotsysd_sysconf.nprotected_refs_cur = + gotsysd_sysconf.nprotected_refs_received; + gotsysd_sysconf.nprotected_refs_needed = 0; + gotsysd_sysconf.nprotected_refs_received = 0; + } + break; + case GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE: + if (gotsysd_sysconf.repo_cur == NULL || + gotsysd_sysconf.nprotected_refs_needed != 0 || + gotsysd_sysconf.protected_refs_cur != NULL || + gotsysd_sysconf.state != + SYSCONF_STATE_EXPECT_REPOS) { + err = got_error_fmt(GOT_ERR_PRIVSEP_MSG, + "received unexpected imsg %d while in " + "state %d\n", imsg.hdr.type, + gotsysd_sysconf.state); + break; + } + log_debug("done receiving protected refs"); gotsysd_sysconf.repo_cur = NULL; break; case GOTSYSD_IMSG_SYSCONF_REPOS_DONE: blob - 87abeb7c7095519d423cde7b9a91554386478b15 blob + 9797f0e5ee96f68cc636be912ebe401e51ecd6c4 --- lib/gotsys_imsg.c +++ lib/gotsys_imsg.c @@ -659,6 +659,99 @@ send_access_rule(struct gotsysd_imsgev *iev, } static const struct got_error * +send_pathlist_elem(struct gotsysd_imsgev *iev, const char *refname, + int imsg_type) +{ + struct gotsysd_imsg_pathlist_elem ielem; + struct ibuf *wbuf = NULL; + + memset(&ielem, 0, sizeof(ielem)); + ielem.path_len = strlen(refname); + + wbuf = imsg_create(&iev->ibuf, imsg_type, 0, 0, + sizeof(ielem) + ielem.path_len); + if (wbuf == NULL) + return got_error_from_errno_fmt("imsg_create %d", imsg_type); + + if (imsg_add(wbuf, &ielem, sizeof(ielem)) == -1) + return got_error_from_errno_fmt("imsg_add %d", imsg_type); + if (imsg_add(wbuf, refname, ielem.path_len) == -1) + return got_error_from_errno_fmt("imsg_add %d", imsg_type); + + imsg_close(&iev->ibuf, wbuf); + return gotsysd_imsg_flush(&iev->ibuf); +} + +static const struct got_error * +send_protected_refs(struct gotsysd_imsgev *iev, struct gotsys_repo *repo) +{ + const struct got_error *err = NULL; + struct got_pathlist_entry *pe; + struct gotsysd_imsg_pathlist ilist; + + memset(&ilist, 0, sizeof(ilist)); + + ilist.nelem = repo->nprotected_tag_namespaces; + if (ilist.nelem > 0) { + if (gotsysd_imsg_compose_event(iev, + GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES, + 0, -1, &ilist, sizeof(ilist)) == -1) { + return got_error_from_errno("imsg compose " + "PROTECTED_TAG_NAMESPACES"); + } + + RB_FOREACH(pe, got_pathlist_head, + &repo->protected_tag_namespaces) { + err = send_pathlist_elem(iev, pe->path, + GOTSYSD_IMSG_SYSCONF_PROTECTED_TAG_NAMESPACES_ELEM); + if (err) + return err; + } + } + + ilist.nelem = repo->nprotected_branch_namespaces; + if (ilist.nelem > 0) { + if (gotsysd_imsg_compose_event(iev, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES, + 0, -1, &ilist, sizeof(ilist)) == -1) { + return got_error_from_errno("imsg compose " + "PROTECTED_BRANCH_NAMESPACES"); + } + + RB_FOREACH(pe, got_pathlist_head, + &repo->protected_branch_namespaces) { + err = send_pathlist_elem(iev, pe->path, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCH_NAMESPACES_ELEM); + if (err) + return err; + } + } + + ilist.nelem = repo->nprotected_branches; + if (ilist.nelem > 0) { + if (gotsysd_imsg_compose_event(iev, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES, + 0, -1, &ilist, sizeof(ilist)) == -1) { + return got_error_from_errno("imsg compose " + "PROTECTED_BRANCH_NAMESPACES"); + } + + RB_FOREACH(pe, got_pathlist_head, &repo->protected_branches) { + err = send_pathlist_elem(iev, pe->path, + GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM); + if (err) + return err; + } + } + + if (gotsysd_imsg_compose_event(iev, + GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE, 0, -1, NULL, 0) == -1) + return got_error_from_errno("gotsysd_imsg_compose_event"); + + return NULL; +} + +static const struct got_error * send_repo(struct gotsysd_imsgev *iev, struct gotsys_repo *repo) { const struct got_error *err; @@ -694,7 +787,9 @@ send_repo(struct gotsysd_imsgev *iev, struct gotsys_re return got_error_from_errno("gotsysd_imsg_compose_event"); } - /* TODO: send protected tags and branches */ + err = send_protected_refs(iev, repo); + if (err) + return err; /* TODO: send notification config */ @@ -823,3 +918,49 @@ gotsys_imsg_recv_access_rule(struct gotsys_access_rule free(identifier); return err; } + +const struct got_error * +gotsys_imsg_recv_pathlist(size_t *npaths, struct imsg *imsg) +{ + struct gotsysd_imsg_pathlist ilist; + size_t datalen; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + if (datalen != sizeof(ilist)) + return got_error(GOT_ERR_PRIVSEP_LEN); + memcpy(&ilist, imsg->data, sizeof(ilist)); + + if (ilist.nelem == 0) + return got_error(GOT_ERR_PRIVSEP_LEN); + + *npaths = ilist.nelem; + return NULL; +} + +const struct got_error * +gotsys_imsg_recv_pathlist_elem(struct imsg *imsg, + struct got_pathlist_head *paths) +{ + const struct got_error *err = NULL; + struct gotsysd_imsg_pathlist_elem ielem; + size_t datalen; + char *path; + struct got_pathlist_entry *pe; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + if (datalen < sizeof(ielem)) + return got_error(GOT_ERR_PRIVSEP_LEN); + memcpy(&ielem, imsg->data, sizeof(ielem)); + + if (datalen != sizeof(ielem) + ielem.path_len) + return got_error(GOT_ERR_PRIVSEP_LEN); + + path = strndup(imsg->data + sizeof(ielem), ielem.path_len); + if (path == NULL) + return got_error_from_errno("strndup"); + + err = got_pathlist_insert(&pe, paths, path, NULL); + if (err || pe == NULL) + free(path); + return err; +} blob - 62a18caba95b0b7be549626721fce091f75e7b31 blob + c383909160e6f6ab6021c5e3f37c1f351274673f --- regress/gotsysd/test_gotsysd.sh +++ regress/gotsysd/test_gotsysd.sh @@ -1181,8 +1181,256 @@ EOF fi test_done "$testroot" "0" +} + +test_set_head() { + local testroot=`test_init set_head 1` + + # An attempt to set the HEAD of gotsys.git is an error. + cat > ${testroot}/bad-gotsys.conf < $testroot/stdout 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "gotsys check succeeded unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + cat > $testroot/stderr.expected </dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + crypted_vm_pw=`echo ${GOTSYSD_VM_PASSWORD} | encrypt | tr -d '\n'` + crypted_pw=`echo ${GOTSYSD_DEV_PASSWORD} | encrypt | tr -d '\n'` + sshkey=`cat ${GOTSYSD_SSH_PUBKEY}` + cat > ${testroot}/wt/gotsys.conf </dev/null) + local commit_id=`git_show_head $testroot/${GOTSYS_REPO}` + + got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO} + ret=$? + if [ $ret -ne 0 ]; then + echo "got send failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + # Wait for gotsysd to apply the new configuration. + echo "$commit_id" > $testroot/stdout.expected + for i in 1 2 3 4 5; do + sleep 1 + ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \ + cat /var/db/gotsysd/commit > $testroot/stdout + if cmp -s $testroot/stdout.expected $testroot/stdout; then + break; + fi + done + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "gotsysd failed to apply configuration" >&2 + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # Create branch "foo" in foo.git. + got clone -q -i ${GOTSYSD_SSH_KEY} -b main \ + ${GOTSYSD_DEV_USER}@${VMIP}:foo.git $testroot/foo.git + got branch -r $testroot/foo.git -c main foo + got send -q -i ${GOTSYSD_SSH_KEY} -r $testroot/foo.git -b foo + ret=$? + if [ $ret -ne 0 ]; then + echo "got send failed unexpectedly" >&2 + return 1 + fi + + # The foo repository should now advertise refs/heads/foo as HEAD. + got clone -q -l anonymous@${VMIP}:foo.git | egrep '^HEAD:' \ + > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "got clone -l failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + echo "HEAD: refs/heads/foo" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "gotsysd failed to apply configuration" >&2 + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" } +test_protect_refs() { + local testroot=`test_init protect_refs 1` + + got checkout -q $testroot/${GOTSYS_REPO} $testroot/wt >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + crypted_vm_pw=`echo ${GOTSYSD_VM_PASSWORD} | encrypt | tr -d '\n'` + crypted_pw=`echo ${GOTSYSD_DEV_PASSWORD} | encrypt | tr -d '\n'` + sshkey=`cat ${GOTSYSD_SSH_PUBKEY}` + cat > ${testroot}/wt/gotsys.conf </dev/null) + local commit_id=`git_show_head $testroot/${GOTSYS_REPO}` + + got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO} + ret=$? + if [ $ret -ne 0 ]; then + echo "got send failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + # Wait for gotsysd to apply the new configuration. + echo "$commit_id" > $testroot/stdout.expected + for i in 1 2 3 4 5; do + sleep 1 + ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \ + cat /var/db/gotsysd/commit > $testroot/stdout + if cmp -s $testroot/stdout.expected $testroot/stdout; then + break; + fi + done + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "gotsysd failed to apply configuration" >&2 + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # Create a new commit on branch "foo" and send it. + got clone -q -i ${GOTSYSD_SSH_KEY} -b foo \ + ${GOTSYSD_DEV_USER}@${VMIP}:foo.git $testroot/foo.git + got checkout -q $testroot/foo.git $testroot/wt-foo > /dev/null + echo "tweak alpha" > $testroot/wt-foo/alpha + (cd $testroot/wt-foo && got commit -m 'change alpha' > /dev/null) + got send -q -i ${GOTSYSD_SSH_KEY} -r $testroot/foo.git -b foo + ret=$? + if [ $ret -ne 0 ]; then + echo "got send failed unexpectedly" >&2 + return 1 + fi + + # Attempt to rewrite the history of branch "foo". + (cd $testroot/wt-foo && got update -q -c :head:-1 > /dev/null) + (cd $testroot/wt-foo && got histedit -d > /dev/null) + got send -q -i ${GOTSYSD_SSH_KEY} -r $testroot/foo.git -b foo -f \ + > $testroot/stdout 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "got send succeeded unexpectedly" >&2 + return 1 + fi + + echo -n "" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "gotsh: refs/heads/foo: reference is protected" \ + > $testroot/stderr.expected + grep '^gotsh:' $testroot/stderr > $testroot/stderr.filtered + cmp -s $testroot/stderr.expected $testroot/stderr.filtered + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr.filtered + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_user_add run_test test_user_mod @@ -1192,3 +1440,5 @@ run_test test_group_del run_test test_repo_create run_test test_user_anonymous run_test test_bad_gotsysconf +run_test test_set_head +run_test test_protect_refs