Hi tech@, The following patch expands acme-client config file `domain` blocks to allow for a `owner user:group` directive, which allows to get rid of customs scripts that "fix" permissions for issued certs, mostly needed in ports land. I don't find it too invasive, so I thought it could be merged. Most of the code and manpage bits were taken from vmd.
Noteworthy details: 1. fileproc.c pledge is expanded with chown. In particular, I don't understand pledge manpage: I was under the impression that wpath and fattr would allow for fchown, but I got an "Operation not supported" while testing. I guess this is related to the paragraph stating that some syscalls be allowed with limitations under some categories. 2. if the private key already exists, keyproc.c won't interfere with its ownership, the same way it does now. The fchown call can be moved if "fixing" the ownership is desirable. bye, -Lucas diff /usr/src commit - 93aad84f8cf14cfaff5b9cdb67494e561810ddc4 path + /usr/src blob - eb5f19eb298c117c3957faa0ed6ced14972ffaca file + usr.sbin/acme-client/acme-client.conf.5 --- usr.sbin/acme-client/acme-client.conf.5 +++ usr.sbin/acme-client/acme-client.conf.5 @@ -197,6 +197,17 @@ will be used. If it is not specified, a default of .Pa /var/www/acme will be used. +.It Ic owner Ar user : Ns Ar group +Set the owner of the key and certificate files create to +.Ar user +and +.Ar group . +If only +.Ar user +is given, only the user is set. +If only +.Pf : Ar group +is given, only the group is set. .El .Sh FILES .Bl -tag -width /etc/examples/acme-client.conf -compact blob - 4b43b6ef4ac302706859bf14b681cf8052aa55c8 file + usr.sbin/acme-client/extern.h --- usr.sbin/acme-client/extern.h +++ usr.sbin/acme-client/extern.h @@ -207,9 +207,9 @@ int fileproc(int, const char *, const char *, const int revokeproc(int, const char *, int, int, const char *const *, size_t); int fileproc(int, const char *, const char *, const char *, - const char *); + const char *, uid_t, gid_t); int keyproc(int, const char *, const char **, size_t, - enum keytype); + enum keytype, uid_t, gid_t); int netproc(int, int, int, int, int, int, int, struct authority_c *, const char *const *, size_t); blob - b8b8651c31907c6d37147c779f302481a1cb3c86 file + usr.sbin/acme-client/fileproc.c --- usr.sbin/acme-client/fileproc.c +++ usr.sbin/acme-client/fileproc.c @@ -29,7 +29,8 @@ serialise(const char *real, const char *v, size_t vsz, #include "extern.h" static int -serialise(const char *real, const char *v, size_t vsz, const char *v2, size_t v2sz) +serialise(const char *real, const char *v, size_t vsz, const char *v2, + size_t v2sz, uid_t uid, gid_t gid) { int fd; char *tmp; @@ -64,6 +65,10 @@ serialise(const char *real, const char *v, size_t vsz, warn("fchmod"); goto out; } + if (fchown(fd, uid, gid) == -1) { + warn("fchown"); + goto out; + } if ((ssize_t)vsz != write(fd, v, vsz)) { warnx("write"); goto out; @@ -91,7 +96,7 @@ fileproc(int certsock, const char *certdir, const char int fileproc(int certsock, const char *certdir, const char *certfile, const char - *chainfile, const char *fullchainfile) + *chainfile, const char *fullchainfile, uid_t uid, gid_t gid) { char *csr = NULL, *ch = NULL; size_t chsz, csz; @@ -108,7 +113,7 @@ fileproc(int certsock, const char *certdir, const char * rpath and cpath for rename, wpath and cpath for * writing to the temporary. fattr for fchmod. */ - if (pledge("stdio cpath wpath rpath fattr", NULL) == -1) { + if (pledge("stdio cpath wpath rpath fattr chown", NULL) == -1) { warn("pledge"); goto out; } @@ -173,7 +178,7 @@ fileproc(int certsock, const char *certdir, const char goto out; if (chainfile) { - if (!serialise(chainfile, ch, chsz, NULL, 0)) + if (!serialise(chainfile, ch, chsz, NULL, 0, uid, gid)) goto out; dodbg("%s: created", chainfile); @@ -190,7 +195,7 @@ fileproc(int certsock, const char *certdir, const char goto out; if (certfile) { - if (!serialise(certfile, csr, csz, NULL, 0)) + if (!serialise(certfile, csr, csz, NULL, 0, uid, gid)) goto out; dodbg("%s: created", certfile); @@ -204,7 +209,7 @@ fileproc(int certsock, const char *certdir, const char */ if (fullchainfile) { if (!serialise(fullchainfile, csr, csz, ch, - chsz)) + chsz, uid, gid)) goto out; dodbg("%s: created", fullchainfile); blob - a3b6666c2796c30b97c5628e5f3d350a765c87cb file + usr.sbin/acme-client/keyproc.c --- usr.sbin/acme-client/keyproc.c +++ usr.sbin/acme-client/keyproc.c @@ -75,7 +75,7 @@ keyproc(int netsock, const char *keyfile, const char * */ int keyproc(int netsock, const char *keyfile, const char **alts, size_t altsz, - enum keytype keytype) + enum keytype keytype, uid_t uid, gid_t gid) { char *der64 = NULL, *der = NULL, *dercp; char *sans = NULL, *san = NULL; @@ -97,7 +97,18 @@ keyproc(int netsock, const char *keyfile, const char * prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO); if ((f = fopen(keyfile, "r")) == NULL && errno == ENOENT) { + int fd; + f = fopen(keyfile, "wx"); + if (f != NULL) { + fd = fileno(f); + if (fd >= 0) { + if (fchown(fd, uid, gid) == -1) { + warn("chown %s", keyfile); + goto out; + } + } + } newkey = 1; } umask(prev); blob - bec17254297fb1e770411c1cb8ddb718e150ee0f file + usr.sbin/acme-client/main.c --- usr.sbin/acme-client/main.c +++ usr.sbin/acme-client/main.c @@ -248,7 +248,7 @@ main(int argc, char *argv[]) close(file_fds[1]); c = keyproc(key_fds[0], domain->key, (const char **)alts, altsz, - domain->keytype); + domain->keytype, domain->owner.uid, domain->owner.gid); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -319,7 +319,7 @@ main(int argc, char *argv[]) close(dns_fds[0]); close(rvk_fds[0]); c = fileproc(file_fds[1], certdir, domain->cert, domain->chain, - domain->fullchain); + domain->fullchain, domain->owner.uid, domain->owner.gid); /* * This is different from the other processes in that it * can return 2 if the certificates were updated. blob - 3954f62a0d0894d8fd243944e8393c2e9dc2e70e file + usr.sbin/acme-client/parse.h --- usr.sbin/acme-client/parse.h +++ usr.sbin/acme-client/parse.h @@ -17,6 +17,7 @@ #ifndef PARSE_H #define PARSE_H +#include <sys/types.h> /* XXX */ #include <sys/queue.h> #define AUTH_MAXLEN 120 /* max length of an authority_c name */ @@ -54,6 +55,10 @@ struct domain_c { char *fullchain; char *auth; char *challengedir; + struct { + uid_t uid; + gid_t gid; + } owner; }; struct altname_c { blob - 2b0d55f20b1947c94defc1c3b07d790e8bea56c8 file + usr.sbin/acme-client/parse.y --- usr.sbin/acme-client/parse.y +++ usr.sbin/acme-client/parse.y @@ -30,7 +30,9 @@ #include <ctype.h> #include <err.h> #include <errno.h> +#include <grp.h> #include <limits.h> +#include <pwd.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -94,6 +96,10 @@ typedef struct { union { int64_t number; char *string; + struct { + uid_t uid; + gid_t gid; + } owner; } v; int lineno; } YYSTYPE; @@ -102,6 +108,7 @@ typedef struct { %token AUTHORITY URL API ACCOUNT CONTACT %token DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH CHALLENGEDIR +%token OWNER %token YES NO %token INCLUDE %token ERROR @@ -110,6 +117,7 @@ typedef struct { %token <v.number> NUMBER %type <v.string> string %type <v.number> keytype +%type <v.owner> owner_id %% @@ -393,6 +401,10 @@ domainoptsl : ALTERNATIVE NAMES '{' optnl altname_l '} err(EXIT_FAILURE, "strdup"); domain->challengedir = s; } + | OWNER owner_id { + domain->owner.uid = $2.uid; + domain->owner.gid = $2.gid; + } ; altname_l : altname optcommanl altname_l @@ -421,6 +433,48 @@ altname : STRING { */ } +owner_id : NUMBER { + $$.uid = $1; + $$.gid = 0; + } + | STRING { + char *user, *group; + struct passwd *pw; + struct group *gr; + + $$.uid = $$.gid = 0; + + user = $1; + if ((group = strchr(user, ':')) != NULL) { + if (group == user) + user = NULL; + *group++ = '\0'; + } + + if (user != NULL && *user) { + if ((pw = getpwnam(user)) == NULL) { + yyerror("failed to get user: %s", + user); + free($1); + YYERROR; + } + $$.uid = pw->pw_uid; + } + + if (group != NULL && *group) { + if ((gr = getgrnam(group)) == NULL) { + yyerror("failed to get group: %s", + group); + free($1); + YYERROR; + } + $$.gid = gr->gr_gid; + } + + free($1); + } + ; + %% struct keywords { @@ -470,6 +524,7 @@ lookup(char *s) {"key", KEY}, {"name", NAME}, {"names", NAMES}, + {"owner", OWNER}, {"rsa", RSA}, {"sign", SIGN}, {"url", URL},