From ac66f160e04c4e96345b449eca97459d4f26602c Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 10 Jan 2018 14:13:49 +0100 Subject: [PATCH] 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); +}