Support for openstack meta_data.json, connect timeout, other fixes.

This commit is contained in:
Reyk Floeter 2018-01-10 14:13:49 +01:00
parent eb9d5b440c
commit ac66f160e0
7 changed files with 279 additions and 55 deletions

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -26,16 +26,20 @@
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <err.h>
#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);

View file

@ -19,10 +19,14 @@
#include <sys/queue.h>
#include <sys/cdefs.h>
#include <sys/socket.h>
#include <stdarg.h>
#include <stddef.h>
#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);

101
agent/openstack.c Normal file
View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2018 Reyk Floeter <reyk@openbsd.org>
*
* 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 <sys/queue.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#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);
}