Support for openstack meta_data.json, connect timeout, other fixes.
This commit is contained in:
parent
eb9d5b440c
commit
ac66f160e0
7 changed files with 279 additions and 55 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
145
agent/main.c
145
agent/main.c
|
@ -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);
|
||||
|
|
31
agent/main.h
31
agent/main.h
|
@ -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
101
agent/openstack.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue