From 6f6e63f99b90f2c14337e5d0e649c8e0ba94300c Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 3 Jul 2017 20:04:57 +0200 Subject: [PATCH 01/48] Disable boot delay on AWS/Azure, set doas to user not wheel, set .forward. --- agent/main.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/agent/main.c b/agent/main.c index 68df50d..98a51ce 100644 --- a/agent/main.c +++ b/agent/main.c @@ -495,12 +495,16 @@ agent_configure(struct system_config *sc, int noaction) "-m", sc->sc_username, NULL) != 0) log_warnx("username failed"); + if (fileout(sc->sc_username, "w", "/root/.forward") != 0) + log_warnx(".forward failed"); + /* password */ if (sc->sc_password == NULL) { str1 = "/PasswordAuthentication/" "s/.*/PasswordAuthentication no/"; - str2 = "permit keepenv nopass :wheel as root\n" - "permit keepenv nopass root\n"; + if (asprintf(&str2, "permit keepenv nopass %s as root\n" + "permit keepenv nopass root\n", sc->sc_username) == -1) + str2 = NULL; } else { if (!noaction && shell("usermod", "-p", sc->sc_password, @@ -509,13 +513,15 @@ agent_configure(struct system_config *sc, int noaction) str1 = "/PasswordAuthentication/" "s/.*/PasswordAuthentication yes/"; - str2 = "permit keepenv persist :wheel as root\n" - "permit keepenv nopass root\n"; + if (asprintf(&str2, "permit keepenv persist %s as root\n" + "permit keepenv nopass root\n", sc->sc_username) == -1) + str2 = NULL; } /* doas */ - if (fileout(str2, "w", "/etc/doas.conf") != 0) + if (str2 == NULL || fileout(str2, "w", "/etc/doas.conf") != 0) log_warnx("doas failed"); + free(str2); /* ssh configuration */ if (sc->sc_password == NULL && !TAILQ_EMPTY(&sc->sc_pubkeys)) From 63d46cd6f1ec3c2c13a647d9de07bc2ce626be30 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 14 Aug 2017 13:40:12 +0200 Subject: [PATCH 02/48] Use -n to 'rescue' ovf-env.xml file and exit --- agent/azure.c | 22 +++++++++++------- agent/cloudinit.c | 3 +++ agent/main.c | 58 ++++++++++++++++++++++++----------------------- agent/main.h | 2 +- 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index 71c97a5..6af3abe 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -61,7 +61,7 @@ static int azure_reporthealth(struct system_config *, const char *); int azure(struct system_config *sc) { - int ret = -1; + int ret = -1; /* Apply defaults */ if ((sc->sc_username = strdup("azure-user")) == NULL) { @@ -72,13 +72,19 @@ azure(struct system_config *sc) sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; sc->sc_priv = &az_config; - if (azure_getendpoint(sc) != 0) { - log_warnx("failed to get endpoint"); + if (azure_getovfenv(sc) != 0) { + log_warnx("failed to get ovf-env.xml"); goto done; } - if (azure_getovfenv(sc) != 0) { - log_warnx("failed to get ovf-env.xml"); + if (sc->sc_dryrun) { + /* Return after backing up the ovf-env.xml file */ + ret = 0; + goto done; + } + + if (azure_getendpoint(sc) != 0) { + log_warnx("failed to get endpoint"); goto done; } @@ -795,9 +801,9 @@ azure_getovfenv(struct system_config *sc) static int azure_getendpoint(struct system_config *sc) { - char path[PATH_MAX], buf[BUFSIZ], *ep = NULL; - int a[4]; - FILE *fp; + char path[PATH_MAX], buf[BUFSIZ], *ep = NULL; + int a[4]; + FILE *fp; if ((size_t)snprintf(path, sizeof(path), "/var/db/dhclient.leases.%s", sc->sc_interface) >= sizeof(path)) { diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 92929ff..58de1bb 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -96,6 +96,9 @@ cloudinit_fetch(struct system_config *sc) sc->sc_addr.ip = sc->sc_endpoint; sc->sc_addr.family = 4; + if (sc->sc_dryrun) + return (0); + /* instance-id */ if ((sc->sc_instance = cloudinit_get(sc, "/latest/meta-data/instance-id", WORD)) == NULL) diff --git a/agent/main.c b/agent/main.c index 98a51ce..516c20e 100644 --- a/agent/main.c +++ b/agent/main.c @@ -31,7 +31,8 @@ #include "xml.h" __dead void usage(void); -static struct system_config *agent_init(void); +static struct system_config *agent_init(const char *, int); +static int agent_configure(struct system_config *); static void agent_free(struct system_config *); static int agent_pf(struct system_config *, int); static void agent_unconfigure(void); @@ -288,13 +289,15 @@ get_word(u_int8_t *ptr, size_t len) } static struct system_config * -agent_init(void) +agent_init(const char *ifname, int dryrun) { struct system_config *sc; if ((sc = calloc(1, sizeof(*sc))) == NULL) return (NULL); + sc->sc_interface = ifname; + sc->sc_dryrun = dryrun ? 1 : 0; TAILQ_INIT(&sc->sc_pubkeys); if ((sc->sc_nullfd = open("/dev/null", O_RDWR)) == -1) { @@ -302,6 +305,14 @@ agent_init(void) return (NULL); } + if (sc->sc_dryrun) + return (sc); + + if (agent_pf(sc, 1) != 0) + fatalx("pf"); + if (http_init() == -1) + fatalx("http_init"); + return (sc); } @@ -460,8 +471,8 @@ agent_pf(struct system_config *sc, int open) return (ret); } -int -agent_configure(struct system_config *sc, int noaction) +static int +agent_configure(struct system_config *sc) { struct ssh_pubkey *ssh; char *str1, *str2; @@ -476,25 +487,21 @@ agent_configure(struct system_config *sc, int noaction) } free(str1); - if (!noaction && - fileout(sc->sc_instance, "w", "/var/db/cloud-instance") != 0) + if (fileout(sc->sc_instance, "w", "/var/db/cloud-instance") != 0) log_warnx("instance failed"); /* hostname */ log_debug("%s: hostname %s", __func__, sc->sc_hostname); - if (!noaction && - fileout(sc->sc_hostname, "w", "/etc/myname") != 0) + if (fileout(sc->sc_hostname, "w", "/etc/myname") != 0) log_warnx("hostname failed"); else (void)shell("hostname", sc->sc_hostname, NULL); /* username */ log_debug("%s: username %s", __func__, sc->sc_username); - if (!noaction && - shell("useradd", "-L", "staff", "-G", "wheel", + if (shell("useradd", "-L", "staff", "-G", "wheel", "-m", sc->sc_username, NULL) != 0) log_warnx("username failed"); - if (fileout(sc->sc_username, "w", "/root/.forward") != 0) log_warnx(".forward failed"); @@ -506,8 +513,7 @@ agent_configure(struct system_config *sc, int noaction) "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; } else { - if (!noaction && - shell("usermod", "-p", sc->sc_password, + if (shell("usermod", "-p", sc->sc_password, sc->sc_username, NULL) != 0) log_warnx("password failed"); @@ -540,8 +546,7 @@ agent_configure(struct system_config *sc, int noaction) if (ssh->ssh_keyval == NULL) continue; log_debug("%s: key %s", __func__, ssh->ssh_keyval); - if (!noaction && - fileout(ssh->ssh_keyval, "a", + if (fileout(ssh->ssh_keyval, "a", "/home/%s/.ssh/authorized_keys", sc->sc_username) != 0) log_warnx("public key failed"); @@ -552,7 +557,7 @@ agent_configure(struct system_config *sc, int noaction) } log_debug("%s: %s", __func__, "/etc/rc.firsttime"); - if (!noaction && fileout("logger -s -t cloud-agent <sc_interface = argv[0]; - - if (agent_pf(sc, 1) != 0) - fatalx("pf"); - - if (http_init() == -1) - fatalx("http_init"); - /* * XXX Detect cloud with help from hostctl and sysctl * XXX in addition to the interface name. @@ -669,13 +666,18 @@ main(int argc, char *const *argv) else fatal("unsupported cloud interface %s", sc->sc_interface); + if (sc->sc_dryrun) { + agent_free(sc); + return (0); + } + if (agent_pf(sc, 0) != 0) fatalx("pf"); if (pledge("stdio cpath rpath wpath exec proc", NULL) == -1) fatal("pledge"); - if (ret == 0 && agent_configure(sc, noaction) != 0) + if (ret == 0 && agent_configure(sc) != 0) fatal("provisioning failed"); agent_free(sc); diff --git a/agent/main.h b/agent/main.h index cd4617e..efd9d6f 100644 --- a/agent/main.h +++ b/agent/main.h @@ -55,6 +55,7 @@ struct system_config { struct ssh_pubkeys sc_pubkeys; int sc_nullfd; + int sc_dryrun; void *sc_priv; }; @@ -75,7 +76,6 @@ char *get_line(u_int8_t *, size_t); char *get_word(u_int8_t *, size_t); int agent_addpubkey(struct system_config *, const char *, const char *); int agent_setpubkey(struct system_config *, const char *, const char *); -int agent_configure(struct system_config *, int); /* log.c */ void log_init(int, int); From cdf3317965aa81aa7ec8fe824b95af6bbbb120f5 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 14 Aug 2017 13:52:00 +0200 Subject: [PATCH 03/48] Print success in xml_parse() debug message --- agent/xml.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/agent/xml.c b/agent/xml.c index 049cbae..7590a97 100644 --- a/agent/xml.c +++ b/agent/xml.c @@ -342,9 +342,10 @@ xml_parse(struct xml *env, const char *file) ssize_t len; if ((fd = open(file, O_RDONLY)) == -1) { - log_debug("%s: open %s", __func__, file); + log_debug("%s: failed to open %s", __func__, file); return (-1); - } + } else + log_debug("%s: opened %s", __func__, file); do { if ((xml = XML_GetBuffer(parser, BUFSIZ)) == NULL) From eb9d5b440c2ef8d592cdb252c68e44bd06516bf7 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 14 Aug 2017 17:59:35 +0200 Subject: [PATCH 04/48] Configure Azure -n, but don't apply confiugration --- agent/azure.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index 6af3abe..5d2168a 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -77,12 +77,6 @@ azure(struct system_config *sc) goto done; } - if (sc->sc_dryrun) { - /* Return after backing up the ovf-env.xml file */ - ret = 0; - goto done; - } - if (azure_getendpoint(sc) != 0) { log_warnx("failed to get endpoint"); goto done; @@ -98,14 +92,16 @@ azure(struct system_config *sc) goto done; } - if (azure_keys(sc) != 0) { - log_warnx("failed to get transport keys"); - goto done; - } + if (!sc->sc_dryrun) { + if (azure_keys(sc) != 0) { + log_warnx("failed to get transport keys"); + goto done; + } - if (azure_certificates(sc) != 0) { - log_warnx("failed to get certificates"); - goto done; + if (azure_certificates(sc) != 0) { + log_warnx("failed to get certificates"); + goto done; + } } if (azure_reporthealth(sc, "Ready") != 0) { From ac66f160e04c4e96345b449eca97459d4f26602c Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 10 Jan 2018 14:13:49 +0100 Subject: [PATCH 05/48] Support for openstack meta_data.json, connect timeout, other fixes. --- agent/Makefile | 2 +- agent/azure.c | 1 + agent/cloudinit.c | 50 +++------------- agent/http.c | 4 +- agent/main.c | 145 +++++++++++++++++++++++++++++++++++++++++++--- agent/main.h | 31 ++++++++++ agent/openstack.c | 101 ++++++++++++++++++++++++++++++++ 7 files changed, 279 insertions(+), 55 deletions(-) create mode 100644 agent/openstack.c diff --git a/agent/Makefile b/agent/Makefile index 0bd3fac..2205fc1 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -1,5 +1,5 @@ PROG= cloud-agent -SRCS= main.c xml.c azure.c cloudinit.c http.c log.c +SRCS= azure.c cloudinit.c http.c json.c jsmn.c log.c openstack.c main.c xml.c BINDIR= /usr/local/libexec MANDIR= /usr/local/man/man diff --git a/agent/azure.c b/agent/azure.c index 5d2168a..9067b8f 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -64,6 +64,7 @@ azure(struct system_config *sc) int ret = -1; /* Apply defaults */ + free(sc->sc_username); if ((sc->sc_username = strdup("azure-user")) == NULL) { log_warnx("failed to set default user"); goto done; diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 58de1bb..54b277b 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -30,12 +30,11 @@ #include "xml.h" static int cloudinit_fetch(struct system_config *); -static char *cloudinit_get(struct system_config *, const char *, - enum strtype); int ec2(struct system_config *sc) { + free(sc->sc_username); if ((sc->sc_username = strdup("ec2-user")) == NULL || (sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { log_warnx("failed to set defaults"); @@ -49,8 +48,7 @@ int cloudinit(struct system_config *sc) { /* XXX get endpoint from DHCP lease file */ - if ((sc->sc_username = strdup("puffy")) == NULL || - (sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { + if ((sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { log_warnx("failed to set defaults"); return (-1); } @@ -58,35 +56,6 @@ cloudinit(struct system_config *sc) return (cloudinit_fetch(sc)); } -static char * -cloudinit_get(struct system_config *sc, const char *path, enum strtype type) -{ - struct httpget *g = NULL; - char *str = NULL; - - log_debug("%s: %s", __func__, path); - - g = http_get(&sc->sc_addr, 1, - sc->sc_endpoint, 80, path, NULL, 0, NULL); - if (g != NULL && g->code == 200 && g->bodypartsz > 0) { - switch (type) { - case TEXT: - /* multi-line string, always printable */ - str = get_string(g->bodypart, g->bodypartsz); - break; - case LINE: - str = get_line(g->bodypart, g->bodypartsz); - break; - case WORD: - str = get_word(g->bodypart, g->bodypartsz); - break; - } - } - http_get_free(g); - - return (str); -} - static int cloudinit_fetch(struct system_config *sc) { @@ -96,37 +65,32 @@ cloudinit_fetch(struct system_config *sc) sc->sc_addr.ip = sc->sc_endpoint; sc->sc_addr.family = 4; - if (sc->sc_dryrun) - return (0); - /* instance-id */ - if ((sc->sc_instance = cloudinit_get(sc, + if ((sc->sc_instance = metadata(sc, "/latest/meta-data/instance-id", WORD)) == NULL) goto fail; /* hostname */ - if ((sc->sc_hostname = cloudinit_get(sc, + if ((sc->sc_hostname = metadata(sc, "/latest/meta-data/local-hostname", WORD)) == NULL) goto fail; /* pubkey */ - if ((str = cloudinit_get(sc, + if ((str = metadata(sc, "/latest/meta-data/public-keys/0/openssh-key", LINE)) == NULL) goto fail; if (agent_addpubkey(sc, str, NULL) != 0) goto fail; /* optional username - this is an extension by meta-data(8) */ - if ((str = cloudinit_get(sc, - "/latest/meta-data/username", WORD)) != NULL) { + if ((str = metadata(sc, "/latest/meta-data/username", WORD)) != NULL) { free(sc->sc_username); sc->sc_username = str; str = NULL; } /* userdata */ - if ((sc->sc_userdata = cloudinit_get(sc, - "/latest/user-data", TEXT)) == NULL) + if ((sc->sc_userdata = metadata(sc, "/latest/user-data", TEXT)) == NULL) goto fail; ret = 0; diff --git a/agent/http.c b/agent/http.c index f871745..1efd92b 100644 --- a/agent/http.c +++ b/agent/http.c @@ -277,8 +277,8 @@ again: if (fd == -1) { warn("%s: socket", addrs[cur].ip); goto again; - } else if (connect(fd, (struct sockaddr *)&ss, len) == -1) { - warn("%s: connect", addrs[cur].ip); + } else if (connect_wait(fd, (struct sockaddr *)&ss, len) == -1) { + warn("http://%s%s", addrs[cur].ip, path); close(fd); goto again; } diff --git a/agent/main.c b/agent/main.c index 516c20e..b0e177a 100644 --- a/agent/main.c +++ b/agent/main.c @@ -26,16 +26,20 @@ #include #include #include +#include +#include #include "main.h" #include "xml.h" __dead void usage(void); -static struct system_config *agent_init(const char *, int); +static struct system_config *agent_init(const char *, int, int); static int agent_configure(struct system_config *); static void agent_free(struct system_config *); static int agent_pf(struct system_config *, int); static void agent_unconfigure(void); +static char *metadata_parse(char *, size_t, enum strtype); +static int agent_timeout; int shell(const char *arg, ...) @@ -289,7 +293,7 @@ get_word(u_int8_t *ptr, size_t len) } static struct system_config * -agent_init(const char *ifname, int dryrun) +agent_init(const char *ifname, int dryrun, int timeout) { struct system_config *sc; @@ -298,12 +302,18 @@ agent_init(const char *ifname, int dryrun) sc->sc_interface = ifname; sc->sc_dryrun = dryrun ? 1 : 0; + sc->sc_timeout = agent_timeout = timeout < 1 ? -1 : timeout * 1000; TAILQ_INIT(&sc->sc_pubkeys); if ((sc->sc_nullfd = open("/dev/null", O_RDWR)) == -1) { free(sc); return (NULL); } + if ((sc->sc_username = strdup("puffy")) == NULL) { + free(sc); + close(sc->sc_nullfd); + return (NULL); + } if (sc->sc_dryrun) return (sc); @@ -599,12 +609,125 @@ agent_unconfigure(void) "permit keepenv nopass root\n", "w", "/etc/doas.conf"); } + +static char * +metadata_parse(char *s, size_t sz, enum strtype type) +{ + char *str; + + switch (type) { + case TEXT: + /* multi-line string, always printable */ + str = get_string(s, sz); + break; + case LINE: + str = get_line(s, sz); + break; + case WORD: + str = get_word(s, sz); + break; + } + + return (str); +} + +char * +metadata(struct system_config *sc, const char *path, enum strtype type) +{ + struct httpget *g = NULL; + char *str = NULL; + + log_debug("%s: %s", __func__, path); + + g = http_get(&sc->sc_addr, 1, + sc->sc_endpoint, 80, path, NULL, 0, NULL); + if (g != NULL && g->code == 200 && g->bodypartsz > 0) + str = metadata_parse(g->bodypart, g->bodypartsz, type); + http_get_free(g); + + return (str); +} + +char * +metadata_file(struct system_config *sc, const char *name, enum strtype type) +{ + FILE *fp, *mfp; + char buf[BUFSIZ], *mbuf, *str; + size_t sz, msz; + + if ((fp = fopen(name, "r")) == NULL) { + log_warn("%s: could not open %s", __func__, name); + return (NULL); + } + + if ((mfp = open_memstream(&mbuf, &msz)) == NULL) { + log_warn("%s: open_memstream", __func__); + fclose(fp); + return (NULL); + } + + do { + if ((sz = fread(buf, 1, sizeof(buf), fp)) < 1) + break; + if (fwrite(buf, sz, 1, mfp) != 1) + break; + } while (sz == sizeof(buf)); + + fclose(mfp); + fclose(fp); + + str = metadata_parse(mbuf, msz, type); + free(mbuf); + + return (str); +} + +int +connect_wait(int s, const struct sockaddr *name, socklen_t namelen) +{ + struct pollfd pfd[1]; + int error = 0, flag; + socklen_t errlen = sizeof(error); + + if ((flag = fcntl(s, F_GETFL, 0)) == -1 || + (fcntl(s, F_SETFL, flag | O_NONBLOCK)) == -1) + return (-1); + + error = connect(s, name, namelen); + do { + pfd[0].fd = s; + pfd[0].events = POLLOUT; + + if ((error = poll(pfd, 1, agent_timeout)) == -1) + continue; + if (error == 0) { + error = ETIMEDOUT; + goto done; + } + if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &errlen) == -1) + continue; + } while (error != 0 && error == EINTR); + + done: + if (fcntl(s, F_SETFL, flag & ~O_NONBLOCK) == -1) + return (-1); + + if (error != 0) { + errno = error; + return (-1); + } + + log_debug("%s:%d error %d", __func__, __LINE__, error); + + return (0); +} + __dead void usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-nuv] interface\n", + fprintf(stderr, "usage: %s [-nuv] [-t 3] interface\n", __progname); exit(1); } @@ -614,9 +737,10 @@ main(int argc, char *const *argv) { struct system_config *sc; int verbose = 0, dryrun = 0, unconfigure = 0; - int ch, ret; + int ch, ret, timeout = CONNECT_TIMEOUT; + const char *error = NULL; - while ((ch = getopt(argc, argv, "nvu")) != -1) { + while ((ch = getopt(argc, argv, "nvt:u")) != -1) { switch (ch) { case 'n': dryrun = 1; @@ -624,6 +748,11 @@ main(int argc, char *const *argv) case 'v': verbose += 2; break; + case 't': + timeout = strtonum(optarg, -1, 86400, &error); + if (error != NULL) + fatalx("invalid timeout: %s", error); + break; case 'u': unconfigure = 1; break; @@ -650,7 +779,7 @@ main(int argc, char *const *argv) if (pledge("stdio cpath rpath wpath exec proc dns inet", NULL) == -1) fatal("pledge"); - if ((sc = agent_init(argv[0], dryrun)) == NULL) + if ((sc = agent_init(argv[0], dryrun, timeout)) == NULL) fatalx("agent"); /* @@ -661,10 +790,8 @@ main(int argc, char *const *argv) ret = azure(sc); else if (strcmp("xnf0", sc->sc_interface) == 0) ret = ec2(sc); - else if (strcmp("vio0", sc->sc_interface) == 0) - ret = cloudinit(sc); else - fatal("unsupported cloud interface %s", sc->sc_interface); + ret = openstack(sc); if (sc->sc_dryrun) { agent_free(sc); diff --git a/agent/main.h b/agent/main.h index efd9d6f..8a1b53c 100644 --- a/agent/main.h +++ b/agent/main.h @@ -19,10 +19,14 @@ #include #include +#include #include #include #include "http.h" +#include "jsmn.h" + +#define CONNECT_TIMEOUT 10 /* in seconds */ enum strtype { WORD, @@ -46,6 +50,7 @@ struct system_config { char *sc_userdata; char *sc_endpoint; char *sc_instance; + int sc_timeout; const char *sc_ovfenv; const char *sc_interface; @@ -59,6 +64,26 @@ struct system_config { void *sc_priv; }; +struct jsmnp; +struct jsmnn { + struct parse *p; + union { + char *str; + struct jsmnp *obj; + struct jsmnn **array; + } d; + size_t fields; + jsmntype_t type; +}; + +/* json.c */ +struct jsmnn *json_parse(const char *, size_t); +void json_free(struct jsmnn *); +struct jsmnn *json_getarrayobj(struct jsmnn *); +struct jsmnn *json_getarray(struct jsmnn *, const char *); +struct jsmnn *json_getobj(struct jsmnn *, const char *); +char *json_getstr(struct jsmnn *, const char *); + /* azure.c */ int azure(struct system_config *); @@ -66,6 +91,9 @@ int azure(struct system_config *); int ec2(struct system_config *); int cloudinit(struct system_config *); +/* openstack.c */ +int openstack(struct system_config *); + /* main.c */ int shell(const char *, ...); int shellout(const char *, char **, const char *, ...); @@ -76,6 +104,9 @@ char *get_line(u_int8_t *, size_t); char *get_word(u_int8_t *, size_t); int agent_addpubkey(struct system_config *, const char *, const char *); int agent_setpubkey(struct system_config *, const char *, const char *); +char *metadata(struct system_config *, const char *, enum strtype); +char *metadata_file(struct system_config *, const char *, enum strtype); +int connect_wait(int, const struct sockaddr *, socklen_t); /* log.c */ void log_init(int, int); diff --git a/agent/openstack.c b/agent/openstack.c new file mode 100644 index 0000000..19e329d --- /dev/null +++ b/agent/openstack.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018 Reyk Floeter + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "http.h" +#include "xml.h" + +static int openstack_fetch(struct system_config *); + +int +openstack(struct system_config *sc) +{ + if ((sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { + log_warnx("failed to set defaults"); + return (-1); + } + + if (openstack_fetch(sc) != 0) { + free(sc->sc_endpoint); + return (cloudinit(sc)); + } + return (0); +} + +static int +openstack_fetch(struct system_config *sc) +{ + int ret = -1; + char *json = NULL, *str; + struct jsmnn *j = NULL, *o, *f; + size_t i; + + sc->sc_addr.ip = sc->sc_endpoint; + sc->sc_addr.family = 4; + + /* meta_data, we don't handle vendor_data */ + if ((json = metadata(sc, + "/openstack/latest/meta_data.json", TEXT)) == NULL) + goto fail; + + if ((j = json_parse(json, strlen(json))) == NULL) + goto fail; + + /* instance-id */ + if ((sc->sc_instance = json_getstr(j, "uuid")) == NULL) + goto fail; + + /* hostname */ + if ((sc->sc_hostname = json_getstr(j, "hostname")) == NULL) + goto fail; + + /* public keys */ + if ((o = json_getarray(j, "keys")) == NULL) + goto fail; + for (i = 0; i < o->fields; i++) { + if ((f = json_getarrayobj(o->d.array[i])) == NULL) + continue; + if ((str = json_getstr(f, "data")) == NULL) + continue; + if (agent_addpubkey(sc, str, NULL) != 0) { + free(str); + goto fail; + } + free(str); + } + + /* userdata */ + if ((sc->sc_userdata = + metadata(sc, "/openstack/latest/user-data", TEXT)) == NULL) + goto fail; + + ret = 0; + fail: + json_free(j); + free(json); + return (ret); +} From ee473a4bd69a36f8bdcc7a84b92e3c4b741eea35 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 10 Jan 2018 15:10:58 +0100 Subject: [PATCH 06/48] Try to get endpoint from "dhcp-server-identifier" --- agent/azure.c | 53 +-------------------------------------- agent/cloudinit.c | 11 ++++----- agent/main.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++- agent/main.h | 2 ++ agent/openstack.c | 9 +++---- 5 files changed, 74 insertions(+), 64 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index 9067b8f..aa258cf 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -51,7 +51,6 @@ static struct httpget static int azure_keys(struct system_config *); static int azure_getpubkeys(struct system_config *); -static int azure_getendpoint(struct system_config *); static int azure_getovfenv(struct system_config *); static int azure_versions(struct system_config *); static int azure_goalstate(struct system_config *); @@ -78,7 +77,7 @@ azure(struct system_config *sc) goto done; } - if (azure_getendpoint(sc) != 0) { + if (dhcp_getendpoint(sc) != 0) { log_warnx("failed to get endpoint"); goto done; } @@ -794,53 +793,3 @@ azure_getovfenv(struct system_config *sc) xml_free(&xml); return (ret); } - -static int -azure_getendpoint(struct system_config *sc) -{ - char path[PATH_MAX], buf[BUFSIZ], *ep = NULL; - int a[4]; - FILE *fp; - - if ((size_t)snprintf(path, sizeof(path), "/var/db/dhclient.leases.%s", - sc->sc_interface) >= sizeof(path)) { - log_debug("%s: invalid path", __func__); - return (-1); - } - - if ((fp = fopen(path, "r")) == NULL) { - log_debug("%s: failed to open %s", __func__, path); - return (-1); - } - - while (fgets(buf, sizeof(buf), fp) != NULL) { - buf[strcspn(buf, ";\n")] = '\0'; - - /* Find last occurence of option-245 */ - if (sscanf(buf, " option option-245 %x:%x:%x:%x", - &a[0], &a[1], &a[2], &a[3]) == 4) { - free(ep); - if (asprintf(&ep, "%d.%d.%d.%d", - a[0], a[1], a[2], a[3]) == -1) { - log_debug("%s: asprintf", __func__); - fclose(fp); - return (-1); - } - } - } - - fclose(fp); - - if (ep == NULL) { - log_debug("%s: endpoint not found", __func__); - return (-1); - } - - sc->sc_endpoint = ep; - sc->sc_addr.ip = sc->sc_endpoint; - sc->sc_addr.family = 4; - - log_debug("%s: %s", __func__, ep); - - return (0); -} diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 54b277b..5247a35 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -36,7 +36,7 @@ ec2(struct system_config *sc) { free(sc->sc_username); if ((sc->sc_username = strdup("ec2-user")) == NULL || - (sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { + (sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { log_warnx("failed to set defaults"); return (-1); } @@ -47,8 +47,8 @@ ec2(struct system_config *sc) int cloudinit(struct system_config *sc) { - /* XXX get endpoint from DHCP lease file */ - if ((sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { + if ((dhcp_getendpoint(sc) == -1) && + (sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { log_warnx("failed to set defaults"); return (-1); } @@ -89,9 +89,8 @@ cloudinit_fetch(struct system_config *sc) str = NULL; } - /* userdata */ - if ((sc->sc_userdata = metadata(sc, "/latest/user-data", TEXT)) == NULL) - goto fail; + /* userdata (optional) */ + sc->sc_userdata = metadata(sc, "/latest/user-data", TEXT); ret = 0; fail: diff --git a/agent/main.c b/agent/main.c index b0e177a..26edb86 100644 --- a/agent/main.c +++ b/agent/main.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -717,7 +718,67 @@ connect_wait(int s, const struct sockaddr *name, socklen_t namelen) return (-1); } - log_debug("%s:%d error %d", __func__, __LINE__, error); + return (0); +} + +int +dhcp_getendpoint(struct system_config *sc) +{ + char path[PATH_MAX], buf[BUFSIZ], *ep = NULL; + int a[4], has245 = 0; + size_t sz; + FILE *fp; + + if ((size_t)snprintf(path, sizeof(path), "/var/db/dhclient.leases.%s", + sc->sc_interface) >= sizeof(path)) { + log_debug("%s: invalid path", __func__); + return (-1); + } + + if ((fp = fopen(path, "r")) == NULL) { + log_debug("%s: failed to open %s", __func__, path); + return (-1); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + buf[strcspn(buf, ";\n")] = '\0'; + + /* Find last occurence of dhcp-server-identifier */ + sz = strlen(" option dhcp-server-identifier "); + if (!has245 && + strncmp(buf, " option dhcp-server-identifier ", sz) == 0) { + free(ep); + if ((ep = strdup(buf + sz)) == NULL) { + log_debug("%s: strdup", __func__); + fclose(fp); + return (-1); + } + } + + /* Find last occurence of option-245 (only on Azure) */ + if (sscanf(buf, " option option-245 %x:%x:%x:%x", + &a[0], &a[1], &a[2], &a[3]) == 4) { + has245 = 1; + free(ep); + if (asprintf(&ep, "%d.%d.%d.%d", + a[0], a[1], a[2], a[3]) == -1) { + log_debug("%s: asprintf", __func__); + fclose(fp); + return (-1); + } + } + } + + fclose(fp); + + if (ep == NULL) + return (-1); + + sc->sc_endpoint = ep; + sc->sc_addr.ip = sc->sc_endpoint; + sc->sc_addr.family = 4; + + log_debug("%s: %s", __func__, ep); return (0); } diff --git a/agent/main.h b/agent/main.h index 8a1b53c..7167d6e 100644 --- a/agent/main.h +++ b/agent/main.h @@ -26,6 +26,7 @@ #include "http.h" #include "jsmn.h" +#define DEFAULT_ENDPOINT "169.254.169.254" #define CONNECT_TIMEOUT 10 /* in seconds */ enum strtype { @@ -107,6 +108,7 @@ int agent_setpubkey(struct system_config *, const char *, const char *); char *metadata(struct system_config *, const char *, enum strtype); char *metadata_file(struct system_config *, const char *, enum strtype); int connect_wait(int, const struct sockaddr *, socklen_t); +int dhcp_getendpoint(struct system_config *); /* log.c */ void log_init(int, int); diff --git a/agent/openstack.c b/agent/openstack.c index 19e329d..a060c3d 100644 --- a/agent/openstack.c +++ b/agent/openstack.c @@ -34,7 +34,8 @@ static int openstack_fetch(struct system_config *); int openstack(struct system_config *sc) { - if ((sc->sc_endpoint = strdup("169.254.169.254")) == NULL) { + if ((dhcp_getendpoint(sc) == -1) && + (sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { log_warnx("failed to set defaults"); return (-1); } @@ -88,10 +89,8 @@ openstack_fetch(struct system_config *sc) free(str); } - /* userdata */ - if ((sc->sc_userdata = - metadata(sc, "/openstack/latest/user-data", TEXT)) == NULL) - goto fail; + /* userdata (optional) */ + sc->sc_userdata = metadata(sc, "/openstack/latest/user_data", TEXT); ret = 0; fail: From eaa8b96541ac8a82b78060008956968d4ca36953 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 7 May 2018 12:23:02 +0200 Subject: [PATCH 07/48] Add missing json files --- agent/jsmn.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++ agent/jsmn.h | 97 +++++++++++++++ agent/json.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 748 insertions(+) create mode 100644 agent/jsmn.c create mode 100644 agent/jsmn.h create mode 100644 agent/json.c diff --git a/agent/jsmn.c b/agent/jsmn.c new file mode 100644 index 0000000..26676fa --- /dev/null +++ b/agent/jsmn.c @@ -0,0 +1,332 @@ +/* + Copyright (c) 2010 Serge A. Zaitsev + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE.* + */ +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/agent/jsmn.h b/agent/jsmn.h new file mode 100644 index 0000000..0a3c5d7 --- /dev/null +++ b/agent/jsmn.h @@ -0,0 +1,97 @@ +/* + Copyright (c) 2010 Serge A. Zaitsev + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE.* + */ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/agent/json.c b/agent/json.c new file mode 100644 index 0000000..cb07e4e --- /dev/null +++ b/agent/json.c @@ -0,0 +1,319 @@ +/* $OpenBSD: json.c,v 1.9 2017/01/24 13:32:55 jsing Exp $ */ + +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "jsmn.h" +#include "main.h" + +struct jsmnp; + +/* + * Objects consist of node pairs: the left-hand side (before the colon) + * and the right-hand side---the data. + */ +struct jsmnp { + struct jsmnn *lhs; /* left of colon */ + struct jsmnn *rhs; /* right of colon */ +}; + +/* + * Object for converting the JSMN token array into a tree. + */ +struct parse { + struct jsmnn *nodes; /* all nodes */ + size_t cur; /* current number */ + size_t max; /* nodes in "nodes" */ +}; + +/* + * Recursive part for convertin a JSMN token array into a tree. + * See "example/jsondump.c" for its construction (it's the same except + * for how it handles allocation errors). + */ +static ssize_t +build(struct parse *parse, struct jsmnn **np, + jsmntok_t *t, const char *js, size_t sz) +{ + size_t i, j; + struct jsmnn *n; + ssize_t tmp; + + if (sz == 0) + return 0; + + assert(parse->cur < parse->max); + n = *np = &parse->nodes[parse->cur++]; + n->p = parse; + n->type = t->type; + + switch (t->type) { + case JSMN_STRING: + /* FALLTHROUGH */ + case JSMN_PRIMITIVE: + n->fields = 1; + n->d.str = strndup + (js + t->start, + t->end - t->start); + if (n->d.str == NULL) + break; + return 1; + case JSMN_OBJECT: + n->fields = t->size; + n->d.obj = calloc(n->fields, + sizeof(struct jsmnp)); + if (n->d.obj == NULL) + break; + for (i = j = 0; i < (size_t)t->size; i++) { + tmp = build(parse, + &n->d.obj[i].lhs, + t + 1 + j, js, sz - j); + if (tmp < 0) + break; + j += tmp; + tmp = build(parse, + &n->d.obj[i].rhs, + t + 1 + j, js, sz - j); + if (tmp < 0) + break; + j += tmp; + } + if (i < (size_t)t->size) + break; + return j + 1; + case JSMN_ARRAY: + n->fields = t->size; + n->d.array = calloc(n->fields, + sizeof(struct jsmnn *)); + if (n->d.array == NULL) + break; + for (i = j = 0; i < (size_t)t->size; i++) { + tmp = build(parse, + &n->d.array[i], + t + 1 + j, js, sz - j); + if (tmp < 0) + break; + j += tmp; + } + if (i < (size_t)t->size) + break; + return j + 1; + default: + break; + } + + return -1; +} + +/* + * Fully free up a parse sequence. + * This handles all nodes sequentially, not recursively. + */ +static void +jsmnparse_free(struct parse *p) +{ + size_t i; + + if (p == NULL) + return; + for (i = 0; i < p->max; i++) { + struct jsmnn *n = &p->nodes[i]; + switch (n->type) { + case JSMN_ARRAY: + free(n->d.array); + break; + case JSMN_OBJECT: + free(n->d.obj); + break; + case JSMN_PRIMITIVE: + free(n->d.str); + break; + case JSMN_STRING: + free(n->d.str); + break; + case JSMN_UNDEFINED: + break; + } + } + free(p->nodes); + free(p); +} + +/* + * Allocate a tree representation of "t". + * This returns NULL on allocation failure or when sz is zero, in which + * case all resources allocated along the way are freed already. + */ +static struct jsmnn * +jsmntree_alloc(jsmntok_t *t, const char *js, size_t sz) +{ + struct jsmnn *first; + struct parse *p; + + if (sz == 0) + return NULL; + + p = calloc(1, sizeof(struct parse)); + if (p == NULL) + return NULL; + + p->max = sz; + p->nodes = calloc(p->max, sizeof(struct jsmnn)); + if (p->nodes == NULL) { + free(p); + return NULL; + } + + if (build(p, &first, t, js, sz) < 0) { + jsmnparse_free(p); + first = NULL; + } + + return first; +} + +/* + * Call through to free parse contents. + */ +void +json_free(struct jsmnn *first) +{ + + if (first != NULL) + jsmnparse_free(first->p); +} + +/* + * Just check that the array object is in fact an object. + */ +struct jsmnn * +json_getarrayobj(struct jsmnn *n) +{ + + return n->type != JSMN_OBJECT ? NULL : n; +} + +/* + * Extract an array from the returned JSON object, making sure that it's + * the correct type. + * Returns NULL on failure. + */ +struct jsmnn * +json_getarray(struct jsmnn *n, const char *name) +{ + size_t i; + + if (n->type != JSMN_OBJECT) + return NULL; + for (i = 0; i < n->fields; i++) { + if (n->d.obj[i].lhs->type != JSMN_STRING && + n->d.obj[i].lhs->type != JSMN_PRIMITIVE) + continue; + else if (strcmp(name, n->d.obj[i].lhs->d.str)) + continue; + break; + } + if (i == n->fields) + return NULL; + if (n->d.obj[i].rhs->type != JSMN_ARRAY) + return NULL; + return n->d.obj[i].rhs; +} + +/* + * Extract a single string from the returned JSON object, making sure + * that it's the correct type. + * Returns NULL on failure. + */ +char * +json_getstr(struct jsmnn *n, const char *name) +{ + size_t i; + char *cp; + + if (n->type != JSMN_OBJECT) + return NULL; + for (i = 0; i < n->fields; i++) { + if (n->d.obj[i].lhs->type != JSMN_STRING && + n->d.obj[i].lhs->type != JSMN_PRIMITIVE) + continue; + else if (strcmp(name, n->d.obj[i].lhs->d.str)) + continue; + break; + } + if (i == n->fields) + return NULL; + if (n->d.obj[i].rhs->type != JSMN_STRING && + n->d.obj[i].rhs->type != JSMN_PRIMITIVE) + return NULL; + + cp = strdup(n->d.obj[i].rhs->d.str); + if (cp == NULL) + warn("strdup"); + return cp; +} + +/* + * Parse an HTTP response body from a buffer of size "sz". + * Returns an opaque pointer on success, otherwise NULL on error. + */ +struct jsmnn * +json_parse(const char *buf, size_t sz) +{ + struct jsmnn *n; + jsmn_parser p; + jsmntok_t *tok; + int r; + size_t tokcount; + + jsmn_init(&p); + tokcount = 128; + + /* Do this until we don't need any more tokens. */ +again: + tok = calloc(tokcount, sizeof(jsmntok_t)); + if (tok == NULL) { + warn("calloc"); + return NULL; + } + + /* Actually try to parse the JSON into the tokens. */ + + r = jsmn_parse(&p, buf, sz, tok, tokcount); + if (r < 0 && r == JSMN_ERROR_NOMEM) { + tokcount *= 2; + free(tok); + goto again; + } else if (r < 0) { + warnx("jsmn_parse: %d", r); + free(tok); + return NULL; + } + + /* Now parse the tokens into a tree. */ + + n = jsmntree_alloc(tok, buf, r); + free(tok); + return n; +} From 9ffe04f62f7f17b9a8fc41aa96ed649c4e4789ae Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 7 May 2018 18:10:27 +0200 Subject: [PATCH 08/48] Fall back to meta-data/public-keys if meta-data/public-keys/0/openssh-key is not available. --- agent/cloudinit.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 5247a35..91149ac 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -77,7 +77,9 @@ cloudinit_fetch(struct system_config *sc) /* pubkey */ if ((str = metadata(sc, - "/latest/meta-data/public-keys/0/openssh-key", LINE)) == NULL) + "/latest/meta-data/public-keys/0/openssh-key", LINE)) == NULL && + (str = metadata(sc, + "/latest/meta-data/public-keys", LINE)) == NULL) goto fail; if (agent_addpubkey(sc, str, NULL) != 0) goto fail; From 4953fa418c6f97ca1bcce9c214bde41755916de2 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 09:48:08 +0200 Subject: [PATCH 09/48] Rename label --- agent/azure.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index aa258cf..d4a0b4a 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -66,7 +66,7 @@ azure(struct system_config *sc) free(sc->sc_username); if ((sc->sc_username = strdup("azure-user")) == NULL) { log_warnx("failed to set default user"); - goto done; + goto fail; } sc->sc_cdrom = "/dev/cd0c"; sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; @@ -74,43 +74,43 @@ azure(struct system_config *sc) if (azure_getovfenv(sc) != 0) { log_warnx("failed to get ovf-env.xml"); - goto done; + goto fail; } if (dhcp_getendpoint(sc) != 0) { log_warnx("failed to get endpoint"); - goto done; + goto fail; } if (azure_versions(sc) != 0) { log_warnx("failed to get endpoint versions"); - goto done; + goto fail; } if (azure_goalstate(sc) != 0) { log_warnx("failed to get goalstate"); - goto done; + goto fail; } if (!sc->sc_dryrun) { if (azure_keys(sc) != 0) { log_warnx("failed to get transport keys"); - goto done; + goto fail; } if (azure_certificates(sc) != 0) { log_warnx("failed to get certificates"); - goto done; + goto fail; } } if (azure_reporthealth(sc, "Ready") != 0) { log_warnx("failed to report health"); - goto done; + goto fail; } ret = 0; - done: + fail: free(az_config.az_container); free(az_config.az_pubkeyval); From cb7edf897eb66d85c17411dd3f3aa285ab5ef343 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 09:48:30 +0200 Subject: [PATCH 10/48] Fix return value on error --- agent/cloudinit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 91149ac..2bf4b91 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -59,7 +59,7 @@ cloudinit(struct system_config *sc) static int cloudinit_fetch(struct system_config *sc) { - int ret = 0; + int ret = -1; char *str = NULL; sc->sc_addr.ip = sc->sc_endpoint; From 73f066699f68a1f1078f03923e7bd98703860ed7 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 09:48:59 +0200 Subject: [PATCH 11/48] Only link to libtls (which will pull libssl/crypto by itself) --- agent/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/Makefile b/agent/Makefile index 2205fc1..39d7743 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -15,7 +15,7 @@ CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith CFLAGS+= -Wsign-compare -Wcast-qual -LDADD+= -lexpat -ltls -lssl -lcrypto +LDADD+= -lexpat -ltls DPADD+= ${LIBEXPAT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} .include From 155d2168450fc697db14acef558292582fd01c30 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 09:51:32 +0200 Subject: [PATCH 12/48] Add support for user-data scripts --- agent/main.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++---- agent/main.h | 6 ++-- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/agent/main.c b/agent/main.c index 26edb86..80524f9 100644 --- a/agent/main.c +++ b/agent/main.c @@ -16,6 +16,9 @@ #include #include +#include + +#include #include #include @@ -24,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -38,8 +42,10 @@ static struct system_config *agent_init(const char *, int, int); static int agent_configure(struct system_config *); static void agent_free(struct system_config *); static int agent_pf(struct system_config *, int); +static int agent_userdata(const unsigned char *, size_t); static void agent_unconfigure(void); static char *metadata_parse(char *, size_t, enum strtype); + static int agent_timeout; int @@ -247,7 +253,7 @@ enable_output(struct system_config *sc, int fd, int oldfd) } char * -get_string(u_int8_t *ptr, size_t len) +get_string(const unsigned char *ptr, size_t len) { size_t i; @@ -265,7 +271,7 @@ get_string(u_int8_t *ptr, size_t len) } char * -get_line(u_int8_t *ptr, size_t len) +get_line(const unsigned char *ptr, size_t len) { size_t i; @@ -280,7 +286,7 @@ get_line(u_int8_t *ptr, size_t len) } char * -get_word(u_int8_t *ptr, size_t len) +get_word(const unsigned char *ptr, size_t len) { size_t i; @@ -487,6 +493,8 @@ agent_configure(struct system_config *sc) { struct ssh_pubkey *ssh; char *str1, *str2; + unsigned char *userdata; + size_t len; /* Skip configuration on the same instance */ if ((str1 = filein("r", "/var/db/cloud-instance")) != NULL) { @@ -564,7 +572,18 @@ agent_configure(struct system_config *sc) } if (sc->sc_userdata) { - /* XXX */ + /* + * The decoded base64 string is smaller than the + * userdata; it is safe to allocate the same length. + */ + len = strlen(sc->sc_userdata); + if ((userdata = calloc(1, len + 1)) == NULL) + log_warnx("failed to allocate user-data"); + else if ((len = b64_pton(sc->sc_userdata, userdata, len)) < 1) + log_warnx("failed to decode user-data"); + else + (void)agent_userdata(userdata, len); + free(userdata); } log_debug("%s: %s", __func__, "/etc/rc.firsttime"); @@ -582,6 +601,55 @@ agent_configure(struct system_config *sc) return (0); } +static int +agent_userdata(const unsigned char *userdata, size_t len) +{ + char *shebang = NULL, *str = NULL, *line = NULL; + const char *file; + int ret = -1; + + /* XXX add support for gzip-encoded user-data */ + if ((shebang = get_line(userdata, len)) == NULL) { + log_warnx("failed to decode shebang from user-data"); + goto fail; + } + + log_debug("%s: user-data: %s", __func__, shebang); + + if (strlen(shebang) <= 2 || strncmp("#!", shebang, 2) != 0) { + log_warnx("unsupported user-data type"); + goto fail; + } + + /* now get the whole script */ + if ((str = get_string(userdata, len)) == NULL) { + log_warnx("invalid user-data script"); + goto fail; + } + + /* write user-data script into file */ + file = "/var/run/user-data"; + if (fileout(str, "w", file) != 0) { + log_warnx("failed to write user-data"); + goto fail; + } + + /* and call it from rc.firsttime later on boot */ + if (asprintf(&line, + "logger -s -t cloud-agent \"running %s\"\n" + "%s %s\nrm %s\n", file, shebang + 2, file, file) == -1 || + fileout(line, "a", "/etc/rc.firsttime") != 0) + log_warnx("failed to add user-data script"); + + ret = 0; + fail: + free(line); + free(str); + free(shebang); + + return (ret); +} + void agent_unconfigure(void) { @@ -638,10 +706,11 @@ metadata(struct system_config *sc, const char *path, enum strtype type) struct httpget *g = NULL; char *str = NULL; - log_debug("%s: %s", __func__, path); - g = http_get(&sc->sc_addr, 1, sc->sc_endpoint, 80, path, NULL, 0, NULL); + if (g != NULL) + log_debug("%s: HTTP %d %s", __func__, g->code, path); + if (g != NULL && g->code == 200 && g->bodypartsz > 0) str = metadata_parse(g->bodypart, g->bodypartsz, type); http_get_free(g); diff --git a/agent/main.h b/agent/main.h index 7167d6e..fb2873a 100644 --- a/agent/main.h +++ b/agent/main.h @@ -100,9 +100,9 @@ int shell(const char *, ...); int shellout(const char *, char **, const char *, ...); int disable_output(struct system_config *, int); int enable_output(struct system_config *, int, int); -char *get_string(u_int8_t *, size_t); -char *get_line(u_int8_t *, size_t); -char *get_word(u_int8_t *, size_t); +char *get_string(const unsigned char *, size_t); +char *get_line(const unsigned char *, size_t); +char *get_word(const unsigned char *, size_t); int agent_addpubkey(struct system_config *, const char *, const char *); int agent_setpubkey(struct system_config *, const char *, const char *); char *metadata(struct system_config *, const char *, enum strtype); From f48b2bc2b974920977c28879a4cb24346ff58a47 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 10:03:38 +0200 Subject: [PATCH 13/48] Make the public key optional but print a warning --- agent/cloudinit.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 2bf4b91..c6bf38c 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -75,13 +75,13 @@ cloudinit_fetch(struct system_config *sc) "/latest/meta-data/local-hostname", WORD)) == NULL) goto fail; - /* pubkey */ + /* optional pubkey */ if ((str = metadata(sc, "/latest/meta-data/public-keys/0/openssh-key", LINE)) == NULL && (str = metadata(sc, "/latest/meta-data/public-keys", LINE)) == NULL) - goto fail; - if (agent_addpubkey(sc, str, NULL) != 0) + log_warnx("failed to get public key"); + else if (agent_addpubkey(sc, str, NULL) != 0) goto fail; /* optional username - this is an extension by meta-data(8) */ From 5de42d6464b6532ea9c5b2ea67906a7e6dd7f993 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 12:08:58 +0200 Subject: [PATCH 14/48] user-data is not always base64-encoded. --- agent/main.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/agent/main.c b/agent/main.c index 80524f9..40d5d61 100644 --- a/agent/main.c +++ b/agent/main.c @@ -493,8 +493,6 @@ agent_configure(struct system_config *sc) { struct ssh_pubkey *ssh; char *str1, *str2; - unsigned char *userdata; - size_t len; /* Skip configuration on the same instance */ if ((str1 = filein("r", "/var/db/cloud-instance")) != NULL) { @@ -572,18 +570,9 @@ agent_configure(struct system_config *sc) } if (sc->sc_userdata) { - /* - * The decoded base64 string is smaller than the - * userdata; it is safe to allocate the same length. - */ - len = strlen(sc->sc_userdata); - if ((userdata = calloc(1, len + 1)) == NULL) - log_warnx("failed to allocate user-data"); - else if ((len = b64_pton(sc->sc_userdata, userdata, len)) < 1) - log_warnx("failed to decode user-data"); - else - (void)agent_userdata(userdata, len); - free(userdata); + if (agent_userdata(sc->sc_userdata, + strlen(sc->sc_userdata)) != 0) + log_warnx("user-data failed"); } log_debug("%s: %s", __func__, "/etc/rc.firsttime"); @@ -608,12 +597,30 @@ agent_userdata(const unsigned char *userdata, size_t len) const char *file; int ret = -1; - /* XXX add support for gzip-encoded user-data */ - if ((shebang = get_line(userdata, len)) == NULL) { - log_warnx("failed to decode shebang from user-data"); + if (len <= 2) { + log_warnx("user-data too short"); goto fail; } + if (userdata[0] == 0x1f && userdata[1] == 0x8b) { + log_warnx("gzip-compressed user-data is not supported"); + goto fail; + } else if (userdata[0] == '#') { + if ((shebang = get_line(userdata, len)) == NULL) { + log_warnx("failed to decode shebang from user-data"); + goto fail; + } + } else if (isprint(userdata[0]) && isprint(userdata[1])) { + /* Decode user-data and call the function again */ + if ((str = calloc(1, len + 1)) == NULL || + (len = b64_pton(userdata, str, len)) < 1 || + agent_userdata(str, len) != 0) { + log_warnx("failed to decode user-data"); + goto fail; + } + goto done; + } + log_debug("%s: user-data: %s", __func__, shebang); if (strlen(shebang) <= 2 || strncmp("#!", shebang, 2) != 0) { @@ -641,6 +648,7 @@ agent_userdata(const unsigned char *userdata, size_t len) fileout(line, "a", "/etc/rc.firsttime") != 0) log_warnx("failed to add user-data script"); + done: ret = 0; fail: free(line); From 8c4f6a384b9d4eea3994a4d66a5c20b4c785895a Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 8 May 2018 12:24:32 +0200 Subject: [PATCH 15/48] /var/run is cleared on boot, store user-data in /etc instead --- agent/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/main.c b/agent/main.c index 40d5d61..b01820b 100644 --- a/agent/main.c +++ b/agent/main.c @@ -635,7 +635,7 @@ agent_userdata(const unsigned char *userdata, size_t len) } /* write user-data script into file */ - file = "/var/run/user-data"; + file = "/etc/rc.user-data"; if (fileout(str, "w", file) != 0) { log_warnx("failed to write user-data"); goto fail; @@ -643,8 +643,8 @@ agent_userdata(const unsigned char *userdata, size_t len) /* and call it from rc.firsttime later on boot */ if (asprintf(&line, - "logger -s -t cloud-agent \"running %s\"\n" - "%s %s\nrm %s\n", file, shebang + 2, file, file) == -1 || + "logger -s -t cloud-agent \"running user-data\"\n" + "%s %s\nrm %s\n", shebang + 2, file, file) == -1 || fileout(line, "a", "/etc/rc.firsttime") != 0) log_warnx("failed to add user-data script"); From bf8bfd607b1f2b14c653b24676c8788a4a40670c Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 15 May 2018 23:11:32 +0200 Subject: [PATCH 16/48] Fix build with LibreSSL 2.8 and constified the ASN1_OBJECTs. --- cms/lib/cms_lib.c | 10 +++++++++- cms/lib/cms_sd.c | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cms/lib/cms_lib.c b/cms/lib/cms_lib.c index 6d27c49..67edf07 100644 --- a/cms/lib/cms_lib.c +++ b/cms/lib/cms_lib.c @@ -353,7 +353,11 @@ void cms_DigestAlgorithm_set(X509_ALGOR *alg, const EVP_MD *md) BIO *cms_DigestAlgorithm_init_bio(X509_ALGOR *digestAlgorithm) { BIO *mdbio = NULL; +#if LIBRESSL_VERSION_NUMBER >= 0x2080000fL + const ASN1_OBJECT *digestoid; +#else ASN1_OBJECT *digestoid; +#endif const EVP_MD *digest; X509_ALGOR_get0(&digestoid, NULL, NULL, digestAlgorithm); digest = EVP_get_digestbyobj(digestoid); @@ -380,7 +384,11 @@ int cms_DigestAlgorithm_find_ctx(EVP_MD_CTX *mctx, BIO *chain, X509_ALGOR *mdalg) { int nid; - ASN1_OBJECT *mdoid; +#if LIBRESSL_VERSION_NUMBER >= 0x2080000fL + const ASN1_OBJECT *mdoid; +#else + const ASN1_OBJECT *mdoid; +#endif X509_ALGOR_get0(&mdoid, NULL, NULL, mdalg); nid = OBJ_obj2nid(mdoid); /* Look for digest type to match signature */ diff --git a/cms/lib/cms_sd.c b/cms/lib/cms_sd.c index 5e7b0a1..f1ff9b0 100644 --- a/cms/lib/cms_sd.c +++ b/cms/lib/cms_sd.c @@ -328,7 +328,11 @@ CMS_SignerInfo *CMS_add1_signer(CMS_ContentInfo *cms, /* See if digest is present in digestAlgorithms */ for (i = 0; i < sk_X509_ALGOR_num(sd->digestAlgorithms); i++) { +#if LIBRESSL_VERSION_NUMBER >= 0x2080000fL + const ASN1_OBJECT *aoid; +#else ASN1_OBJECT *aoid; +#endif alg = sk_X509_ALGOR_value(sd->digestAlgorithms, i); X509_ALGOR_get0(&aoid, NULL, NULL, alg); if (OBJ_obj2nid(aoid) == EVP_MD_type(md)) From daec249cc060062df49d08004b26c0e3d5e858b7 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 15 May 2018 23:16:57 +0200 Subject: [PATCH 17/48] Fix previous --- cms/lib/cms_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/lib/cms_lib.c b/cms/lib/cms_lib.c index 67edf07..0e0d9fc 100644 --- a/cms/lib/cms_lib.c +++ b/cms/lib/cms_lib.c @@ -387,7 +387,7 @@ int cms_DigestAlgorithm_find_ctx(EVP_MD_CTX *mctx, BIO *chain, #if LIBRESSL_VERSION_NUMBER >= 0x2080000fL const ASN1_OBJECT *mdoid; #else - const ASN1_OBJECT *mdoid; + ASN1_OBJECT *mdoid; #endif X509_ALGOR_get0(&mdoid, NULL, NULL, mdalg); nid = OBJ_obj2nid(mdoid); From bc8d60d5f608bf0bbc09dd70e7c1c80808b7319d Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 15 May 2018 23:37:46 +0200 Subject: [PATCH 18/48] Update README.md, remove warning, mention more clouds --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68141d0..fea3ef0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ cloud-agent for OpenBSD ======================= -**This is just experimental. Be warned.** - This is a simple OpenBSD-specific agent that aims to handle provisioning and cloud initialization on public clouds such as Microsoft Azure and Amazon AWS. For OpenBSD on Azure, it is a minimal @@ -31,8 +29,12 @@ Installation is easy, `cloud-agent` detects the cloud type automatically. * On Amazon AWS, create a file `/etc/hostname.xnf0` +* On Exoscale, create a file `/etc/hostname.vio0` + * On OpenBSD VMM (with meta-data), create a file `/etc/hostname.vio0` +* On OpenStack/VMware, create a file `/etc/hostname.vmx0` + * The content of the file is identical for all of them: dhcp From ec87db177d4928fc442aee0063ac72fde772751e Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 16 May 2018 12:28:49 +0200 Subject: [PATCH 19/48] Revert LDADD to -ltls -lssl -lcrypto as just -ltls breaks static linking. --- agent/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/Makefile b/agent/Makefile index 39d7743..2205fc1 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -15,7 +15,7 @@ CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith CFLAGS+= -Wsign-compare -Wcast-qual -LDADD+= -lexpat -ltls +LDADD+= -lexpat -ltls -lssl -lcrypto DPADD+= ${LIBEXPAT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} .include From d9899d488a4bc209f57d296f1045ba00650c926d Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 13 Aug 2018 19:30:41 +0200 Subject: [PATCH 20/48] Add initial support for OpenNebula contextualization. Thanks to datacenterlight.ch by ungleich glarus AG for providing access to their OpenNebula-based cloud. --- README.md | 3 + agent/Makefile | 7 +- agent/azure.c | 27 ++---- agent/cloudinit.c | 2 + agent/main.c | 188 +++++++++++++++++++++++++++++++++++++++- agent/main.h | 36 ++++++++ agent/opennebula.c | 209 +++++++++++++++++++++++++++++++++++++++++++++ agent/openstack.c | 2 + 8 files changed, 449 insertions(+), 25 deletions(-) create mode 100644 agent/opennebula.c diff --git a/README.md b/README.md index fea3ef0..8de4435 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ Installation is easy, `cloud-agent` detects the cloud type automatically. * On OpenStack/VMware, create a file `/etc/hostname.vmx0` +* On OpenNebula, create a file `/etc/hostname.if` + where _if_ is the name of your primary interface. + * The content of the file is identical for all of them: dhcp diff --git a/agent/Makefile b/agent/Makefile index 2205fc1..8d57acb 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -1,5 +1,6 @@ PROG= cloud-agent -SRCS= azure.c cloudinit.c http.c json.c jsmn.c log.c openstack.c main.c xml.c +SRCS= http.c json.c jsmn.c log.c main.c xml.c +SRCS+= azure.c cloudinit.c opennebula.c openstack.c BINDIR= /usr/local/libexec MANDIR= /usr/local/man/man @@ -15,7 +16,7 @@ CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith CFLAGS+= -Wsign-compare -Wcast-qual -LDADD+= -lexpat -ltls -lssl -lcrypto -DPADD+= ${LIBEXPAT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} +LDADD+= -lexpat -ltls -lssl -lcrypto -lutil +DPADD+= ${LIBEXPAT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} .include diff --git a/agent/azure.c b/agent/azure.c index d4a0b4a..40efd03 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -62,13 +62,14 @@ azure(struct system_config *sc) { int ret = -1; + sc->sc_stack = "azure"; + /* Apply defaults */ free(sc->sc_username); if ((sc->sc_username = strdup("azure-user")) == NULL) { log_warnx("failed to set default user"); goto fail; } - sc->sc_cdrom = "/dev/cd0c"; sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; sc->sc_priv = &az_config; @@ -654,36 +655,22 @@ azure_getovfenv(struct system_config *sc) struct xml xml; struct xmlelem *xp, *xe, *xk, *xv; char *sshfp, *sshval, *str; - int mount = 0, ret = -1, fd = -1; + int ret = -1, fd = -1; FILE *fp; - /* Silently try to mount the cdrom */ - fd = disable_output(sc, STDERR_FILENO); - ret = shell("mount", "-r", sc->sc_cdrom, "/mnt", NULL); - enable_output(sc, STDERR_FILENO, fd); - fd = -1; - - if (ret == 0) { - log_debug("%s: mounted %s", __func__, sc->sc_cdrom); - mount = 1; - } - ret = -1; - if (xml_init(&xml) != 0) { log_debug("%s: xml", __func__); goto done; } - /* Fallback to and older ovf-env.xml file */ + /* + * Assume that the cdrom is already mounted. + * Fallback to and older ovf-env.xml file. + */ if (xml_parse(&xml, "/mnt/ovf-env.xml") == -1 && xml_parse(&xml, sc->sc_ovfenv) == -1) goto done; - /* unmount if we mounted the cdrom before */ - if (mount && shell("umount", "/mnt", NULL) == 0) { - log_debug("%s: unmounted %s", __func__, sc->sc_cdrom); - } - if ((xp = xml_findl(&xml.ox_root, "Environment", "wa:ProvisioningSection", "LinuxProvisioningConfigurationSet", NULL)) == NULL) { diff --git a/agent/cloudinit.c b/agent/cloudinit.c index c6bf38c..807d784 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -41,6 +41,7 @@ ec2(struct system_config *sc) return (-1); } + sc->sc_stack = "ec2"; return (cloudinit_fetch(sc)); } @@ -53,6 +54,7 @@ cloudinit(struct system_config *sc) return (-1); } + sc->sc_stack = "cloudinit"; return (cloudinit_fetch(sc)); } diff --git a/agent/main.c b/agent/main.c index b01820b..706d3d8 100644 --- a/agent/main.c +++ b/agent/main.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Reyk Floeter + * Copyright (c) 2017, 2018 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,19 +15,24 @@ */ #include +#include #include #include +#include #include +#include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -40,6 +45,7 @@ __dead void usage(void); static struct system_config *agent_init(const char *, int, int); static int agent_configure(struct system_config *); +static int agent_network(struct system_config *); static void agent_free(struct system_config *); static int agent_pf(struct system_config *, int); static int agent_userdata(const unsigned char *, size_t); @@ -303,14 +309,17 @@ static struct system_config * agent_init(const char *ifname, int dryrun, int timeout) { struct system_config *sc; + int fd, ret; if ((sc = calloc(1, sizeof(*sc))) == NULL) return (NULL); sc->sc_interface = ifname; + sc->sc_cdrom = "/dev/cd0c"; sc->sc_dryrun = dryrun ? 1 : 0; sc->sc_timeout = agent_timeout = timeout < 1 ? -1 : timeout * 1000; TAILQ_INIT(&sc->sc_pubkeys); + TAILQ_INIT(&sc->sc_netaddrs); if ((sc->sc_nullfd = open("/dev/null", O_RDWR)) == -1) { free(sc); @@ -322,6 +331,15 @@ agent_init(const char *ifname, int dryrun, int timeout) return (NULL); } + /* Silently try to mount the cdrom */ + fd = disable_output(sc, STDERR_FILENO); + ret = shell("mount", "-r", sc->sc_cdrom, "/mnt", NULL); + enable_output(sc, STDERR_FILENO, fd); + if (ret == 0) { + log_debug("%s: mounted %s", __func__, sc->sc_cdrom); + sc->sc_mount = 1; + } + if (sc->sc_dryrun) return (sc); @@ -337,6 +355,12 @@ static void agent_free(struct system_config *sc) { struct ssh_pubkey *ssh; + struct net_addr *net; + + /* unmount if we mounted the cdrom before */ + if (sc->sc_mount && shell("umount", "/mnt", NULL) == 0) { + log_debug("%s: unmounted %s", __func__, sc->sc_cdrom); + } free(sc->sc_hostname); free(sc->sc_username); @@ -352,6 +376,12 @@ agent_free(struct system_config *sc) TAILQ_REMOVE(&sc->sc_pubkeys, ssh, ssh_entry); free(ssh); } + + while ((net = TAILQ_FIRST(&sc->sc_netaddrs))) { + TAILQ_REMOVE(&sc->sc_netaddrs, net, net_entry); + free(net->net_value); + free(net); + } } int @@ -405,6 +435,103 @@ agent_setpubkey(struct system_config *sc, const char *sshval, const char *sshfp) return (ret); } +struct net_addr * +agent_getnetaddr(struct system_config *sc, struct net_addr *net) +{ + struct net_addr *na; + + TAILQ_FOREACH(na, &sc->sc_netaddrs, net_entry) { + if (na->net_type != net->net_type) + continue; + if (na->net_ifunit != net->net_ifunit) + continue; + if (net->net_addr.ss_family != AF_UNSPEC) { + if (na->net_addr.ss_family != + net->net_addr.ss_family) + continue; + if (memcmp(&na->net_addr, &net->net_addr, + na->net_addr.ss_len) != 0) + continue; + } + + return (na); + } + + return (NULL); +} + +int +agent_addnetaddr(struct system_config *sc, unsigned int unit, + const char *value, int af, enum net_type type) +{ + const char *errstr; + struct addrinfo hints, *res; + struct net_addr *net, *na; + + if ((net = calloc(1, sizeof(*net))) == NULL) { + log_debug("%s: calloc", __func__); + return (-1); + } + net->net_ifunit = unit; + net->net_type = type; + + switch (type) { + case NET_MAC: + if (ether_aton(value) == NULL) { + log_debug("%s: if%u mac %s", __func__, unit, value); + free(net); + return (-1); + } + break; + case NET_MTU: + case NET_PREFIX: + net->net_num = strtonum(value, 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_debug("%s: if%u %s", __func__, unit, value); + free(net); + return (-1); + } + break; + default: + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(value, "0", &hints, &res) != 0) { + log_debug("%s: invalid address %s", + __func__, value); + free(net); + return (-1); + } + + if (res->ai_addrlen > sizeof(net->net_addr)) { + log_debug("%s: address too long", + __func__); + free(net); + freeaddrinfo(res); + return (-1); + } + memcpy(&net->net_addr, res->ai_addr, res->ai_addrlen); + net->net_addr.ss_len = res->ai_addrlen; + net->net_addr.ss_family = res->ai_family; + } + + /* Address already exists, ignore new entry */ + if ((na = agent_getnetaddr(sc, net)) != NULL) { + free(net); + return (0); + } + + if ((net->net_value = strdup(value)) == NULL) { + free(net); + return (-1); + } + + TAILQ_INSERT_TAIL(&sc->sc_netaddrs, net, net_entry); + + return (0); +} + static int fileout(const char *str, const char *mode, const char *fmt, ...) { @@ -575,6 +702,9 @@ agent_configure(struct system_config *sc) log_warnx("user-data failed"); } + if (agent_network(sc) != 0) + log_warnx("network configuration failed"); + log_debug("%s: %s", __func__, "/etc/rc.firsttime"); if (fileout("logger -s -t cloud-agent <sc_network) + return (0); + + if (strlcpy(ift, sc->sc_interface, sizeof(ift)) >= sizeof(ift)) + return (-1); + ift[strcspn(ift, "0123456789")] = '\0'; + + TAILQ_FOREACH(net, &sc->sc_netaddrs, net_entry) { + snprintf(ifname, sizeof(ifname), "%s%u", ift, net->net_ifunit); + switch (net->net_type) { + case NET_IP: + family = net->net_addr.ss_family == AF_INET ? + "inet" : "inet6"; + /* XXX prefix */ + + /* hostname.if startup configuration */ + snprintf(line, sizeof(line), "%s alias %s", + family, net->net_value); + snprintf(path, sizeof(path), + "/etc/hostname.%s", ifname); + fileout(line, "a", path); + + /* runtime configuration */ + (void)shell("ifconfig", ifname, family, + "alias", net->net_value, NULL); + break; + case NET_GATEWAY: + fileout(net->net_value, "a", "/etc/mygate"); + break; + case NET_DNS: + snprintf(line, sizeof(line), "nameserver %s", + net->net_value); + fileout(line, "a", "/etc/resolv.conf"); + break; + default: + break; + } + } + + return (0); +} + void agent_unconfigure(void) { @@ -924,13 +1103,18 @@ main(int argc, char *const *argv) * XXX Detect cloud with help from hostctl and sysctl * XXX in addition to the interface name. */ - if (strcmp("hvn0", sc->sc_interface) == 0) + if (opennebula(sc) == 0) + ret = 0; + else if (strcmp("hvn0", sc->sc_interface) == 0) ret = azure(sc); else if (strcmp("xnf0", sc->sc_interface) == 0) ret = ec2(sc); else ret = openstack(sc); + if (sc->sc_stack != NULL) + log_debug("%s: %s", __func__, sc->sc_stack); + if (sc->sc_dryrun) { agent_free(sc); return (0); diff --git a/agent/main.h b/agent/main.h index fb2873a..7fdaf30 100644 --- a/agent/main.h +++ b/agent/main.h @@ -43,7 +43,31 @@ struct ssh_pubkey { }; TAILQ_HEAD(ssh_pubkeys, ssh_pubkey); +enum net_type { + NET_IP, + NET_MASK, + NET_PREFIX, + NET_MAC, + NET_MTU, + NET_GATEWAY, + NET_DNS, + NET_MAX +}; + +struct net_addr { + enum net_type net_type; + unsigned int net_ifunit; + char *net_value; + struct sockaddr_storage net_addr; + unsigned int net_num; + + TAILQ_ENTRY(net_addr) net_entry; +}; +TAILQ_HEAD(net_addrs, net_addr); + struct system_config { + const char *sc_stack; + char *sc_hostname; char *sc_username; char *sc_password; @@ -56,10 +80,15 @@ struct system_config { const char *sc_ovfenv; const char *sc_interface; const char *sc_cdrom; + int sc_mount; struct source sc_addr; struct ssh_pubkeys sc_pubkeys; + int sc_network; + struct net_addrs sc_netaddrs; + unsigned int sc_netmtu; + int sc_nullfd; int sc_dryrun; void *sc_priv; @@ -92,6 +121,9 @@ int azure(struct system_config *); int ec2(struct system_config *); int cloudinit(struct system_config *); +/* opennebula.c */ +int opennebula(struct system_config *); + /* openstack.c */ int openstack(struct system_config *); @@ -105,6 +137,10 @@ char *get_line(const unsigned char *, size_t); char *get_word(const unsigned char *, size_t); int agent_addpubkey(struct system_config *, const char *, const char *); int agent_setpubkey(struct system_config *, const char *, const char *); +struct net_addr * + agent_getnetaddr(struct system_config *, struct net_addr *); +int agent_addnetaddr(struct system_config *, unsigned int, + const char *, int, enum net_type); char *metadata(struct system_config *, const char *, enum strtype); char *metadata_file(struct system_config *, const char *, enum strtype); int connect_wait(int, const struct sockaddr *, socklen_t); diff --git a/agent/opennebula.c b/agent/opennebula.c new file mode 100644 index 0000000..1372537 --- /dev/null +++ b/agent/opennebula.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2018 Reyk Floeter + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" + +int +opennebula(struct system_config *sc) +{ + FILE *fp; + const char *delim = "\\\\\0", *errstr; + char *line = NULL, *k, *v, *p, q; + char *hname = NULL; + size_t len, lineno = 0, i; + int ret = -1; + unsigned int unit; + + /* Return silently without error */ + if ((fp = fopen("/mnt/context.sh", "r")) == NULL) + goto done; + + sc->sc_stack = "opennebula"; + + while ((line = fparseln(fp, &len, &lineno, + delim, FPARSELN_UNESCALL)) != NULL) { + /* key */ + k = line; + + /* a context always starts with this header */ + if (lineno == 1) { + ret = strcmp(line, + "# Context variables generated by OpenNebula"); + if (ret != 0) { + log_debug("%s: unsupported context", __func__); + goto done; + } + free(line); + continue; + } + line[strcspn(line, "#")] = '\0'; + + /* value */ + if ((v = strchr(line, '=')) == NULL || *(v + 1) == '\0') { + free(line); + continue; + } + *v++ = '\0'; + + /* value is quoted */ + q = *v; + if (strspn(v, "\"'") == 0 || (p = strrchr(v, q)) == v) { + free(line); + continue; + } + *v++ = '\0'; + *p = '\0'; + + /* continue if value is empty */ + if (*v == '\0') { + free(line); + continue; + } + + log_debug("%s: %s = %s", __func__, k, v); + + if (strcasecmp("NETWORK", k) == 0) { + if (strcasecmp("YES", v) == 0) + sc->sc_network = 1; + else if (strcasecmp("YES", v) == 0) + sc->sc_network = 0; + } else if (fnmatch("ETH*_*", k, 0) != FNM_NOMATCH) { + /* Extract interface unit */ + if ((p = strdup(k + 3)) == NULL) { + log_debug("%s: %s", __func__, k); + goto done; + } + p[strcspn(p, "_")] = '\0'; + unit = strtonum(p, 0, UINT32_MAX, &errstr); + free(p); + if (errstr != NULL) { + log_debug("%s: %s", __func__, k); + goto done; + } + + /* Get subkey */ + k += strcspn(k, "_") + 1; + + if (strcasecmp("DNS", k) == 0) { + /* We don't support per-interface DNS */ + for (p = v; *p != '\0'; v = p) { + p = v + strcspn(v, " \t"); + *p++ = '\0'; + if ((ret = agent_addnetaddr(sc, 0, + v, AF_UNSPEC, NET_DNS)) != 0) + break; + } + } else if (strcasecmp("IP", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_INET, NET_IP); + } else if (strcasecmp("MASK", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_INET, NET_MASK); + } else if (strcasecmp("GATEWAY", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_INET, NET_GATEWAY); + } else if (strcasecmp("IP6", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_INET6, NET_IP); + } else if (strcasecmp("GATEWAY6", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_INET6, NET_GATEWAY); + } else if (strcasecmp("PREFIX_LENGTH", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_INET6, NET_PREFIX); + } else if (strcasecmp("MAC", k) == 0) { + if (unit == 0 && hname == NULL) { + /* Fake a hostname using the mac */ + if ((hname = p = calloc(1, + strlen(v) + 3)) == NULL) { + log_debug("%s: calloc", + __func__); + goto done; + } + *p++ = 'v'; + *p++ = 'm'; + for (i = 0; i < strlen(v); i++) { + if (!isalnum(v[i])) + continue; + *p++ = v[i]; + } + } + + ret = agent_addnetaddr(sc, unit, + v, AF_UNSPEC, NET_MAC); + } else if (strcasecmp("MTU", k) == 0) { + ret = agent_addnetaddr(sc, unit, + v, AF_UNSPEC, NET_MTU); + } else + ret = 0; + if (ret != 0) { + log_debug("%s: failed to parse %s", + __func__, k); + goto done; + } + } else if (strcasecmp("SSH_PUBLIC_KEY", k) == 0) { + if (agent_addpubkey(sc, v, NULL) != 0) + log_warnx("failed to ssh pubkey"); + } + + free(line); + } + + fclose(fp); + fp = NULL; + + /* + * OpenNebula doesn't provide an instance id so we + * calculate one using the hash of the context file. + * This might break if the context is not consistent. + */ + if ((sc->sc_instance = + calloc(1, SHA256_DIGEST_STRING_LENGTH)) == NULL || + SHA256File("/mnt/context.sh", sc->sc_instance) == NULL) { + log_debug("%s: failed to calculate instance hash", + __func__); + goto done; + } + log_debug("%s: context instance %s", __func__, sc->sc_instance); + + /* Even the hostname is optional */ + if (hname != NULL) { + free(sc->sc_hostname); + sc->sc_hostname = hname; + log_debug("%s: hostname %s", __func__, hname); + } + + line = NULL; + ret = 0; + + done: + if (fp != NULL) + fclose(fp); + free(line); + return (ret); +} diff --git a/agent/openstack.c b/agent/openstack.c index a060c3d..9c84d34 100644 --- a/agent/openstack.c +++ b/agent/openstack.c @@ -44,6 +44,7 @@ openstack(struct system_config *sc) free(sc->sc_endpoint); return (cloudinit(sc)); } + return (0); } @@ -62,6 +63,7 @@ openstack_fetch(struct system_config *sc) if ((json = metadata(sc, "/openstack/latest/meta_data.json", TEXT)) == NULL) goto fail; + sc->sc_stack = "openstack"; if ((j = json_parse(json, strlen(json))) == NULL) goto fail; From 333f7ac6d7f42a949df6d8abb0cace3682d11486 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 14 Aug 2018 09:55:17 +0200 Subject: [PATCH 21/48] Write network configuration files --- agent/main.c | 97 +++++++++++++++++++++++++++++++++++++++++----- agent/main.h | 4 +- agent/opennebula.c | 17 ++++++-- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/agent/main.c b/agent/main.c index 706d3d8..7cf319a 100644 --- a/agent/main.c +++ b/agent/main.c @@ -362,6 +362,7 @@ agent_free(struct system_config *sc) log_debug("%s: unmounted %s", __func__, sc->sc_cdrom); } + free(sc->sc_args); free(sc->sc_hostname); free(sc->sc_username); free(sc->sc_password); @@ -445,6 +446,9 @@ agent_getnetaddr(struct system_config *sc, struct net_addr *net) continue; if (na->net_ifunit != net->net_ifunit) continue; + if (na->net_type == NET_DNS_DOMAIN && + strcasecmp(na->net_value, net->net_value) != 0) + continue; if (net->net_addr.ss_family != AF_UNSPEC) { if (na->net_addr.ss_family != net->net_addr.ss_family) @@ -476,6 +480,13 @@ agent_addnetaddr(struct system_config *sc, unsigned int unit, net->net_type = type; switch (type) { + case NET_DNS_DOMAIN: + if (strlen(value) >= NI_MAXHOST) { + log_debug("%s: if%u domain %s", __func__, unit, value); + free(net); + return (-1); + } + break; case NET_MAC: if (ether_aton(value) == NULL) { log_debug("%s: if%u mac %s", __func__, unit, value); @@ -516,17 +527,18 @@ agent_addnetaddr(struct system_config *sc, unsigned int unit, net->net_addr.ss_family = res->ai_family; } - /* Address already exists, ignore new entry */ - if ((na = agent_getnetaddr(sc, net)) != NULL) { - free(net); - return (0); - } - if ((net->net_value = strdup(value)) == NULL) { free(net); return (-1); } + /* Address already exists, ignore new entry */ + if ((na = agent_getnetaddr(sc, net)) != NULL) { + free(net->net_value); + free(net); + return (0); + } + TAILQ_INSERT_TAIL(&sc->sc_netaddrs, net, net_entry); return (0); @@ -794,6 +806,10 @@ agent_network(struct system_config *sc) struct net_addr *net; char ift[16], ifname[16], line[1024], path[PATH_MAX]; const char *family; + char domain[(NI_MAXHOST + 1) * 6 + 8]; /* up to 6 domains */ + int has_domain = 0; + char ifidx[UINT16_MAX]; + const char *comment = "# Generated by cloud-agent"; if (!sc->sc_network) return (0); @@ -802,21 +818,36 @@ agent_network(struct system_config *sc) return (-1); ift[strcspn(ift, "0123456789")] = '\0'; + memset(ifidx, 0, sizeof(ifidx)); + snprintf(domain, sizeof(domain), "search "); + fileout(comment, "w", "/etc/mygate"); + fileout(comment, "w", "/etc/resolv.conf"); + TAILQ_FOREACH(net, &sc->sc_netaddrs, net_entry) { snprintf(ifname, sizeof(ifname), "%s%u", ift, net->net_ifunit); switch (net->net_type) { case NET_IP: family = net->net_addr.ss_family == AF_INET ? "inet" : "inet6"; - /* XXX prefix */ + /* XXX prefix or mask */ /* hostname.if startup configuration */ - snprintf(line, sizeof(line), "%s alias %s", - family, net->net_value); snprintf(path, sizeof(path), "/etc/hostname.%s", ifname); + if (!ifidx[net->net_ifunit]) + fileout(comment, "w", path); + + snprintf(line, sizeof(line), "%s alias %s", + family, net->net_value); fileout(line, "a", path); + if (!ifidx[net->net_ifunit]++ && + net->net_ifunit == 0) { + snprintf(line, sizeof(line), + "!%s", sc->sc_args); + fileout(line, "a", path); + } + /* runtime configuration */ (void)shell("ifconfig", ifname, family, "alias", net->net_value, NULL); @@ -829,11 +860,24 @@ agent_network(struct system_config *sc) net->net_value); fileout(line, "a", "/etc/resolv.conf"); break; + case NET_DNS_DOMAIN: + if (!has_domain++) { + /* use the first search domain as our own */ + snprintf(line, sizeof(line), "domain %s", + net->net_value); + fileout(line, "a", "/etc/resolv.conf"); + } else + (void)strlcat(domain, " ", sizeof(domain)); + (void)strlcat(domain, net->net_value, sizeof(domain)); + break; default: break; } } + if (has_domain) + fileout(domain, "a", "/etc/resolv.conf"); + return (0); } @@ -1049,6 +1093,36 @@ usage(void) exit(1); } +static char * +get_args(int argc, char *const *argv) +{ + char *args, path[PATH_MAX]; + size_t argslen = 0; + int i; + + /* Store args in a string */ + for (i = 0; i < argc; i++) { + if (i == 0) { + realpath(argv[0], path); + argslen += strlen(path) + 1; + } else { + argslen += strlen(argv[i]) + 1; + } + } + if ((args = calloc(1, argslen + 1)) == NULL) + return (NULL); + for (i = 0; i < argc; i++) { + if (i == 0) + strlcat(args, path, argslen); + else { + strlcat(args, " ", argslen); + strlcat(args, argv[i], argslen); + } + } + + return (args); +} + int main(int argc, char *const *argv) { @@ -1056,6 +1130,10 @@ main(int argc, char *const *argv) int verbose = 0, dryrun = 0, unconfigure = 0; int ch, ret, timeout = CONNECT_TIMEOUT; const char *error = NULL; + char *args; + + if ((args = get_args(argc, argv)) == NULL) + fatalx("failed to save args"); while ((ch = getopt(argc, argv, "nvt:u")) != -1) { switch (ch) { @@ -1098,6 +1176,7 @@ main(int argc, char *const *argv) if ((sc = agent_init(argv[0], dryrun, timeout)) == NULL) fatalx("agent"); + sc->sc_args = args; /* * XXX Detect cloud with help from hostctl and sysctl diff --git a/agent/main.h b/agent/main.h index 7fdaf30..17fd724 100644 --- a/agent/main.h +++ b/agent/main.h @@ -51,12 +51,13 @@ enum net_type { NET_MTU, NET_GATEWAY, NET_DNS, + NET_DNS_DOMAIN, NET_MAX }; struct net_addr { enum net_type net_type; - unsigned int net_ifunit; + unsigned short net_ifunit; char *net_value; struct sockaddr_storage net_addr; unsigned int net_num; @@ -67,6 +68,7 @@ TAILQ_HEAD(net_addrs, net_addr); struct system_config { const char *sc_stack; + char *sc_args; char *sc_hostname; char *sc_username; diff --git a/agent/opennebula.c b/agent/opennebula.c index 1372537..c17c16c 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -37,7 +37,7 @@ opennebula(struct system_config *sc) char *hname = NULL; size_t len, lineno = 0, i; int ret = -1; - unsigned int unit; + unsigned short unit; /* Return silently without error */ if ((fp = fopen("/mnt/context.sh", "r")) == NULL) @@ -99,7 +99,7 @@ opennebula(struct system_config *sc) goto done; } p[strcspn(p, "_")] = '\0'; - unit = strtonum(p, 0, UINT32_MAX, &errstr); + unit = strtonum(p, 0, UINT16_MAX, &errstr); free(p); if (errstr != NULL) { log_debug("%s: %s", __func__, k); @@ -118,6 +118,14 @@ opennebula(struct system_config *sc) v, AF_UNSPEC, NET_DNS)) != 0) break; } + } else if (strcasecmp("SEARCH_DOMAIN", k) == 0) { + for (p = v; *p != '\0'; v = p) { + p = v + strcspn(v, " \t"); + *p++ = '\0'; + if ((ret = agent_addnetaddr(sc, 0, + v, AF_UNSPEC, NET_DNS_DOMAIN)) != 0) + break; + } } else if (strcasecmp("IP", k) == 0) { ret = agent_addnetaddr(sc, unit, v, AF_INET, NET_IP); @@ -166,9 +174,12 @@ opennebula(struct system_config *sc) __func__, k); goto done; } + } else if (strcasecmp("HOSTNAME", k) == 0) { + if ((hname = strdup(v)) == NULL) + log_warnx("failed to set hostname"); } else if (strcasecmp("SSH_PUBLIC_KEY", k) == 0) { if (agent_addpubkey(sc, v, NULL) != 0) - log_warnx("failed to ssh pubkey"); + log_warnx("failed to set ssh pubkey"); } free(line); From 91eb82f9028050ec4f182ea05f667646612cf004 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 15 Aug 2018 11:46:21 +0200 Subject: [PATCH 22/48] Make the group egress optional (dynamic) in the initial pf rule --- agent/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/main.c b/agent/main.c index 7cf319a..003e896 100644 --- a/agent/main.c +++ b/agent/main.c @@ -619,7 +619,7 @@ agent_pf(struct system_config *sc, int open) return (0); if (open) - ret = shellout("pass out proto tcp from egress to port www\n", + ret = shellout("pass out proto tcp from (egress) to port www\n", NULL, "pfctl", "-f", "-", NULL); else ret = shellout("\n", NULL, "pfctl", "-f", "-", NULL); From 20e2f78f8379b915e6cfae15c662662d5fef8310 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 15 Aug 2018 12:27:19 +0200 Subject: [PATCH 23/48] Add -U option to overwrite the user --- agent/cloud-agent.8 | 19 +++++++++++++++++ agent/main.c | 52 ++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index 5b88044..c708508 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -23,6 +23,8 @@ .Sh SYNOPSIS .Nm cloud-agent .Op Fl nuv +.Op Fl t Ar timeout +.Op Fl U Ar username .Ar interface .Sh DESCRIPTION The @@ -34,6 +36,23 @@ The options are as follows: .Bl -tag -width Ds .It Fl n Do not configure the system and skip the provisioning step. +.It Fl t Ar timeout +Change the HTTP timeout. +The default is 3 seconds. +.It Fl U Ar username +Change the default user. +The default is +.Dq ec2-user +on AWS, +.Dq azure-user +on Azure, and +.Dq puffy +everywhere else. +The default user is used when it is not obtained from the cloud +configuration. +Using +.Dq root +is supported, but not recommended. .It Fl u Deprovision and unconfigure the system. This deletes keys, passwords, and logs files without asking for permission. diff --git a/agent/main.c b/agent/main.c index 003e896..51b2736 100644 --- a/agent/main.c +++ b/agent/main.c @@ -325,11 +325,6 @@ agent_init(const char *ifname, int dryrun, int timeout) free(sc); return (NULL); } - if ((sc->sc_username = strdup("puffy")) == NULL) { - free(sc); - close(sc->sc_nullfd); - return (NULL); - } /* Silently try to mount the cdrom */ fd = disable_output(sc, STDERR_FILENO); @@ -646,6 +641,13 @@ agent_configure(struct system_config *sc) if (fileout(sc->sc_instance, "w", "/var/db/cloud-instance") != 0) log_warnx("instance failed"); + /* Set default username if not set */ + if ((sc->sc_username == NULL) && + (sc->sc_username = strdup("puffy")) == NULL) { + log_warn("default username"); + return (-1); + } + /* hostname */ log_debug("%s: hostname %s", __func__, sc->sc_hostname); if (fileout(sc->sc_hostname, "w", "/etc/myname") != 0) @@ -655,11 +657,13 @@ agent_configure(struct system_config *sc) /* username */ log_debug("%s: username %s", __func__, sc->sc_username); - if (shell("useradd", "-L", "staff", "-G", "wheel", - "-m", sc->sc_username, NULL) != 0) - log_warnx("username failed"); - if (fileout(sc->sc_username, "w", "/root/.forward") != 0) - log_warnx(".forward failed"); + if (strcmp("root", sc->sc_username) != 0) { + if (shell("useradd", "-L", "staff", "-G", "wheel", + "-m", sc->sc_username, NULL) != 0) + log_warnx("username failed"); + if (fileout(sc->sc_username, "w", "/root/.forward") != 0) + log_warnx(".forward failed"); + } /* password */ if (sc->sc_password == NULL) { @@ -703,7 +707,8 @@ agent_configure(struct system_config *sc) continue; log_debug("%s: key %s", __func__, ssh->ssh_keyval); if (fileout(ssh->ssh_keyval, "a", - "/home/%s/.ssh/authorized_keys", + "%s/%s/.ssh/authorized_keys", + strcmp("root", sc->sc_username) == 0 ? "" : "/home", sc->sc_username) != 0) log_warnx("public key failed"); } @@ -804,7 +809,7 @@ static int agent_network(struct system_config *sc) { struct net_addr *net; - char ift[16], ifname[16], line[1024], path[PATH_MAX]; + char ift[16], ifname[16], line[1024]; const char *family; char domain[(NI_MAXHOST + 1) * 6 + 8]; /* up to 6 domains */ int has_domain = 0; @@ -832,20 +837,19 @@ agent_network(struct system_config *sc) /* XXX prefix or mask */ /* hostname.if startup configuration */ - snprintf(path, sizeof(path), - "/etc/hostname.%s", ifname); if (!ifidx[net->net_ifunit]) - fileout(comment, "w", path); + fileout(comment, "w", + "/etc/hostname.%s", ifname); snprintf(line, sizeof(line), "%s alias %s", family, net->net_value); - fileout(line, "a", path); + fileout(line, "a", "/etc/hostname.%s", ifname); if (!ifidx[net->net_ifunit]++ && net->net_ifunit == 0) { snprintf(line, sizeof(line), "!%s", sc->sc_args); - fileout(line, "a", path); + fileout(line, "a", "/etc/hostname.%s", ifname); } /* runtime configuration */ @@ -1088,7 +1092,7 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-nuv] [-t 3] interface\n", + fprintf(stderr, "usage: %s [-nuv] [-t 3] [-U puffy] interface\n", __progname); exit(1); } @@ -1130,12 +1134,12 @@ main(int argc, char *const *argv) int verbose = 0, dryrun = 0, unconfigure = 0; int ch, ret, timeout = CONNECT_TIMEOUT; const char *error = NULL; - char *args; + char *args, *username = NULL; if ((args = get_args(argc, argv)) == NULL) fatalx("failed to save args"); - while ((ch = getopt(argc, argv, "nvt:u")) != -1) { + while ((ch = getopt(argc, argv, "nvt:U:u")) != -1) { switch (ch) { case 'n': dryrun = 1; @@ -1148,6 +1152,10 @@ main(int argc, char *const *argv) if (error != NULL) fatalx("invalid timeout: %s", error); break; + case 'U': + if ((username = strdup(optarg)) == NULL) + fatal("username"); + break; case 'u': unconfigure = 1; break; @@ -1177,6 +1185,10 @@ main(int argc, char *const *argv) if ((sc = agent_init(argv[0], dryrun, timeout)) == NULL) fatalx("agent"); sc->sc_args = args; + if (username != NULL) { + free(sc->sc_username); + sc->sc_username = username; + } /* * XXX Detect cloud with help from hostctl and sysctl From 3290c272108a4115a411afeed91fa77cbd77141a Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 15 Aug 2018 13:48:51 +0200 Subject: [PATCH 24/48] If root, don't overwrite doas.conf --- agent/main.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/agent/main.c b/agent/main.c index 51b2736..4ec29df 100644 --- a/agent/main.c +++ b/agent/main.c @@ -667,8 +667,6 @@ agent_configure(struct system_config *sc) /* password */ if (sc->sc_password == NULL) { - str1 = "/PasswordAuthentication/" - "s/.*/PasswordAuthentication no/"; if (asprintf(&str2, "permit keepenv nopass %s as root\n" "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; @@ -677,15 +675,14 @@ agent_configure(struct system_config *sc) sc->sc_username, NULL) != 0) log_warnx("password failed"); - str1 = "/PasswordAuthentication/" - "s/.*/PasswordAuthentication yes/"; if (asprintf(&str2, "permit keepenv persist %s as root\n" "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; } /* doas */ - if (str2 == NULL || fileout(str2, "w", "/etc/doas.conf") != 0) + if ((strcmp("root", sc->sc_username) != 0) && + (str2 == NULL || fileout(str2, "w", "/etc/doas.conf")) != 0) log_warnx("doas failed"); free(str2); From 3e3c5d914ee86d1a9255ed77422b7aaea3f68402 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 10 May 2019 11:33:15 +0200 Subject: [PATCH 25/48] Unbreak cloud-agent's cms build on newer LibreSSL (OpenBSD 6.5) Fix from github.com/xenotrope --- cms/lib/cms.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cms/lib/cms.h b/cms/lib/cms.h index 8fbb726..f3c6815 100644 --- a/cms/lib/cms.h +++ b/cms/lib/cms.h @@ -80,6 +80,13 @@ DECLARE_ASN1_FUNCTIONS(CMS_ContentInfo) DECLARE_ASN1_FUNCTIONS(CMS_ReceiptRequest) DECLARE_ASN1_PRINT_FUNCTION(CMS_ContentInfo) +#if LIBRESSL_VERSION_NUMBER >= 0x20900000L +#define M_ASN1_new_of(type) \ + (type *)ASN1_item_new(ASN1_ITEM_rptr(type)) +#define M_ASN1_free_of(x, type) \ + ASN1_item_free(CHECKED_PTR_OF(type, x), ASN1_ITEM_rptr(type)) +#endif + # define CMS_SIGNERINFO_ISSUER_SERIAL 0 # define CMS_SIGNERINFO_KEYIDENTIFIER 1 From ffe93c5b4f77c528fff5c59feb31803442ae6efc Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 10 May 2019 12:09:59 +0200 Subject: [PATCH 26/48] Add #if defined(LIBRESSL_VERSION_NUMBER) --- cms/lib/cms.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/lib/cms.h b/cms/lib/cms.h index f3c6815..c761e82 100644 --- a/cms/lib/cms.h +++ b/cms/lib/cms.h @@ -80,7 +80,7 @@ DECLARE_ASN1_FUNCTIONS(CMS_ContentInfo) DECLARE_ASN1_FUNCTIONS(CMS_ReceiptRequest) DECLARE_ASN1_PRINT_FUNCTION(CMS_ContentInfo) -#if LIBRESSL_VERSION_NUMBER >= 0x20900000L +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x20900000L #define M_ASN1_new_of(type) \ (type *)ASN1_item_new(ASN1_ITEM_rptr(type)) #define M_ASN1_free_of(x, type) \ From a8490a757f83bbeac9f8e70800e685c33c79e155 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sat, 1 Jun 2019 17:01:29 +0200 Subject: [PATCH 27/48] Escape backslash in mdoc --- agent/cloud-agent.8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index c708508..2c6e2c4 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -68,7 +68,7 @@ of the VM's primary networking interface: .Bd -literal -offset indent # cat /etc/hostname.hvn0 dhcp -!/usr/local/libexec/cloud-agent "\$if" +!/usr/local/libexec/cloud-agent "\e$if" .Ed .Sh FILES .Bl -tag -width "/usr/local/libexec/cloud-agentX" -compact From 8dfa3c843a3706474dcd41c217dfe5cb51103bb0 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sat, 1 Jun 2019 23:41:32 +0200 Subject: [PATCH 28/48] Add "-r rootdisk" growdisk support --- agent/Makefile | 1 + agent/cloud-agent.8 | 10 ++- agent/growdisk.c | 149 ++++++++++++++++++++++++++++++++++++++++++++ agent/main.c | 50 +++++++++------ agent/main.h | 4 ++ 5 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 agent/growdisk.c diff --git a/agent/Makefile b/agent/Makefile index 8d57acb..0e2925c 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -1,6 +1,7 @@ PROG= cloud-agent SRCS= http.c json.c jsmn.c log.c main.c xml.c SRCS+= azure.c cloudinit.c opennebula.c openstack.c +SRCS+= growdisk.c BINDIR= /usr/local/libexec MANDIR= /usr/local/man/man diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index 2c6e2c4..0cec012 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -23,6 +23,7 @@ .Sh SYNOPSIS .Nm cloud-agent .Op Fl nuv +.Op Fl r Ar rootdisk .Op Fl t Ar timeout .Op Fl U Ar username .Ar interface @@ -53,6 +54,10 @@ configuration. Using .Dq root is supported, but not recommended. +.It Fl r Ar rootdisk +Automatically grow the last +.Ox +FFS partition of the root disk to use all the available space. .It Fl u Deprovision and unconfigure the system. This deletes keys, passwords, and logs files without asking for permission. @@ -64,11 +69,12 @@ Enable .Nm in the .Xr hostname.if 5 -of the VM's primary networking interface: +of the VM's primary networking interface and automatically the last +partition of the root disk: .Bd -literal -offset indent # cat /etc/hostname.hvn0 dhcp -!/usr/local/libexec/cloud-agent "\e$if" +!/usr/local/libexec/cloud-agent -r sd0 "\e$if" .Ed .Sh FILES .Bl -tag -width "/usr/local/libexec/cloud-agentX" -compact diff --git a/agent/growdisk.c b/agent/growdisk.c new file mode 100644 index 0000000..dd920bb --- /dev/null +++ b/agent/growdisk.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019 Reyk Floeter + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include /* DEV_BSIZE */ +#include +#include +#include +#define DKTYPENAMES +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" + +static uint16_t dkcksum(struct disklabel *); + +static uint16_t +dkcksum(struct disklabel *lp) +{ + uint16_t *start, *end, sum; + start = (uint16_t *)lp; + end = (uint16_t *)&lp->d_partitions[lp->d_npartitions]; + for (sum = 0; start < end;) + sum ^= *start++; + return (sum); +} + +int +growdisk(struct system_config *sc) +{ + char c, last_part = 0, *out, *path = NULL; + int ret = -1, i, errfd, outfd; + struct partition *pp, *p = NULL; + struct disklabel lp; + int fd; + uint16_t cksum; + + /* + * Grow the OpenBSD MBR partition + */ + + /* XXX this is a bit ugly but easier to do */ + if (shellout("e 3\n\n\n\n*\nw\nq\n", &out, + "fdisk", "-e", sc->sc_rootdisk, NULL) != 0) { + log_warnx("failed to grow OpenBSD partition"); + return (-1); + } + free(out); + + /* + * Grow the last partition in the disklabel + */ + + if ((fd = opendev(sc->sc_rootdisk, + O_RDWR, OPENDEV_PART, NULL)) == -1) { + log_warn("failed to open %s", sc->sc_rootdisk); + return (-1); + } + + if (ioctl(fd, DIOCGDINFO, &lp) == -1) { + log_warn("failed to get disklabel"); + goto done; + } + + if (lp.d_magic != DISKMAGIC || lp.d_magic2 != DISKMAGIC) { + log_warnx("invalid disklabel magic bytes"); + goto done; + } + cksum = lp.d_checksum; + lp.d_checksum = 0; + + if (dkcksum(&lp) != cksum) { + log_warnx("invalid disklabel checksum"); + goto done; + } + + pp = lp.d_partitions; + for (i = 0, pp = lp.d_partitions; i < lp.d_npartitions; i++, pp++) { + if (!DL_GETPSIZE(pp)) + continue; + c = 'a' + i; + if (pp->p_fstype == FS_BSDFFS) { + last_part = c; + p = pp; + } + } + + if (last_part == 0) { + log_warnx("last BSD partition not found"); + goto done; + } + + /* Update OpenBSD boundaries */ + DL_SETBEND(&lp, DL_GETDSIZE(&lp) - DL_GETBSTART(&lp)); + + /* Update the size of the last partition */ + DL_SETPSIZE(p, DL_GETBEND(&lp) - DL_GETPOFFSET(p)); + + lp.d_checksum = dkcksum(&lp); + + if (ioctl(fd, DIOCWDINFO, &lp) == -1) { + log_warn("failed to write disklabel"); + goto done; + } + + /* + * Grow the filesystem + */ + + if (asprintf(&path, "/dev/%s%c", sc->sc_rootdisk, last_part) == -1) + goto done; + + errfd = disable_output(sc, STDERR_FILENO); + outfd = disable_output(sc, STDOUT_FILENO); + + (void)shell("umount", "-f", path, NULL); + (void)shell("growfs", "-yq", path, NULL); + if ((ret = shell("fsck", "-y", path, NULL)) != 0) + ret = -1; + + enable_output(sc, STDERR_FILENO, errfd); + enable_output(sc, STDOUT_FILENO, outfd); + + ret = 0; + done: + free(path); + close(fd); + return (ret); +} diff --git a/agent/main.c b/agent/main.c index 4ec29df..4632c97 100644 --- a/agent/main.c +++ b/agent/main.c @@ -42,17 +42,18 @@ #include "main.h" #include "xml.h" -__dead void usage(void); -static struct system_config *agent_init(const char *, int, int); -static int agent_configure(struct system_config *); -static int agent_network(struct system_config *); -static void agent_free(struct system_config *); -static int agent_pf(struct system_config *, int); -static int agent_userdata(const unsigned char *, size_t); -static void agent_unconfigure(void); -static char *metadata_parse(char *, size_t, enum strtype); +__dead void usage(void); +static struct system_config + *agent_init(const char *, const char *, int, int); +static int agent_configure(struct system_config *); +static int agent_network(struct system_config *); +static void agent_free(struct system_config *); +static int agent_pf(struct system_config *, int); +static int agent_userdata(const unsigned char *, size_t); +static void agent_unconfigure(void); +static char *metadata_parse(char *, size_t, enum strtype); -static int agent_timeout; +static int agent_timeout; int shell(const char *arg, ...) @@ -306,7 +307,7 @@ get_word(const unsigned char *ptr, size_t len) } static struct system_config * -agent_init(const char *ifname, int dryrun, int timeout) +agent_init(const char *ifname, const char *rootdisk, int dryrun, int timeout) { struct system_config *sc; int fd, ret; @@ -318,6 +319,7 @@ agent_init(const char *ifname, int dryrun, int timeout) sc->sc_cdrom = "/dev/cd0c"; sc->sc_dryrun = dryrun ? 1 : 0; sc->sc_timeout = agent_timeout = timeout < 1 ? -1 : timeout * 1000; + sc->sc_rootdisk = rootdisk; TAILQ_INIT(&sc->sc_pubkeys); TAILQ_INIT(&sc->sc_netaddrs); @@ -1089,8 +1091,8 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-nuv] [-t 3] [-U puffy] interface\n", - __progname); + fprintf(stderr, "usage: %s [-nuv] [-r rootdisk] [-t 3] [-U puffy] " + "interface\n", __progname); exit(1); } @@ -1130,19 +1132,19 @@ main(int argc, char *const *argv) struct system_config *sc; int verbose = 0, dryrun = 0, unconfigure = 0; int ch, ret, timeout = CONNECT_TIMEOUT; - const char *error = NULL; + const char *error = NULL, *rootdisk = NULL; char *args, *username = NULL; if ((args = get_args(argc, argv)) == NULL) fatalx("failed to save args"); - while ((ch = getopt(argc, argv, "nvt:U:u")) != -1) { + while ((ch = getopt(argc, argv, "nr:t:U:uv")) != -1) { switch (ch) { case 'n': dryrun = 1; break; - case 'v': - verbose += 2; + case 'r': + rootdisk = optarg; break; case 't': timeout = strtonum(optarg, -1, 86400, &error); @@ -1156,6 +1158,9 @@ main(int argc, char *const *argv) case 'u': unconfigure = 1; break; + case 'v': + verbose += 2; + break; default: usage(); } @@ -1176,11 +1181,18 @@ main(int argc, char *const *argv) if (argc != 1) usage(); - if (pledge("stdio cpath rpath wpath exec proc dns inet", NULL) == -1) + if (pledge(rootdisk == NULL ? + "stdio cpath rpath wpath exec proc dns inet" : + "stdio cpath rpath wpath exec proc dns inet disklabel", + NULL) == -1) fatal("pledge"); - if ((sc = agent_init(argv[0], dryrun, timeout)) == NULL) + if ((sc = agent_init(argv[0], rootdisk, dryrun, timeout)) == NULL) fatalx("agent"); + + if (rootdisk != NULL && growdisk(sc) == -1) + fatalx("failed to grow %s", rootdisk); + sc->sc_args = args; if (username != NULL) { free(sc->sc_username); diff --git a/agent/main.h b/agent/main.h index 17fd724..29a59ec 100644 --- a/agent/main.h +++ b/agent/main.h @@ -82,6 +82,7 @@ struct system_config { const char *sc_ovfenv; const char *sc_interface; const char *sc_cdrom; + const char *sc_rootdisk; int sc_mount; struct source sc_addr; @@ -129,6 +130,9 @@ int opennebula(struct system_config *); /* openstack.c */ int openstack(struct system_config *); +/* growdisk.c */ +int growdisk(struct system_config *); + /* main.c */ int shell(const char *, ...); int shellout(const char *, char **, const char *, ...); From aa963100ba764676a96149d3475694b04302915a Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 2 Jun 2019 02:41:36 +0200 Subject: [PATCH 29/48] Fall back to 169.254.169.254 if DHCP endpoint does not work (unbreak OpenStack) --- agent/cloudinit.c | 51 +++++++++++++++++++++++++++++++++++++---------- agent/main.h | 4 ++++ agent/openstack.c | 14 ++----------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 807d784..aff28d6 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -31,31 +31,60 @@ static int cloudinit_fetch(struct system_config *); +int +tryendpoint(struct system_config *sc, + int (fetch)(struct system_config *), + int (next)(struct system_config *)) +{ + free(sc->sc_endpoint); + sc->sc_endpoint = NULL; + + switch (sc->sc_dhcpendpoint) { + case 0: + sc->sc_dhcpendpoint = 1; + if (dhcp_getendpoint(sc) == -1) + return tryendpoint(sc, fetch, next); + break; + case 1: + sc->sc_dhcpendpoint = 2; + if ((sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { + log_warnx("failed to set defaults"); + return (-1); + } + break; + default: + if (next == NULL) + return (-1); + sc->sc_dhcpendpoint = 0; + return (*next)(sc); + } + + if ((*fetch)(sc) != 0) + return tryendpoint(sc, fetch, next); + + return (0); +} + int ec2(struct system_config *sc) { free(sc->sc_username); - if ((sc->sc_username = strdup("ec2-user")) == NULL || - (sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { - log_warnx("failed to set defaults"); + if ((sc->sc_username = strdup("ec2-user")) == NULL) { + log_warnx("failed to set default user"); return (-1); } sc->sc_stack = "ec2"; - return (cloudinit_fetch(sc)); + sc->sc_dhcpendpoint = 1; + return tryendpoint(sc, cloudinit_fetch, NULL); } int cloudinit(struct system_config *sc) { - if ((dhcp_getendpoint(sc) == -1) && - (sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { - log_warnx("failed to set defaults"); - return (-1); - } - sc->sc_stack = "cloudinit"; - return (cloudinit_fetch(sc)); + sc->sc_dhcpendpoint = 0; + return tryendpoint(sc, cloudinit_fetch, NULL); } static int diff --git a/agent/main.h b/agent/main.h index 29a59ec..3ae86f7 100644 --- a/agent/main.h +++ b/agent/main.h @@ -76,6 +76,7 @@ struct system_config { char *sc_pubkey; char *sc_userdata; char *sc_endpoint; + int sc_dhcpendpoint; char *sc_instance; int sc_timeout; @@ -123,6 +124,9 @@ int azure(struct system_config *); /* cloudinit.c */ int ec2(struct system_config *); int cloudinit(struct system_config *); +int tryendpoint(struct system_config *, + int (fetch)(struct system_config *), + int (next)(struct system_config *)); /* opennebula.c */ int opennebula(struct system_config *); diff --git a/agent/openstack.c b/agent/openstack.c index 9c84d34..c60acd1 100644 --- a/agent/openstack.c +++ b/agent/openstack.c @@ -31,21 +31,11 @@ static int openstack_fetch(struct system_config *); + int openstack(struct system_config *sc) { - if ((dhcp_getendpoint(sc) == -1) && - (sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { - log_warnx("failed to set defaults"); - return (-1); - } - - if (openstack_fetch(sc) != 0) { - free(sc->sc_endpoint); - return (cloudinit(sc)); - } - - return (0); + return tryendpoint(sc, openstack_fetch, cloudinit); } static int From 139f35d9d441d49faac00f781fcad16cd5d4bc66 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 2 Jun 2019 03:01:55 +0200 Subject: [PATCH 30/48] Only update disk if size was changed and not under dryrun --- agent/growdisk.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/agent/growdisk.c b/agent/growdisk.c index dd920bb..587afbc 100644 --- a/agent/growdisk.c +++ b/agent/growdisk.c @@ -48,19 +48,21 @@ dkcksum(struct disklabel *lp) int growdisk(struct system_config *sc) { - char c, last_part = 0, *out, *path = NULL; + char c, last_part = 0, *out = NULL, *path = NULL; int ret = -1, i, errfd, outfd; + uint64_t bend, psize; struct partition *pp, *p = NULL; struct disklabel lp; - int fd; uint16_t cksum; + int fd; /* * Grow the OpenBSD MBR partition */ /* XXX this is a bit ugly but easier to do */ - if (shellout("e 3\n\n\n\n*\nw\nq\n", &out, + if (!sc->sc_dryrun && + shellout("e 3\n\n\n\n*\nw\nq\n", &out, "fdisk", "-e", sc->sc_rootdisk, NULL) != 0) { log_warnx("failed to grow OpenBSD partition"); return (-1); @@ -110,11 +112,26 @@ growdisk(struct system_config *sc) goto done; } + bend = DL_GETDSIZE(&lp) - DL_GETBSTART(&lp); + psize = DL_GETBEND(&lp) - DL_GETPOFFSET(p); + + if (sc->sc_dryrun || + (bend == DL_GETBEND(&lp) && psize == DL_GETPSIZE(p))) { + log_debug("%s: %s%c uses maximum size %llu", + __func__, sc->sc_rootdisk, last_part, psize); + + ret = 0; + goto done; + } + + log_debug("%s: growing %s%c from %llu to %llu", + __func__, sc->sc_rootdisk, last_part, DL_GETPSIZE(p), psize); + /* Update OpenBSD boundaries */ - DL_SETBEND(&lp, DL_GETDSIZE(&lp) - DL_GETBSTART(&lp)); + DL_SETBEND(&lp, bend); /* Update the size of the last partition */ - DL_SETPSIZE(p, DL_GETBEND(&lp) - DL_GETPOFFSET(p)); + DL_SETPSIZE(p, psize); lp.d_checksum = dkcksum(&lp); From 63935a1b5f2c4d20b7d4629d97b236453cd3edb1 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 2 Jun 2019 03:13:58 +0200 Subject: [PATCH 31/48] Don't print errors from the DHCP endpoint --- agent/cloudinit.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/agent/cloudinit.c b/agent/cloudinit.c index aff28d6..75b28e7 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -36,6 +36,8 @@ tryendpoint(struct system_config *sc, int (fetch)(struct system_config *), int (next)(struct system_config *)) { + int errfd = -1, ret; + free(sc->sc_endpoint); sc->sc_endpoint = NULL; @@ -59,7 +61,10 @@ tryendpoint(struct system_config *sc, return (*next)(sc); } - if ((*fetch)(sc) != 0) + errfd = disable_output(sc, STDERR_FILENO); + ret = (*fetch)(sc); + enable_output(sc, STDERR_FILENO, errfd); + if (ret != 0) return tryendpoint(sc, fetch, next); return (0); From 95e8cb1cb159d3c51218818f71f4b215e804e6fd Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 2 Jun 2019 03:21:22 +0200 Subject: [PATCH 32/48] Fix bEnd --- agent/growdisk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/growdisk.c b/agent/growdisk.c index 587afbc..aa5dfdc 100644 --- a/agent/growdisk.c +++ b/agent/growdisk.c @@ -113,7 +113,7 @@ growdisk(struct system_config *sc) } bend = DL_GETDSIZE(&lp) - DL_GETBSTART(&lp); - psize = DL_GETBEND(&lp) - DL_GETPOFFSET(p); + psize = bend - DL_GETPOFFSET(p); if (sc->sc_dryrun || (bend == DL_GETBEND(&lp) && psize == DL_GETPSIZE(p))) { From bce8634bf5a60e11d67cb3859c00910ef4b3fbd0 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 2 Jun 2019 03:25:40 +0200 Subject: [PATCH 33/48] re-mount updated filesystem --- agent/growdisk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/growdisk.c b/agent/growdisk.c index aa5dfdc..d9e8581 100644 --- a/agent/growdisk.c +++ b/agent/growdisk.c @@ -154,6 +154,7 @@ growdisk(struct system_config *sc) (void)shell("growfs", "-yq", path, NULL); if ((ret = shell("fsck", "-y", path, NULL)) != 0) ret = -1; + (void)shell("mount", "-a", "-t", "nonfs,vnd", NULL); enable_output(sc, STDERR_FILENO, errfd); enable_output(sc, STDOUT_FILENO, outfd); From 0509d8d6193db0368fe2ec6feaae7bc79a0d582a Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 2 Jun 2019 11:22:57 +0200 Subject: [PATCH 34/48] Only grow, but never shrink the disk --- agent/growdisk.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/agent/growdisk.c b/agent/growdisk.c index d9e8581..2f76be4 100644 --- a/agent/growdisk.c +++ b/agent/growdisk.c @@ -32,6 +32,8 @@ #include "main.h" +#define MEG(_n) ((_n) * DEV_BSIZE / 1024 / 1024) + static uint16_t dkcksum(struct disklabel *); static uint16_t @@ -115,18 +117,22 @@ growdisk(struct system_config *sc) bend = DL_GETDSIZE(&lp) - DL_GETBSTART(&lp); psize = bend - DL_GETPOFFSET(p); - if (sc->sc_dryrun || - (bend == DL_GETBEND(&lp) && psize == DL_GETPSIZE(p))) { - log_debug("%s: %s%c uses maximum size %llu", - __func__, sc->sc_rootdisk, last_part, psize); - + /* Only grow, but never shring the disk */ + if (bend <= DL_GETBEND(&lp) && psize <= DL_GETPSIZE(p)) { + log_debug("%s: not growing %s%c, size is %lluMB", + __func__, sc->sc_rootdisk, last_part, MEG(psize)); + ret = 0; + } else { + log_info("growing %s%c from %lluMB to %lluMB", + sc->sc_rootdisk, last_part, + MEG(DL_GETPSIZE(p)), MEG(psize)); + ret = -1; + } + if (sc->sc_dryrun || ret == 0) { ret = 0; goto done; } - log_debug("%s: growing %s%c from %llu to %llu", - __func__, sc->sc_rootdisk, last_part, DL_GETPSIZE(p), psize); - /* Update OpenBSD boundaries */ DL_SETBEND(&lp, bend); From 99f8b2d2b034bd34185553c89a85220c7a3967e1 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 4 Jun 2019 15:15:24 +0200 Subject: [PATCH 35/48] The SSH_PUBLIC_KEY line in OpenNebula's context can span multiple lines --- agent/opennebula.c | 57 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/agent/opennebula.c b/agent/opennebula.c index c17c16c..e9da666 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -34,6 +34,7 @@ opennebula(struct system_config *sc) FILE *fp; const char *delim = "\\\\\0", *errstr; char *line = NULL, *k, *v, *p, q; + char *value = NULL, *next = NULL, *last; char *hname = NULL; size_t len, lineno = 0, i; int ret = -1; @@ -72,20 +73,55 @@ opennebula(struct system_config *sc) /* value is quoted */ q = *v; - if (strspn(v, "\"'") == 0 || (p = strrchr(v, q)) == v) { + if (strspn(v, "\"'") == 0) { free(line); continue; } *v++ = '\0'; - *p = '\0'; + + /* quoted value can be continued on multiple lines */ + if ((value = strdup("")) == NULL) { + log_debug("%s: strdup", __func__); + goto done; + } + next = v; + do { + if ((p = strrchr(next, q)) != NULL) + *p++ = '\0'; + if (*next) { + last = value; + if (asprintf(&value, "%s%s\n", + last, next) == -1) { + log_debug("%s: asprintf", __func__); + if (next != v) + free(next); + goto done; + } + free(last); + } + if (next != v) + free(next); + } while (p == NULL && + (next = fparseln(fp, &len, &lineno, + delim, FPARSELN_UNESCALL)) != NULL); + next = NULL; + v = value; + + /* strip trailing newline */ + if ((p = strrchr(v, '\n')) != NULL) + *p = '\0'; /* continue if value is empty */ if (*v == '\0') { free(line); + free(value); + value = NULL; continue; } - log_debug("%s: %s = %s", __func__, k, v); + /* print key/value unless it is a multi-line value */ + if (strcasecmp("SSH_PUBLIC_KEY", k) != 0) + log_debug("%s: %s = %s", __func__, k, v); if (strcasecmp("NETWORK", k) == 0) { if (strcasecmp("YES", v) == 0) @@ -178,11 +214,21 @@ opennebula(struct system_config *sc) if ((hname = strdup(v)) == NULL) log_warnx("failed to set hostname"); } else if (strcasecmp("SSH_PUBLIC_KEY", k) == 0) { - if (agent_addpubkey(sc, v, NULL) != 0) - log_warnx("failed to set ssh pubkey"); + do { + p = v + strcspn(v, "\n"); + *p++ = '\0'; + if (*v) + log_debug("%s: %s = %s", + __func__, k, v); + if (*v && agent_addpubkey(sc, v, NULL) != 0) + log_warnx("failed to set ssh pubkey"); + v = p + strspn(p, "\n"); + } while (*v != '\0'); } free(line); + free(value); + value = NULL; } fclose(fp); @@ -216,5 +262,6 @@ opennebula(struct system_config *sc) if (fp != NULL) fclose(fp); free(line); + free(value); return (ret); } From c5c1705cd7379d5c747b78064ad5605ad533432b Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 5 Jun 2019 18:18:49 +0200 Subject: [PATCH 36/48] Add support for OpenNebula's userdata START_SCRIPT and START_SCRIPT_BASE64 --- agent/main.c | 20 ++++++++++++++++---- agent/opennebula.c | 22 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/agent/main.c b/agent/main.c index 4632c97..2ac73b1 100644 --- a/agent/main.c +++ b/agent/main.c @@ -49,7 +49,8 @@ static int agent_configure(struct system_config *); static int agent_network(struct system_config *); static void agent_free(struct system_config *); static int agent_pf(struct system_config *, int); -static int agent_userdata(const unsigned char *, size_t); +static int agent_userdata(struct system_config *, + const unsigned char *, size_t); static void agent_unconfigure(void); static char *metadata_parse(char *, size_t, enum strtype); @@ -713,7 +714,7 @@ agent_configure(struct system_config *sc) } if (sc->sc_userdata) { - if (agent_userdata(sc->sc_userdata, + if (agent_userdata(sc, sc->sc_userdata, strlen(sc->sc_userdata)) != 0) log_warnx("user-data failed"); } @@ -737,7 +738,8 @@ agent_configure(struct system_config *sc) } static int -agent_userdata(const unsigned char *userdata, size_t len) +agent_userdata(struct system_config *sc, + const unsigned char *userdata, size_t len) { char *shebang = NULL, *str = NULL, *line = NULL; const char *file; @@ -760,7 +762,7 @@ agent_userdata(const unsigned char *userdata, size_t len) /* Decode user-data and call the function again */ if ((str = calloc(1, len + 1)) == NULL || (len = b64_pton(userdata, str, len)) < 1 || - agent_userdata(str, len) != 0) { + agent_userdata(sc, str, len) != 0) { log_warnx("failed to decode user-data"); goto fail; } @@ -780,6 +782,9 @@ agent_userdata(const unsigned char *userdata, size_t len) goto fail; } + if (sc->sc_dryrun) + goto done; + /* write user-data script into file */ file = "/etc/rc.user-data"; if (fileout(str, "w", file) != 0) { @@ -1212,6 +1217,13 @@ main(int argc, char *const *argv) else ret = openstack(sc); + /* Debug userdata */ + if (sc->sc_dryrun && sc->sc_userdata) { + if (agent_userdata(sc, sc->sc_userdata, + strlen(sc->sc_userdata)) != 0) + log_warnx("user-data failed"); + } + if (sc->sc_stack != NULL) log_debug("%s: %s", __func__, sc->sc_stack); diff --git a/agent/opennebula.c b/agent/opennebula.c index e9da666..8395920 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -49,11 +49,11 @@ opennebula(struct system_config *sc) while ((line = fparseln(fp, &len, &lineno, delim, FPARSELN_UNESCALL)) != NULL) { /* key */ - k = line; + k = line + strspn(line, " \t\r"); /* a context always starts with this header */ if (lineno == 1) { - ret = strcmp(line, + ret = strcmp(k, "# Context variables generated by OpenNebula"); if (ret != 0) { log_debug("%s: unsupported context", __func__); @@ -62,7 +62,12 @@ opennebula(struct system_config *sc) free(line); continue; } - line[strcspn(line, "#")] = '\0'; + + /* Strip comments that do not occur within a value */ + if (*k == '#') { + free(line); + continue; + } /* value */ if ((v = strchr(line, '=')) == NULL || *(v + 1) == '\0') { @@ -120,7 +125,9 @@ opennebula(struct system_config *sc) } /* print key/value unless it is a multi-line value */ - if (strcasecmp("SSH_PUBLIC_KEY", k) != 0) + if (strcasecmp("SSH_PUBLIC_KEY", k) != 0 && + strcasecmp("START_SCRIPT", k) != 0 && + strcasecmp("START_SCRIPT_BASE64", k) != 0) log_debug("%s: %s = %s", __func__, k, v); if (strcasecmp("NETWORK", k) == 0) { @@ -224,6 +231,13 @@ opennebula(struct system_config *sc) log_warnx("failed to set ssh pubkey"); v = p + strspn(p, "\n"); } while (*v != '\0'); + } else if (strcasecmp("START_SCRIPT", k) == 0 || + strcasecmp("START_SCRIPT_BASE64", k) == 0) { + log_debug("%s: %s = ...", __func__, k); + + /* We will detect and decode base64 later */ + if ((sc->sc_userdata = strdup(v)) == NULL) + log_warnx("failed to set userdata"); } free(line); From b8ae4a13fc00f6648ffe54177a1f8b65bbaf876f Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 5 Jun 2019 21:39:05 +0200 Subject: [PATCH 37/48] Allow to generate and write a comment into ~/.ssh/authorized_keys --- agent/azure.c | 16 ++++---- agent/cloud-agent.8 | 14 +++++++ agent/main.c | 93 ++++++++++++++++++++++++++++++++++++++++----- agent/main.h | 3 +- 4 files changed, 109 insertions(+), 17 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index 40efd03..990d973 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "main.h" @@ -719,7 +720,7 @@ azure_getovfenv(struct system_config *sc) } if ((xe = xml_findl(&xp->xe_head, "UserPassword", NULL)) != NULL) { - if ((sc->sc_password = calloc(1, 128)) == NULL) { + if ((sc->sc_password_hash = calloc(1, _PASSWORD_LEN)) == NULL) { log_debug("%s: password failed", __func__); goto done; } @@ -727,13 +728,14 @@ azure_getovfenv(struct system_config *sc) str = strndup(xe->xe_data, xe->xe_datalen); if (str == NULL || crypt_newhash(str, "bcrypt,a", - sc->sc_password, 128) != 0) { + sc->sc_password_hash, _PASSWORD_LEN) != 0) { log_debug("%s: password hashing failed", __func__); - free(sc->sc_password); - sc->sc_password = NULL; + free(sc->sc_password_hash); + sc->sc_password_hash = NULL; free(str); goto done; } + explicit_bzero(str, xe->xe_datalen); free(str); /* Replace unencrypted password with hash */ @@ -743,11 +745,11 @@ azure_getovfenv(struct system_config *sc) /* Update element for xml_print() below */ explicit_bzero(xe->xe_data, xe->xe_datalen); free(xe->xe_data); - xe->xe_data = strdup(sc->sc_password); - xe->xe_datalen = strlen(sc->sc_password); + xe->xe_data = strdup(sc->sc_password_hash); + xe->xe_datalen = strlen(sc->sc_password_hash); } else if ((xe = xml_findl(&xp->xe_head, "UserPasswordHash", NULL)) != NULL) { - if ((sc->sc_password = + if ((sc->sc_password_hash = get_word(xe->xe_data, xe->xe_datalen)) != NULL) { log_debug("%s: password hash failed", __func__); goto done; diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index 0cec012..b6990f3 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -23,6 +23,7 @@ .Sh SYNOPSIS .Nm cloud-agent .Op Fl nuv +.Op Fl p Ar length .Op Fl r Ar rootdisk .Op Fl t Ar timeout .Op Fl U Ar username @@ -35,6 +36,17 @@ environments, including Microsoft Azure and Amazon AWS. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl p Ar length +Generate and set a random password for the default user. +The password will be written in its plain form into the +.Pa ~/.ssh/authorized_keys +file. +This allows to use the +.Xr doas 1 +command to gain root privileges. +The minimum +.Ar length +is 8 characters and the default is an empty password. .It Fl n Do not configure the system and skip the provisioning step. .It Fl t Ar timeout @@ -78,6 +90,8 @@ dhcp .Ed .Sh FILES .Bl -tag -width "/usr/local/libexec/cloud-agentX" -compact +.It Pa ~/.ssh/authorized_keys +The location of the agent-configured SSH public keys and optional password. .It Pa /usr/local/libexec/cloud-agent The agent itself. .It Pa /usr/local/bin/cms diff --git a/agent/main.c b/agent/main.c index 2ac73b1..e4c30cd 100644 --- a/agent/main.c +++ b/agent/main.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include "main.h" @@ -360,13 +361,19 @@ agent_free(struct system_config *sc) log_debug("%s: unmounted %s", __func__, sc->sc_cdrom); } - free(sc->sc_args); + if (sc->sc_password_plain != NULL) { + /* XXX can be removed with calloc_conceal() post-6.6 */ + explicit_bzero(sc->sc_password_plain, + strlen(sc->sc_password_plain)); + free(sc->sc_password_plain); + } + free(sc->sc_password_hash); free(sc->sc_hostname); free(sc->sc_username); - free(sc->sc_password); free(sc->sc_userdata); free(sc->sc_endpoint); free(sc->sc_instance); + free(sc->sc_args); close(sc->sc_nullfd); while ((ssh = TAILQ_FIRST(&sc->sc_pubkeys))) { @@ -628,6 +635,7 @@ agent_pf(struct system_config *sc, int open) static int agent_configure(struct system_config *sc) { + char pwbuf[_PASSWORD_LEN + 2]; struct ssh_pubkey *ssh; char *str1, *str2; @@ -669,18 +677,30 @@ agent_configure(struct system_config *sc) } /* password */ - if (sc->sc_password == NULL) { + if (sc->sc_password_hash == NULL) { if (asprintf(&str2, "permit keepenv nopass %s as root\n" "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; } else { - if (shell("usermod", "-p", sc->sc_password, + if (shell("usermod", "-p", sc->sc_password_hash, sc->sc_username, NULL) != 0) log_warnx("password failed"); if (asprintf(&str2, "permit keepenv persist %s as root\n" "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; + + /* write generated password as comment to authorized_keys */ + if (sc->sc_password_plain != NULL) { + snprintf(pwbuf, sizeof(pwbuf), "# %s", + sc->sc_password_plain); + if (fileout(pwbuf, "w", + "%s/%s/.ssh/authorized_keys", + strcmp("root", sc->sc_username) == 0 ? "" : "/home", + sc->sc_username) != 0) + log_warnx("password comment failed"); + explicit_bzero(pwbuf, sizeof(pwbuf)); + } } /* doas */ @@ -690,7 +710,7 @@ agent_configure(struct system_config *sc) free(str2); /* ssh configuration */ - if (sc->sc_password == NULL && !TAILQ_EMPTY(&sc->sc_pubkeys)) + if (sc->sc_password_hash == NULL && !TAILQ_EMPTY(&sc->sc_pubkeys)) str1 = "/PasswordAuthentication/" "s/.*/PasswordAuthentication no/"; else @@ -1096,8 +1116,8 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-nuv] [-r rootdisk] [-t 3] [-U puffy] " - "interface\n", __progname); + fprintf(stderr, "usage: %s [-nuv] [-p length] [-r rootdisk] " + "[-t 3] [-U puffy] interface\n", __progname); exit(1); } @@ -1131,23 +1151,65 @@ get_args(int argc, char *const *argv) return (args); } +static char * +pwgen(size_t len, char **hash) +{ + char *password; + size_t i, alphabet_len; + const char *alphabet = + "0123456789_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + alphabet_len = strlen(alphabet); + *hash = NULL; + + /* XXX use calloc_conceal() post-6.6 */ + if ((password = calloc(1, len + 1)) == NULL) + return (NULL); + + /* Simple password generator */ + for (i = 0; i < len; i++) + password[i] = alphabet[arc4random_uniform(alphabet_len)]; + + if ((*hash = calloc(1, _PASSWORD_LEN)) == NULL) { + freezero(password, len + 1); + return (NULL); + } + + if (crypt_newhash(password, "bcrypt,a", + *hash, _PASSWORD_LEN) != 0) { + freezero(password, len + 1); + freezero(*hash, _PASSWORD_LEN); + password = NULL; + *hash = NULL; + } + + return (password); +} + int main(int argc, char *const *argv) { struct system_config *sc; int verbose = 0, dryrun = 0, unconfigure = 0; - int ch, ret, timeout = CONNECT_TIMEOUT; + int genpw = 0, ch, ret, timeout = CONNECT_TIMEOUT; const char *error = NULL, *rootdisk = NULL; char *args, *username = NULL; if ((args = get_args(argc, argv)) == NULL) fatalx("failed to save args"); - while ((ch = getopt(argc, argv, "nr:t:U:uv")) != -1) { + while ((ch = getopt(argc, argv, "np:r:t:U:uv")) != -1) { switch (ch) { case 'n': dryrun = 1; break; + case 'p': + genpw = strtonum(optarg, 8, 8192, &error); + if (error != NULL) + fatalx("invalid password length: %s", error); + break; case 'r': rootdisk = optarg; break; @@ -1217,6 +1279,19 @@ main(int argc, char *const *argv) else ret = openstack(sc); + if (genpw) { + if (sc->sc_password_hash != NULL) + log_debug("%s: user password hash: %s", __func__, + sc->sc_password_hash); + else if ((sc->sc_password_plain = pwgen(genpw, + &sc->sc_password_hash)) != NULL) { + if (log_getverbose() > 2) + log_debug("%s: generated password: %s", + __func__, sc->sc_password_plain); + } else + log_warnx("failed to generate password"); + } + /* Debug userdata */ if (sc->sc_dryrun && sc->sc_userdata) { if (agent_userdata(sc, sc->sc_userdata, diff --git a/agent/main.h b/agent/main.h index 3ae86f7..93b5f92 100644 --- a/agent/main.h +++ b/agent/main.h @@ -72,7 +72,8 @@ struct system_config { char *sc_hostname; char *sc_username; - char *sc_password; + char *sc_password_plain; + char *sc_password_hash; char *sc_pubkey; char *sc_userdata; char *sc_endpoint; From 3be970741809d506bda6d805b661623403ecaa65 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 5 Jun 2019 22:01:21 +0200 Subject: [PATCH 38/48] Remove /var/db/cloud-instance file on -u --- agent/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/main.c b/agent/main.c index e4c30cd..05ae3dd 100644 --- a/agent/main.c +++ b/agent/main.c @@ -935,8 +935,10 @@ agent_unconfigure(void) (void)fileout("permit keepenv persist :wheel as root\n" "permit keepenv nopass root\n", "w", "/etc/doas.conf"); -} + /* Remove cloud-instance file */ + (void)unlink("/var/db/cloud-instance"); +} static char * metadata_parse(char *s, size_t sz, enum strtype type) From ba34eb76dd5364e17551e36b55a2f897038c47b5 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 5 Jun 2019 22:12:42 +0200 Subject: [PATCH 39/48] Add cloud-agent(8) as markdown file --- README.md | 6 ++- cloud-agent.md | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 cloud-agent.md diff --git a/README.md b/README.md index 8de4435..0836734 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,11 @@ has removed CMS which is required by Azure. Usage ----- -Installation is easy, `cloud-agent` detects the cloud type automatically. +See the [cloud-agent(8)](cloud-agent.md) documentation for more +information about the usage. + +Basic installation is easy, `cloud-agent` detects the cloud type +automatically. * On Microsoft Azure, create a file `/etc/hostname.hvn0` diff --git a/cloud-agent.md b/cloud-agent.md new file mode 100644 index 0000000..4ef3ea3 --- /dev/null +++ b/cloud-agent.md @@ -0,0 +1,119 @@ +CLOUD-AGENT(8) - System Manager's Manual + +# NAME + +**cloud-agent** - cloud provisioning for OpenBSD VMs + +# SYNOPSIS + +**cloud-agent** +\[**-nuv**] +\[**-p** *length*] +\[**-r** *rootdisk*] +\[**-t** *timeout*] +\[**-U** *username*] +*interface* + +# DESCRIPTION + +The +**cloud-agent** +program manages the OpenBSD provisioning and VM interaction in cloud +environments, including Microsoft Azure and Amazon AWS. + +The options are as follows: + +**-p** *length* + +> Generate and set a random password for the default user. +> The password will be written in its plain form into the +> *~/.ssh/authorized\_keys* +> file. +> This allows to use the +> doas(1) +> command to gain root privileges. +> The minimum +> *length* +> is 8 characters and the default is an empty password. + +**-n** + +> Do not configure the system and skip the provisioning step. + +**-t** *timeout* + +> Change the HTTP timeout. +> The default is 3 seconds. + +**-U** *username* + +> Change the default user. +> The default is +> "ec2-user" +> on AWS, +> "azure-user" +> on Azure, and +> "puffy" +> everywhere else. +> The default user is used when it is not obtained from the cloud +> configuration. +> Using +> "root" +> is supported, but not recommended. + +**-r** *rootdisk* + +> Automatically grow the last +> OpenBSD +> FFS partition of the root disk to use all the available space. + +**-u** + +> Deprovision and unconfigure the system. +> This deletes keys, passwords, and logs files without asking for permission. + +**-v** + +> Produce more verbose output. + +Enable +**cloud-agent** +in the +hostname.if(5) +of the VM's primary networking interface and automatically the last +partition of the root disk: + + # cat /etc/hostname.hvn0 + dhcp + !/usr/local/libexec/cloud-agent -r sd0 "\$if" + +# FILES + +*~/.ssh/authorized\_keys* + +> The location of the agent-configured SSH public keys and optional password. + +*/usr/local/libexec/cloud-agent* + +> The agent itself. + +*/usr/local/bin/cms* + +> The CMS binary that is used to decrypt messages from the Azure fabric. + +*/var/db/cloud-instance* + +> The instance ID as reported by the cloud. +> **cloud-agent** +> reprovisions the system when the value has changed. + +# SEE ALSO + +meta-data(8), +vmd(8) + +# AUTHORS + +Reyk Floeter <[reyk@openbsd.org](mailto:reyk@openbsd.org)> + +OpenBSD 6.5 - June 5, 2019 From ca22cba8e41247701338142fa2057343e2a517fb Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 4 Jun 2019 18:09:50 +0200 Subject: [PATCH 40/48] Allow to specify the probed cloud stacks with -c cloud[,cloud...] --- agent/azure.c | 22 ++++--- agent/cloud-agent.8 | 24 +++++++- agent/cloudinit.c | 62 ++++---------------- agent/main.c | 140 ++++++++++++++++++++++++++++++++++++++------ agent/main.h | 32 ++++++++-- agent/opennebula.c | 7 ++- agent/openstack.c | 8 ++- 7 files changed, 210 insertions(+), 85 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index 990d973..c880f9d 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -63,16 +63,22 @@ azure(struct system_config *sc) { int ret = -1; - sc->sc_stack = "azure"; + if (sc->sc_state == STATE_INIT) { + free(sc->sc_username); + if ((sc->sc_username = strdup("azure-user")) == NULL) { + log_warnx("failed to set default user"); + return (-1); + } - /* Apply defaults */ - free(sc->sc_username); - if ((sc->sc_username = strdup("azure-user")) == NULL) { - log_warnx("failed to set default user"); - goto fail; + /* Apply defaults */ + sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; + sc->sc_priv = &az_config; + sc->sc_state = STATE_DHCP; + return (-1); } - sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; - sc->sc_priv = &az_config; + + /* Don't try other endpoints */ + sc->sc_state = STATE_DONE; if (azure_getovfenv(sc) != 0) { log_warnx("failed to get ovf-env.xml"); diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index b6990f3..5f01cf0 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -23,6 +23,7 @@ .Sh SYNOPSIS .Nm cloud-agent .Op Fl nuv +.Op Fl c Ar cloud Ns Op , Ns Ar cloud Ns ... .Op Fl p Ar length .Op Fl r Ar rootdisk .Op Fl t Ar timeout @@ -32,10 +33,31 @@ The .Nm program manages the OpenBSD provisioning and VM interaction in cloud -environments, including Microsoft Azure and Amazon AWS. +environments. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl c Ar cloud Ns Op , Ns Ar cloud Ns ... +Probe a list of cloud stacks for provisioning in the specified order. +If this option is not specified, +.Nm +tries to detect the environment and possible cloud stacks automatically. +Supported +.Ar cloud +stacks are: +.Pp +.Bl -tag -width opennebula -offset indent -compact +.It Ic azure +Microsoft Azure +.It Ic cloudinit +Generic cloud-init +.It Ic ec2 +Amazon AWS EC2 +.It Ic opennebula +OpenNebula +.It Ic openstack +OpenStack +.El .It Fl p Ar length Generate and set a random password for the default user. The password will be written in its plain form into the diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 75b28e7..29582b4 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -31,65 +31,29 @@ static int cloudinit_fetch(struct system_config *); -int -tryendpoint(struct system_config *sc, - int (fetch)(struct system_config *), - int (next)(struct system_config *)) -{ - int errfd = -1, ret; - - free(sc->sc_endpoint); - sc->sc_endpoint = NULL; - - switch (sc->sc_dhcpendpoint) { - case 0: - sc->sc_dhcpendpoint = 1; - if (dhcp_getendpoint(sc) == -1) - return tryendpoint(sc, fetch, next); - break; - case 1: - sc->sc_dhcpendpoint = 2; - if ((sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { - log_warnx("failed to set defaults"); - return (-1); - } - break; - default: - if (next == NULL) - return (-1); - sc->sc_dhcpendpoint = 0; - return (*next)(sc); - } - - errfd = disable_output(sc, STDERR_FILENO); - ret = (*fetch)(sc); - enable_output(sc, STDERR_FILENO, errfd); - if (ret != 0) - return tryendpoint(sc, fetch, next); - - return (0); -} - int ec2(struct system_config *sc) { - free(sc->sc_username); - if ((sc->sc_username = strdup("ec2-user")) == NULL) { - log_warnx("failed to set default user"); + if (sc->sc_state == STATE_INIT) { + free(sc->sc_username); + if ((sc->sc_username = strdup("ec2-user")) == NULL) { + log_warnx("failed to set default user"); + return (-1); + } + sc->sc_state = STATE_169; return (-1); } - - sc->sc_stack = "ec2"; - sc->sc_dhcpendpoint = 1; - return tryendpoint(sc, cloudinit_fetch, NULL); + return cloudinit_fetch(sc); } int cloudinit(struct system_config *sc) { - sc->sc_stack = "cloudinit"; - sc->sc_dhcpendpoint = 0; - return tryendpoint(sc, cloudinit_fetch, NULL); + if (sc->sc_state == STATE_INIT) { + sc->sc_state = STATE_DHCP; + return (-1); + } + return cloudinit_fetch(sc); } static int diff --git a/agent/main.c b/agent/main.c index 05ae3dd..ed44b3a 100644 --- a/agent/main.c +++ b/agent/main.c @@ -56,6 +56,7 @@ static void agent_unconfigure(void); static char *metadata_parse(char *, size_t, enum strtype); static int agent_timeout; +static char *cloudnames[] = CLOUDNAMES; int shell(const char *arg, ...) @@ -970,7 +971,8 @@ metadata(struct system_config *sc, const char *path, enum strtype type) g = http_get(&sc->sc_addr, 1, sc->sc_endpoint, 80, path, NULL, 0, NULL); if (g != NULL) - log_debug("%s: HTTP %d %s", __func__, g->code, path); + log_debug("%s: HTTP %d http://%s%s", __func__, g->code, + sc->sc_endpoint, path); if (g != NULL && g->code == 200 && g->bodypartsz > 0) str = metadata_parse(g->bodypart, g->bodypartsz, type); @@ -1190,20 +1192,118 @@ pwgen(size_t len, char **hash) return (password); } +static void +cloud_add(enum cloudname name, struct clouds *clouds) +{ + struct cloud *cloud; + + TAILQ_FOREACH(cloud, clouds, cloud_entry) { + if (cloud->cloud_name == name) + fatalx("cloud %s defined twice", + cloudnames[name]); + } + + if ((cloud = calloc(1, sizeof(*cloud))) == NULL) + fatal("%s: calloc", __func__); + + cloud->cloud_name = name; + switch (name) { + case AZURE: + cloud->fetch = azure; + break; + case CLOUDINIT: + cloud->fetch = cloudinit; + break; + case EC2: + cloud->fetch = ec2; + break; + case OPENNEBULA: + cloud->fetch = opennebula; + break; + case OPENSTACK: + cloud->fetch = openstack; + break; + } + + TAILQ_INSERT_TAIL(clouds, cloud, cloud_entry); +} + +static int +trycloud(struct system_config *sc, struct cloud *cloud) +{ + int errfd = -1, ret = -1; + + free(sc->sc_endpoint); + sc->sc_endpoint = NULL; + + switch (sc->sc_state) { + case STATE_INIT: + if ((cloud = TAILQ_FIRST(sc->sc_clouds)) == NULL) + return (0); + sc->sc_stack = cloudnames[cloud->cloud_name]; + log_debug("%s: %s", __func__, sc->sc_stack); + TAILQ_REMOVE(sc->sc_clouds, cloud, cloud_entry); + break; + case STATE_DHCP: + sc->sc_state = STATE_169; + if (dhcp_getendpoint(sc) == -1) + return trycloud(sc, cloud); + break; + case STATE_169: + sc->sc_state = STATE_DONE; + if ((sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { + log_warnx("failed to set defaults"); + goto done; + } + break; + case STATE_DONE: + sc->sc_state = STATE_INIT; + ret = trycloud(sc, NULL); + goto done; + } + + errfd = disable_output(sc, STDERR_FILENO); + ret = (*cloud->fetch)(sc); + enable_output(sc, STDERR_FILENO, errfd); + if (ret != 0) + return trycloud(sc, cloud); + + done: + free(cloud); + return (0); +} + int main(int argc, char *const *argv) { struct system_config *sc; - int verbose = 0, dryrun = 0, unconfigure = 0; + int verbose = 0, dryrun = 0, unconfigure = 0, sub; int genpw = 0, ch, ret, timeout = CONNECT_TIMEOUT; const char *error = NULL, *rootdisk = NULL; - char *args, *username = NULL; + char *args, *username = NULL, *options, *value; + struct clouds clouds; + + /* log to stderr */ + log_init(1, LOG_DAEMON); if ((args = get_args(argc, argv)) == NULL) fatalx("failed to save args"); - while ((ch = getopt(argc, argv, "np:r:t:U:uv")) != -1) { + TAILQ_INIT(&clouds); + + while ((ch = getopt(argc, argv, "c:np:r:t:U:uv")) != -1) { switch (ch) { + case 'c': + options = optarg; + while (*options) { + if ((sub = getsubopt(&options, + cloudnames, &value)) == -1) + fatalx("invalid cloud stack"); + else if (value != NULL) + fatalx("unexpected value"); + cloud_add(sub, &clouds); + } + break; case 'n': dryrun = 1; break; @@ -1238,8 +1338,6 @@ main(int argc, char *const *argv) argv += optind; argc -= optind; - /* log to stderr */ - log_init(1, LOG_DAEMON); log_setverbose(verbose); if (unconfigure) { @@ -1262,24 +1360,30 @@ main(int argc, char *const *argv) if (rootdisk != NULL && growdisk(sc) == -1) fatalx("failed to grow %s", rootdisk); + sc->sc_clouds = &clouds; sc->sc_args = args; if (username != NULL) { free(sc->sc_username); sc->sc_username = username; } - /* - * XXX Detect cloud with help from hostctl and sysctl - * XXX in addition to the interface name. - */ - if (opennebula(sc) == 0) - ret = 0; - else if (strcmp("hvn0", sc->sc_interface) == 0) - ret = azure(sc); - else if (strcmp("xnf0", sc->sc_interface) == 0) - ret = ec2(sc); - else - ret = openstack(sc); + if (TAILQ_EMPTY(&clouds)) { + /* + * XXX Auto-detect cloud with help from hostctl and + * XXX sysctl in addition to the interface name. + */ + if (strcmp("hvn0", sc->sc_interface) == 0) { + cloud_add(AZURE, &clouds); + } else if (strcmp("xnf0", sc->sc_interface) == 0) { + cloud_add(OPENNEBULA, &clouds); + cloud_add(EC2, &clouds); + } else { + cloud_add(OPENNEBULA, &clouds); + cloud_add(OPENSTACK, &clouds); + cloud_add(CLOUDINIT, &clouds); + } + } + ret = trycloud(sc, NULL); if (genpw) { if (sc->sc_password_hash != NULL) diff --git a/agent/main.h b/agent/main.h index 93b5f92..b54b789 100644 --- a/agent/main.h +++ b/agent/main.h @@ -29,12 +29,30 @@ #define DEFAULT_ENDPOINT "169.254.169.254" #define CONNECT_TIMEOUT 10 /* in seconds */ +enum cloudname { + AZURE, + CLOUDINIT, + EC2, + OPENNEBULA, + OPENSTACK +}; +#define CLOUDNAMES { \ + "azure", "cloudinit", "ec2", "opennebula", "openstack", NULL \ +} + enum strtype { WORD, LINE, TEXT }; +enum state { + STATE_INIT, + STATE_DHCP, + STATE_169, + STATE_DONE +}; + struct ssh_pubkey { char *ssh_keyval; char *ssh_keyfp; @@ -66,6 +84,14 @@ struct net_addr { }; TAILQ_HEAD(net_addrs, net_addr); +struct system_config; +struct cloud { + enum cloudname cloud_name; + int (*fetch)(struct system_config *); + TAILQ_ENTRY(cloud) cloud_entry; +}; +TAILQ_HEAD(clouds, cloud); + struct system_config { const char *sc_stack; char *sc_args; @@ -77,7 +103,8 @@ struct system_config { char *sc_pubkey; char *sc_userdata; char *sc_endpoint; - int sc_dhcpendpoint; + enum state sc_state; + struct clouds *sc_clouds; char *sc_instance; int sc_timeout; @@ -125,9 +152,6 @@ int azure(struct system_config *); /* cloudinit.c */ int ec2(struct system_config *); int cloudinit(struct system_config *); -int tryendpoint(struct system_config *, - int (fetch)(struct system_config *), - int (next)(struct system_config *)); /* opennebula.c */ int opennebula(struct system_config *); diff --git a/agent/opennebula.c b/agent/opennebula.c index 8395920..69d36e0 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -40,12 +40,15 @@ opennebula(struct system_config *sc) int ret = -1; unsigned short unit; + if (sc->sc_state == STATE_INIT) { + sc->sc_state = STATE_169; + return (-1); + } + /* Return silently without error */ if ((fp = fopen("/mnt/context.sh", "r")) == NULL) goto done; - sc->sc_stack = "opennebula"; - while ((line = fparseln(fp, &len, &lineno, delim, FPARSELN_UNESCALL)) != NULL) { /* key */ diff --git a/agent/openstack.c b/agent/openstack.c index c60acd1..9088a1a 100644 --- a/agent/openstack.c +++ b/agent/openstack.c @@ -31,11 +31,14 @@ static int openstack_fetch(struct system_config *); - int openstack(struct system_config *sc) { - return tryendpoint(sc, openstack_fetch, cloudinit); + if (sc->sc_state == STATE_INIT) { + sc->sc_state = STATE_DHCP; + return (-1); + } + return openstack_fetch(sc); } static int @@ -53,7 +56,6 @@ openstack_fetch(struct system_config *sc) if ((json = metadata(sc, "/openstack/latest/meta_data.json", TEXT)) == NULL) goto fail; - sc->sc_stack = "openstack"; if ((j = json_parse(json, strlen(json))) == NULL) goto fail; From c968f169fd46705d57b0d513a5350cf112b46ffe Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 11 Jun 2019 12:23:36 +0200 Subject: [PATCH 41/48] Tweak previous, sync cloud-agent.md --- agent/main.c | 3 ++- cloud-agent.md | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/agent/main.c b/agent/main.c index ed44b3a..70fd381 100644 --- a/agent/main.c +++ b/agent/main.c @@ -1110,7 +1110,8 @@ dhcp_getendpoint(struct system_config *sc) sc->sc_addr.ip = sc->sc_endpoint; sc->sc_addr.family = 4; - log_debug("%s: %s", __func__, ep); + if (log_getverbose() > 2) + log_debug("%s: %s", __func__, ep); return (0); } diff --git a/cloud-agent.md b/cloud-agent.md index 4ef3ea3..28a1a2a 100644 --- a/cloud-agent.md +++ b/cloud-agent.md @@ -8,6 +8,7 @@ CLOUD-AGENT(8) - System Manager's Manual **cloud-agent** \[**-nuv**] +\[**-c** *cloud*\[,*cloud*...]] \[**-p** *length*] \[**-r** *rootdisk*] \[**-t** *timeout*] @@ -19,10 +20,40 @@ CLOUD-AGENT(8) - System Manager's Manual The **cloud-agent** program manages the OpenBSD provisioning and VM interaction in cloud -environments, including Microsoft Azure and Amazon AWS. +environments. The options are as follows: +**-c** *cloud*\[,*cloud*...] + +> Probe a list of cloud stacks for provisioning in the specified order. +> If this option is not specified, +> **cloud-agent** +> tries to detect the environment and possible cloud stacks automatically. +> Supported +> *cloud* +> stacks are: + +> **azure** + +> > Microsoft Azure + +> **cloudinit** + +> > Generic cloud-init + +> **ec2** + +> > Amazon AWS EC2 + +> **opennebula** + +> > OpenNebula + +> **openstack** + +> > OpenStack + **-p** *length* > Generate and set a random password for the default user. @@ -116,4 +147,4 @@ vmd(8) Reyk Floeter <[reyk@openbsd.org](mailto:reyk@openbsd.org)> -OpenBSD 6.5 - June 5, 2019 +OpenBSD 6.5 - June 11, 2019 From fd7fa10f1b1e6604c967c1fb088197f69635e213 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 11 Jun 2019 12:29:08 +0200 Subject: [PATCH 42/48] Update copyright --- LICENSE.md | 6 +++--- agent/azure.c | 2 +- agent/cloud-agent.8 | 2 +- agent/cloudinit.c | 2 +- agent/main.c | 2 +- agent/main.h | 2 +- agent/opennebula.c | 2 +- agent/openstack.c | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 42ee86d..d52a0ca 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -6,10 +6,10 @@ License * `cloud-agent` is free software under OpenBSD's ISC-style license. * Most of the code has been written by Reyk Floeter -* The http.[ch] files have been written by Kristaps Dzonsons +* The {http,json}.[ch] files were written by Kristaps Dzonsons * Please refer to the individual source files for other copyright holders! -> Copyright (c) 2017 Reyk Floeter +> Copyright (c) 2017, 2018, 2019 Reyk Floeter > > Permission to use, copy, modify, and distribute this software for any > purpose with or without fee is hereby granted, provided that the above @@ -26,5 +26,5 @@ License `cms/` ------ -* The CMS code is from the OpenSSL and/or LibreSSL. +* The CMS code is from OpenSSL and/or LibreSSL. * Please refer to the individual source files for other copyright holders! diff --git a/agent/azure.c b/agent/azure.c index c880f9d..1dd5f9f 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Reyk Floeter + * Copyright (c) 2017, 2018, 2019 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index 5f01cf0..f70de8f 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -1,6 +1,6 @@ .\" $OpenBSD: mdoc.template,v 1.15 2014/03/31 00:09:54 dlg Exp $ .\" -.\" Copyright (c) 2017 Reyk Floeter +.\" Copyright (c) 2017, 2018, 2019 Reyk Floeter .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 29582b4..f063289 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Reyk Floeter + * Copyright (c) 2017, 2018, 2019 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/agent/main.c b/agent/main.c index 70fd381..5600c56 100644 --- a/agent/main.c +++ b/agent/main.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Reyk Floeter + * Copyright (c) 2017, 2018, 2019 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/agent/main.h b/agent/main.h index b54b789..7cf78d0 100644 --- a/agent/main.h +++ b/agent/main.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Reyk Floeter + * Copyright (c) 2017, 2018, 2019 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/agent/opennebula.c b/agent/opennebula.c index 69d36e0..74c3fb9 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Reyk Floeter + * Copyright (c) 2018, 2019 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/agent/openstack.c b/agent/openstack.c index 9088a1a..3dbb8c6 100644 --- a/agent/openstack.c +++ b/agent/openstack.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Reyk Floeter + * Copyright (c) 2018, 2019 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above From 39f33c76a43f3ecf24121b16f3d681fb8f7d8bd4 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 11 Jun 2019 15:11:28 +0200 Subject: [PATCH 43/48] Add Changelog, tweak README.md --- CHANGELOG.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 20 ++++++++++++----- 2 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a78e71f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +## v0.9 (unreleased) + +* Added support for `-c` to specify the probing order of different cloud stacks. +* Added support for OpenNebula's START_SCRIPT methods (user-data alike). +* Added support for generating a default user password and writing it into + `~/.ssh/authorized_keys`. +* Fixed handling of OpenNebula SSH_PUBLIC_KEY entries with multiple keys. +* Improved documentation, added `CHANGELOG.md` file. + +## v0.8 (2019-06-02) + +* Added support for growing the root disk and its last partition (optional). +* Fixed OpenStack support. +* Fixed compilation with LibreSSL on OpenBSD 6.5 or newer. +* Fixed probing order and OpenStack with `169.254.169.254` as the endpoint IP. +* Improved OpenNebula support. + +## v0.7 (2018-08-15) + +* Added initial support for OpenNebula contextualization. +* Added support for setting a custom login user or "root" with `-U`. +* Added support for writing `resolv.conf` and static network configuration. +* Fixed the generated pf rule that is loaded during cloud-agent operation. + +## v0.6 (2018-05-15) + +* Fixed compilation with (old) OpenSSL releases. + +--- + +## v0.5 (2018-05-08) + +* Fixed the user-data script by loading it from /etc/rc.user-data. + +## v0.4 (2018-05-08) + +* Added support for user-data that is not base64-encoded. + +## v0.3 (2018-05-08) + +* Added support for user-data scripts. +* Make the public key optional for stacks that supply a password (e.g. Azure). + +## v0.2.2 (2018-05-07) + +* Fixed issues in the v0.2.1 release. + +## v0.2.2 (2018-05-07) + +* Fixed issues in the v0.2 release. + +## v0.2 (2018-01-10) + +* Added support for OpenStack and its JSON-based meta data. +* Added support for Apache CloudStack. +* Try to get meta data from `dhcp-server-identifier` instead of + `169.254.169.254`. + +## v0.1 (2017-07-03) + +* Initial release with support for Microsoft Azure and Amazon AWS EC2. diff --git a/README.md b/README.md index 0836734..942810f 100644 --- a/README.md +++ b/README.md @@ -33,20 +33,30 @@ automatically. * On Amazon AWS, create a file `/etc/hostname.xnf0` -* On Exoscale, create a file `/etc/hostname.vio0` +* On CloudStack, such as Exoscale, create a file `/etc/hostname.vio0` * On OpenBSD VMM (with meta-data), create a file `/etc/hostname.vio0` * On OpenStack/VMware, create a file `/etc/hostname.vmx0` -* On OpenNebula, create a file `/etc/hostname.if` - where _if_ is the name of your primary interface. - -* The content of the file is identical for all of them: +* The content of the file is identical for all of the above: dhcp !/usr/local/libexec/cloud-agent "\$if" +* On OpenNebula, such as Data Center Light, create a file `/etc/hostname.if` + where _if_ is the name of your primary interface. + The `dhcp` line should be ommitted in the file: + + !/usr/local/libexec/cloud-agent "\$if" + +Releases +-------- + +See the [Changelog](CHANGELOG.md) for a summary of changes and +download the releases from the +[release page](https://github.com/reyk/cloud-agent/releases). + Author ------ From e800a2b7d2ff74f3d0934fe9d20e4fcb717dd987 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 14 Jun 2019 09:45:48 +0200 Subject: [PATCH 44/48] Add support OpenNebula's USERNAME --- agent/opennebula.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/agent/opennebula.c b/agent/opennebula.c index 74c3fb9..a185f3d 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -35,7 +35,7 @@ opennebula(struct system_config *sc) const char *delim = "\\\\\0", *errstr; char *line = NULL, *k, *v, *p, q; char *value = NULL, *next = NULL, *last; - char *hname = NULL; + char *hname = NULL, *uname = NULL; size_t len, lineno = 0, i; int ret = -1; unsigned short unit; @@ -241,6 +241,13 @@ opennebula(struct system_config *sc) /* We will detect and decode base64 later */ if ((sc->sc_userdata = strdup(v)) == NULL) log_warnx("failed to set userdata"); + } else if (strcasecmp("USERNAME", k) == 0) { + if ((uname = strdup(v)) == NULL) + log_warnx("failed to set username"); + else { + free(sc->sc_username); + sc->sc_username = uname; + } } free(line); From c4595717fbc042f1eea009ac12188aeccdb14dcf Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 26 Jun 2019 14:42:12 +0200 Subject: [PATCH 45/48] Finalize version 0.9 --- CHANGELOG.md | 3 ++- cloud-agent.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a78e71f..9802c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog -## v0.9 (unreleased) +## v0.9 (2019-06-26) * Added support for `-c` to specify the probing order of different cloud stacks. * Added support for OpenNebula's START_SCRIPT methods (user-data alike). +* Added support for OpenNebula's USERNAME method. * Added support for generating a default user password and writing it into `~/.ssh/authorized_keys`. * Fixed handling of OpenNebula SSH_PUBLIC_KEY entries with multiple keys. diff --git a/cloud-agent.md b/cloud-agent.md index 28a1a2a..52327a2 100644 --- a/cloud-agent.md +++ b/cloud-agent.md @@ -147,4 +147,4 @@ vmd(8) Reyk Floeter <[reyk@openbsd.org](mailto:reyk@openbsd.org)> -OpenBSD 6.5 - June 11, 2019 +OpenBSD 6.5 - June 26, 2019 From 0b1fee287c1f2e3b01cd284c9d3d792d35b21372 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 28 Jun 2019 16:07:06 +0200 Subject: [PATCH 46/48] I forgot -c in the usage, will be part of the next release --- agent/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/main.c b/agent/main.c index 5600c56..6b15b6a 100644 --- a/agent/main.c +++ b/agent/main.c @@ -1121,7 +1121,8 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-nuv] [-p length] [-r rootdisk] " + fprintf(stderr, "usage: %s [-nuv] " + "[-c cloud[,...]] [-p length] [-r rootdisk]\n\t" "[-t 3] [-U puffy] interface\n", __progname); exit(1); } From beef9f736c304a81a6119661e8f6d66ae180daf6 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 29 Nov 2019 17:06:39 +0000 Subject: [PATCH 47/48] Append /etc/resolv.conf.tail if it exists --- CHANGELOG.md | 5 +++++ agent/main.c | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9802c72..00ad555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v1.0 (2019-11-29) + +* Append `/etc/resolv.conf.tail` to `/etc/resolv.conf` if it exists. +* Fixed usage. + ## v0.9 (2019-06-26) * Added support for `-c` to specify the probing order of different cloud stacks. diff --git a/agent/main.c b/agent/main.c index 6b15b6a..2c83270 100644 --- a/agent/main.c +++ b/agent/main.c @@ -838,7 +838,7 @@ agent_network(struct system_config *sc) const char *family; char domain[(NI_MAXHOST + 1) * 6 + 8]; /* up to 6 domains */ int has_domain = 0; - char ifidx[UINT16_MAX]; + char ifidx[UINT16_MAX], *str; const char *comment = "# Generated by cloud-agent"; if (!sc->sc_network) @@ -907,6 +907,12 @@ agent_network(struct system_config *sc) if (has_domain) fileout(domain, "a", "/etc/resolv.conf"); + /* append resolv.conf.tail if it exists */ + if ((str = filein("r", "/etc/resolv.conf.tail")) != NULL) { + fileout(str, "a", "/etc/resolv.conf"); + free(str); + } + return (0); } From 14d0149cdbe2753cd13e73a3fd2dd0715080484d Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 29 Nov 2019 17:22:07 +0000 Subject: [PATCH 48/48] CMS is back! Use it in LibreSSL > 3.0.2 or with USE_LIBRESSL_CMS=1 --- Makefile | 5 ++++- agent/azure.c | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0f27d6e..893159f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ # # The Azure agents needs CMS to obtain the SSH public keys. # LibreSSL has removed CMS, so either use OpenSSL to decrypt CMS -# messages or compile the old CMS code for LibreSSL. +# messages or compile the old CMS code for LibreSSL. Or use +# CMS that has returned to newer versions of LibreSSL. # .ifdef USE_OPENSSL MAKE_FLAGS+= USE_OPENSSL=1 +.elifdef USE_LIBRESSL_CMS +MAKE_FLAGS+= USE_LIBRESSL_CMS=1 .else SUBDIR= cms .endif diff --git a/agent/azure.c b/agent/azure.c index 1dd5f9f..9b451aa 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -26,6 +26,8 @@ #include #include +#include + #include "main.h" #include "http.h" #include "xml.h" @@ -441,13 +443,16 @@ azure_certificates(struct system_config *sc) fd = disable_output(sc, STDERR_FILENO); -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) /* * XXX Now comes the part that needs CMS which is only * XXX present in OpenSSL but got removed from LibreSSL. */ log_debug("%s: running openssl cms", __func__); if (shell("/usr/local/bin/eopenssl", "cms", /* )) */ +#elif defined(USE_LIBRESSL_CMS) || LIBRESSL_VERSION_NUMBER > 0x3000200fL + /* And CMS returned to LibreSSL! */ + if (shell("/usr/bin/openssl", "cms", /* )) */ #else if (shell("/usr/local/bin/cms", #endif