Add initial support for OpenNebula contextualization.

Thanks to datacenterlight.ch by ungleich glarus AG for providing
access to their OpenNebula-based cloud.
This commit is contained in:
reykfloeter 2018-08-13 19:30:41 +02:00
parent ec87db177d
commit d9899d488a
8 changed files with 449 additions and 25 deletions

View File

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

View File

@ -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 <bsd.prog.mk>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2017, 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
@ -15,19 +15,24 @@
*/
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <limits.h>
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
@ -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 <<EOF\n"
"#############################################################\n"
@ -658,6 +788,55 @@ agent_userdata(const unsigned char *userdata, size_t len)
return (ret);
}
static int
agent_network(struct system_config *sc)
{
struct net_addr *net;
char ift[16], ifname[16], line[1024], path[PATH_MAX];
const char *family;
if (!sc->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);

View File

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

209
agent/opennebula.c Normal file
View File

@ -0,0 +1,209 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fnmatch.h>
#include <fcntl.h>
#include <ctype.h>
#include <sha2.h>
#include <err.h>
#include <util.h>
#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);
}

View File

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