From e13513543aa1b537f79ab3fc02ddac6093321ba4 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Mon, 10 Oct 2022 11:14:23 -0700 Subject: [PATCH 01/10] CURL isn't used in this project - cleanup. --- tests/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index a49c8c9..f39c159 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -11,8 +11,8 @@ check_PROGRAMS = check_LTLIBRARIES = libtest.la COMMON_CFLAGS = -std=gnu99 -I$(top_srcdir)/src -I$(top_srcdir)/src/ccmodules \ -I$(top_srcdir)/src/interpreters \ - $(CHECK_FLAGS) $(GLIB_CFLAGS) $(CURL_CFLAGS) $(YAML_CFLAGS) $(BLKID_CFLAGS) $(PARTED_CFLAGS) -COMMON_LDADD = $(CHECK_LIBS) $(GLIB_LIBS) $(CURL_LIBS) $(YAML_LIBS) $(BLKID_LIBS) $(PARTED_LIBS) + $(CHECK_FLAGS) $(GLIB_CFLAGS) $(YAML_CFLAGS) $(BLKID_CFLAGS) $(PARTED_CFLAGS) +COMMON_LDADD = $(CHECK_LIBS) $(GLIB_LIBS) $(YAML_LIBS) $(BLKID_LIBS) $(PARTED_LIBS) libtest_la_SOURCES = \ ../src/lib.c \ From 9bd1c89e88e56089f6bb0fc147ccb256167a5328 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Mon, 10 Oct 2022 11:40:02 -0700 Subject: [PATCH 02/10] Fix data fetcher not being able to provision multiple SSH keys The data fetcher tool operated assuming the key file presented on the server contains only 1 key. However, several cloud providers use this file to present multiple keys to the client systems at provisioning time and this should be functional. Without this change, only the first key will be properly inserted into yaml, and all secondary keys will result in a yaml failure or be ignored entirely. It would look something like this: ``` ssh_authorized_keys: - ssh-rsa ssh-rsa ssh-rsa ``` The template is modified to allow line-by-line reading of the key file from the server, and each line written will be prefixed with the yaml " - " entry prefix in the output file. When writing the SSH keys to our generated cloud-config file, make sure to follow with an extra line feed before we concatenate the user-data contents. Otherwise, we could end up including the first line of the user-data response in the authorized_keys file, e.g. ssh-rsa <...key...> user@host#cloud-config ^^^^^^^^^^^^^ Instead, worst case, /var/lib/cloud/-user-data might just have an extra (ignored) blank line. --- src/ucd-data-fetch.c | 52 ++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index c0da102..ad5b5d2 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -76,8 +76,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { "users:\n" \ " - name: clear\n" \ " groups: wheelnopw\n" \ - "ssh_authorized_keys:\n" \ - " - " + "ssh_authorized_keys:\n" }, { "oci", @@ -89,8 +88,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { " - name: opc\n" \ " groups: wheelnopw\n" \ " gecos: Oracle Public Cloud User\n" \ - "ssh_authorized_keys:\n" \ - " - " + "ssh_authorized_keys:\n" }, { "tencent", @@ -101,8 +99,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { "users:\n" \ " - name: tencent\n" \ " groups: wheelnopw\n" \ - "ssh_authorized_keys:\n" \ - " - " + "ssh_authorized_keys:\n" }, { "aliyun", @@ -113,8 +110,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { "users:\n" \ " - name: aliyun\n" \ " groups: wheelnopw\n" \ - "ssh_authorized_keys:\n" \ - " - " + "ssh_authorized_keys:\n" } }; @@ -162,31 +158,36 @@ static int parse_headers(FILE *f, size_t *cl) } /** - * write_lines() - write remaining data from stream f into out, while minding cl length + * write_lines() - write remaining lines from stream f into out, while minding cl length + * - if prefix != NULL, each line written is prefixed with the prefix. * - returns 0 on success, 1 on failure - * - after this call, the calue of `cl` outside the function is invalid. + * - after this call, the value of `cl` outside the function is invalid. */ -static int write_lines(int out, FILE *f, size_t cl) +static int write_lines(int out, FILE *f, size_t cl, const char *prefix) { for (;;) { if (cl == 0) { return 0; } - size_t len; - char buf[512] = {0}; + char buf[2048] = {0}; - len = (cl > sizeof(buf)) ? sizeof(buf) : cl; - - size_t r = fread(buf, 1, len, f); + char *r = fgets(buf, sizeof(buf), f); if (ferror(f)) { return 1; - } else if (r == 0) { + } else if (!r) { return 0; } - cl -= r; - if (write(out, buf, r) < (ssize_t)r) { + size_t len = strlen(r); + cl -= len; + + if (prefix) { + if (write(out, prefix, strlen(prefix)) < (ssize_t)strlen(prefix)) + return 1; + } + + if (write(out, buf, len) < (ssize_t)len) { return 1; } } @@ -298,13 +299,22 @@ int main(int argc, char *argv[]) { FAIL("write()"); } - if (write_lines(out, f, cl) != 0) { + /* Write out SSH keys */ + if (write_lines(out, f, cl, " - ") != 0) { close(out); fclose(f); unlink(outpath); FAIL("write_lines()"); } + /* Write an extra linefeed in case this didn't end with one */ + if (write(out, "\n", 1) < (ssize_t) 1) { + close(out); + fclose(f); + unlink(outpath); + FAIL("write()"); + } + /* reopen socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { @@ -357,7 +367,7 @@ int main(int argc, char *argv[]) { } /* don't write part #2 if 404 or some non-error */ - if ((result != 2) && (write_lines(out, f, cl) != 0)) { + if ((result != 2) && (write_lines(out, f, cl, NULL) != 0)) { close(out); fclose(f); unlink(outpath); From 9fb582c54a83a70286e5194675859a3be4d207c2 Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Fri, 14 Oct 2022 11:51:17 -0700 Subject: [PATCH 03/10] Adopt an instance service for providers Instead of creating a unique service file for each provider, just use a single instance service. We'll change the symlinks in packaging. --- Makefile.am | 6 ++---- configure.ac | 5 +---- data/ucd-aliyun.service.in | 17 ----------------- data/ucd-oci.service.in | 17 ----------------- data/ucd-tencent.service.in | 17 ----------------- data/{ucd-aws.service.in => ucd@.service.in} | 6 +++--- 6 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 data/ucd-aliyun.service.in delete mode 100644 data/ucd-oci.service.in delete mode 100644 data/ucd-tencent.service.in rename data/{ucd-aws.service.in => ucd@.service.in} (68%) diff --git a/Makefile.am b/Makefile.am index 2a4b961..daefd26 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,7 @@ EXTRA_DIST = \ LICENSE \ COPYING \ data/ucd.service.in \ + data/ucd@.service.in \ docs/ucd.1.md \ docs/ucd-data-fetch.1.md \ docs/cloud-config.5.md @@ -87,10 +88,7 @@ ucd_data_fetch_CFLAGS = $(AM_CFLAGS) SYSTEMD_DIR=$(prefix)/lib/systemd/system/ systemdsystemunitdir = @SYSTEMD_SYSTEMUNITDIR@ systemdsystemunit_DATA = data/ucd.service \ - data/ucd-aws.service \ - data/ucd-oci.service \ - data/ucd-tencent.service \ - data/ucd-aliyun.service + data/ucd@.service systemdsystemunit-install-local: mkdir -p $(DESTDIR)$(systemdsystemunitdir)/multi-user.target.wants/ diff --git a/configure.ac b/configure.ac index 8542e81..c2c7063 100644 --- a/configure.ac +++ b/configure.ac @@ -8,10 +8,7 @@ AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_FILES([Makefile tests/Makefile data/ucd.service - data/ucd-aws.service - data/ucd-oci.service - data/ucd-tencent.service - data/ucd-aliyun.service]) + data/ucd@.service]) AC_CONFIG_HEADERS([config.h]) LT_INIT diff --git a/data/ucd-aliyun.service.in b/data/ucd-aliyun.service.in deleted file mode 100644 index 33c4264..0000000 --- a/data/ucd-aliyun.service.in +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=micro-config-drive job for Aliyun -After=network.target systemd-networkd.service -Wants=local-fs.target sshd.service sshd-keygen.service -ConditionPathExists=!/var/lib/cloud/aliyun-user-data - -[Service] -Type=oneshot -ExecStart=@prefix@/bin/ucd-data-fetch aliyun -RemainAfterExit=yes -TimeoutSec=0 - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=multi-user.target diff --git a/data/ucd-oci.service.in b/data/ucd-oci.service.in deleted file mode 100644 index cddb7b0..0000000 --- a/data/ucd-oci.service.in +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=micro-config-drive job for OCI -After=network.target systemd-networkd.service -Wants=local-fs.target sshd.service sshd-keygen.service -ConditionPathExists=!/var/lib/cloud/oci-user-data - -[Service] -Type=oneshot -ExecStart=@prefix@/bin/ucd-data-fetch oci -RemainAfterExit=yes -TimeoutSec=0 - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=multi-user.target diff --git a/data/ucd-tencent.service.in b/data/ucd-tencent.service.in deleted file mode 100644 index 776fb3d..0000000 --- a/data/ucd-tencent.service.in +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=micro-config-drive job for TENCENT -After=network.target systemd-networkd.service -Wants=local-fs.target sshd.service sshd-keygen.service -ConditionPathExists=!/var/lib/cloud/tencent-user-data - -[Service] -Type=oneshot -ExecStart=@prefix@/bin/ucd-data-fetch tencent -RemainAfterExit=yes -TimeoutSec=0 - -# Output needs to appear in instance console output -StandardOutput=journal+console - -[Install] -WantedBy=multi-user.target diff --git a/data/ucd-aws.service.in b/data/ucd@.service.in similarity index 68% rename from data/ucd-aws.service.in rename to data/ucd@.service.in index aaf4f4b..a4620a6 100644 --- a/data/ucd-aws.service.in +++ b/data/ucd@.service.in @@ -1,12 +1,12 @@ [Unit] -Description=micro-config-drive job for AWS +Description=micro-config-drive job for %I After=network.target systemd-networkd.service Wants=local-fs.target sshd.service sshd-keygen.service -ConditionPathExists=!/var/lib/cloud/aws-user-data +ConditionPathExists=!/var/lib/cloud/%I-user-data [Service] Type=oneshot -ExecStart=@prefix@/bin/ucd-data-fetch aws +ExecStart=@prefix@/bin/ucd-data-fetch %I RemainAfterExit=yes TimeoutSec=0 From 032a97cb97552199d7df59200e2f3606b6057d76 Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Mon, 10 Oct 2022 15:19:13 -0700 Subject: [PATCH 04/10] Allow use of hostname instead of IP address Some servers require the host name to be sent in the user-data API request. We can allow a host name instead of IP address in the config struct by doing a lookup if it's not an IP address already. --- src/ucd-data-fetch.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index ad5b5d2..f0b3d98 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -236,6 +237,18 @@ int main(int argc, char *argv[]) { server.sin_addr.s_addr = inet_addr(config[conf].ip); server.sin_port = htons(80); + /* Do we need to look up a hostname? */ + if ((int) server.sin_addr.s_addr == -1) { + struct hostent *hp = gethostbyname(config[conf].ip); + if (!hp || hp->h_length <= 0) { + FAIL("gethostbyname()"); + } + + /* Got it; use the resulting IP address */ + server.sin_family = (short unsigned int) (hp->h_addrtype & 0xFFFF); + memcpy(&(server.sin_addr.s_addr), hp->h_addr, (size_t) hp->h_length); + } + struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 50000000; From 03faef4ff6266d78bbf0afd52ff90dc1dd93b1fe Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Mon, 10 Oct 2022 11:57:49 -0700 Subject: [PATCH 05/10] Add equinix provisioning template To provision on equinix we can grab the `keys` file from their service. The address I entered in the template is tentative - it needs to be checked that metadata.platformequinix.com resolves to that address in their internal network as well that it's on a static IP address and not some RR load balancer service - ideally. --- src/ucd-data-fetch.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index f0b3d98..e9d8f4b 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -66,7 +66,7 @@ struct cloud_struct { char *cloud_config_header; }; -#define MAX_CONFIGS 4 +#define MAX_CONFIGS 5 static struct cloud_struct config[MAX_CONFIGS] = { { "aws", @@ -112,6 +112,16 @@ static struct cloud_struct config[MAX_CONFIGS] = { " - name: aliyun\n" \ " groups: wheelnopw\n" \ "ssh_authorized_keys:\n" + }, + { + "equinix", + "metadata.platformequinix.com", + "/2009-04-04/meta-data/public-keys", + NULL, + "#cloud-config\n" \ + " - name: clear\n" \ + " groups: wheelnopw\n" \ + "ssh_authorized_keys:\n" } }; From 2edf331bb382c7cf2b23821d9f77035b7c15fad1 Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Mon, 10 Oct 2022 15:50:37 -0700 Subject: [PATCH 06/10] Make sure fds are closed in error paths Don't close the socket until we're done reading. Make sure the socket fd/stream gets closed in all the error paths preceding our close/fclose. Also ensure the output file is closed properly in error cases. --- src/ucd-data-fetch.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index e9d8f4b..97e7e35 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -299,26 +299,29 @@ int main(int argc, char *argv[]) { size_t cl; int result = parse_headers(f, &cl); if (result != 1) { - close(sockfd); + fclose(f); FAIL("parse_headers()"); } - close(sockfd); - int out; (void) mkdir(USER_DATA_PATH, 0); if (asprintf(&outpath, "%s/%s-user-data", USER_DATA_PATH, config[conf].name) < 0) { + fclose(f); FAIL("asprintf()"); } (void) unlink(outpath); out = open(outpath, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (out < 0) { + fclose(f); FAIL("open()"); } /* Insert cloud-config header above SSH key. */ len = strlen(config[conf].cloud_config_header); if (write(out, config[conf].cloud_config_header, len) < (ssize_t)len) { + close(out); + fclose(f); + unlink(outpath); FAIL("write()"); } @@ -337,6 +340,7 @@ int main(int argc, char *argv[]) { unlink(outpath); FAIL("write()"); } + close(sockfd); /* reopen socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); From 50ce502935f412c0624bc52ac7392595f3031157 Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Tue, 11 Oct 2022 11:42:53 -0700 Subject: [PATCH 07/10] Fetch the user-configured user data for equinix Additional URI https://metadata.platformequinix.com/userdata Also add missing users key in generated equinix conf Our output for the equinix cloud config file omitted the "users:" key prior to defining the user. --- src/ucd-data-fetch.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index 97e7e35..c1d6320 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -117,8 +117,9 @@ static struct cloud_struct config[MAX_CONFIGS] = { "equinix", "metadata.platformequinix.com", "/2009-04-04/meta-data/public-keys", - NULL, + "/userdata", "#cloud-config\n" \ + "users:\n" \ " - name: clear\n" \ " groups: wheelnopw\n" \ "ssh_authorized_keys:\n" From d3ba7dc26a2c4360405336345ffaf36a19e43ef0 Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Tue, 11 Oct 2022 14:46:35 -0700 Subject: [PATCH 08/10] Support setting a TCP port for HTTP fetch --- src/ucd-data-fetch.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index c1d6320..c576bb0 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -61,6 +61,7 @@ struct cloud_struct { char *name; char *ip; + uint16_t port; char *request_sshkey_path; char *request_userdata_path; char *cloud_config_header; @@ -71,6 +72,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { { "aws", "169.254.169.254", + 80, "/latest/meta-data/public-keys/0/openssh-key", "/latest/user-data", "#cloud-config\n" \ @@ -82,6 +84,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { { "oci", "169.254.169.254", + 80, "/opc/v1/instance/metadata/ssh_authorized_keys", NULL, "#cloud-config\n" \ @@ -94,6 +97,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { { "tencent", "169.254.0.23", + 80, "/latest/meta-data/public-keys/0/openssh-key", NULL, "#cloud-config\n" \ @@ -105,6 +109,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { { "aliyun", "100.100.100.200", + 80, "/latest/meta-data/public-keys/0/openssh-key", NULL, "#cloud-config\n" \ @@ -116,6 +121,7 @@ static struct cloud_struct config[MAX_CONFIGS] = { { "equinix", "metadata.platformequinix.com", + 80, "/2009-04-04/meta-data/public-keys", "/userdata", "#cloud-config\n" \ @@ -246,7 +252,7 @@ int main(int argc, char *argv[]) { memset(&server, 0, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(config[conf].ip); - server.sin_port = htons(80); + server.sin_port = htons(config[conf].port); /* Do we need to look up a hostname? */ if ((int) server.sin_addr.s_addr == -1) { From 4ab8a9a2a7925f9a6dfba0297f75031273bf60ef Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Fri, 14 Oct 2022 10:47:03 -0700 Subject: [PATCH 09/10] Add a fetch test To improve package test capabilities, introduce a test target that will attempt to fetch SSH keys and user data from a locally-spawned test server. Add other bits in ucd-data-fetch.c to support testing. Add fetch_test, which spawns an HTTP server to serve some sample user-data and cloud-config files, and verify that ucd-data-fetch can retrieve them. --- src/ucd-data-fetch.c | 30 +++++++++++++++++++++++++++--- tests/Makefile.am | 11 +++++++++-- tests/fetch_data/expected | 17 +++++++++++++++++ tests/fetch_data/public-keys | 3 +++ tests/fetch_data/user-data | 8 ++++++++ tests/fetch_test | 23 +++++++++++++++++++++++ 6 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 tests/fetch_data/expected create mode 100644 tests/fetch_data/public-keys create mode 100644 tests/fetch_data/user-data create mode 100755 tests/fetch_test diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index c576bb0..4aa3517 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -67,7 +67,7 @@ struct cloud_struct { char *cloud_config_header; }; -#define MAX_CONFIGS 5 +#define MAX_CONFIGS 6 static struct cloud_struct config[MAX_CONFIGS] = { { "aws", @@ -129,6 +129,18 @@ static struct cloud_struct config[MAX_CONFIGS] = { " - name: clear\n" \ " groups: wheelnopw\n" \ "ssh_authorized_keys:\n" + }, + { + "test", + "127.0.0.254", + 8123, + "/public-keys", + "/user-data", + "#cloud-config\n" \ + "users:\n" \ + " - name: clear\n" \ + " groups: wheelnopw\n" \ + "ssh_authorized_keys:\n" } }; @@ -316,6 +328,13 @@ int main(int argc, char *argv[]) { fclose(f); FAIL("asprintf()"); } + /* Special case for testing -- can't use/don't need privileged directory */ + if (0 == strcmp(config[conf].name, "test")) { + if (asprintf(&outpath, "%s-user-data", config[conf].name) < 0) { + fclose(f); + FAIL("asprintf()"); + } + } (void) unlink(outpath); out = open(outpath, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (out < 0) { @@ -414,6 +433,11 @@ int main(int argc, char *argv[]) { finish: - (void) execl(BINDIR "/ucd", BINDIR "/ucd", "-u", outpath, (char *)NULL); - FAIL("exec()"); + /* Don't run ucd for the test template */ + if (strcmp(config[conf].name, "test") != 0) { + (void) execl(BINDIR "/ucd", BINDIR "/ucd", "-u", outpath, (char *)NULL); + FAIL("exec()"); + } + + return 0; } diff --git a/tests/Makefile.am b/tests/Makefile.am index f39c159..6e25ac9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,7 +6,10 @@ ACLOCAL_AMFLAGS = -I m4 TESTS = +EXTRA_DIST = + check_PROGRAMS = +check_SCRIPTS = check_LTLIBRARIES = libtest.la COMMON_CFLAGS = -std=gnu99 -I$(top_srcdir)/src -I$(top_srcdir)/src/ccmodules \ @@ -45,14 +48,18 @@ lib_test_SOURCES = lib_test.c lib_test_CFLAGS = $(COMMON_CFLAGS) $(AM_CFLAGS) lib_test_LDADD = libtest.la $(COMMON_LDADD) TESTS += lib_test +check_PROGRAMS += lib_test userdata_test_SOURCES = userdata_test.c userdata_test_CFLAGS = $(COMMON_CFLAGS) $(AM_CFLAGS) userdata_test_LDADD = libtest.la $(COMMON_LDADD) TESTS += userdata_test +check_PROGRAMS += userdata_test - -check_PROGRAMS += $(TESTS) +# fetch_test is a shell script +TESTS += fetch_test +check_SCRIPTS += fetch_test +EXTRA_DIST += fetch_test fetch_data CLEANFILES = *~ *.log diff --git a/tests/fetch_data/expected b/tests/fetch_data/expected new file mode 100644 index 0000000..7b6b799 --- /dev/null +++ b/tests/fetch_data/expected @@ -0,0 +1,17 @@ +#cloud-config +users: + - name: clear + groups: wheelnopw +ssh_authorized_keys: + - SSH_TEST_KEY_STRING_1 + - SSH_TEST_KEY_STRING_2 + - SSH_TEST_KEY_STRING_3 + +#cloud-config + +users: + - name: test1 + groups: group1 + - name: test2 + groups: group2 + diff --git a/tests/fetch_data/public-keys b/tests/fetch_data/public-keys new file mode 100644 index 0000000..b6f8e61 --- /dev/null +++ b/tests/fetch_data/public-keys @@ -0,0 +1,3 @@ +SSH_TEST_KEY_STRING_1 +SSH_TEST_KEY_STRING_2 +SSH_TEST_KEY_STRING_3 diff --git a/tests/fetch_data/user-data b/tests/fetch_data/user-data new file mode 100644 index 0000000..7f7fb10 --- /dev/null +++ b/tests/fetch_data/user-data @@ -0,0 +1,8 @@ +#cloud-config + +users: + - name: test1 + groups: group1 + - name: test2 + groups: group2 + diff --git a/tests/fetch_test b/tests/fetch_test new file mode 100755 index 0000000..f0a9cff --- /dev/null +++ b/tests/fetch_test @@ -0,0 +1,23 @@ +#!/bin/bash -x + +set -euo pipefail +SCRIPT_PATH="$(dirname "$(readlink -f "${BASH_SOURCE}")")" + +# Launch a lightweight HTTP server and attempt to fetch cloud config from it +# Uses the "test" template in ucd-fetch-data + +cd "${SCRIPT_PATH}/fetch_data" +python -m http.server 8123 --bind 127.0.0.254 & +HTTP_PID=$! +trap "sleep 1; kill ${HTTP_PID}" EXIT +cd "${SCRIPT_PATH}" + +sleep 2 + +../ucd-data-fetch test + +# Compare what we got/generated with what we expect +cmp -b fetch_data/expected test-user-data + +# Cleanup the test data file +rm test-user-data From 95ee67aa5bcc2d0ed2252367d64fb646d3b3c35b Mon Sep 17 00:00:00 2001 From: "Brett T. Warden" Date: Mon, 17 Oct 2022 13:50:10 -0700 Subject: [PATCH 10/10] Wait up to 100 seconds for hostname lookup In case networking takes as long as a minute to come up, give hostname lookup up to 100 seconds to complete. Give initial HTTP request up to 120 seconds _from when we started_ to succeed, so even if we spent all our time waiting on DNS, we still give 20 more seconds for the HTTP server. --- src/ucd-data-fetch.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/ucd-data-fetch.c b/src/ucd-data-fetch.c index 4aa3517..6736dbc 100644 --- a/src/ucd-data-fetch.c +++ b/src/ucd-data-fetch.c @@ -266,22 +266,40 @@ int main(int argc, char *argv[]) { server.sin_addr.s_addr = inet_addr(config[conf].ip); server.sin_port = htons(config[conf].port); + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 50000000; + /* Do we need to look up a hostname? */ if ((int) server.sin_addr.s_addr == -1) { - struct hostent *hp = gethostbyname(config[conf].ip); - if (!hp || hp->h_length <= 0) { - FAIL("gethostbyname()"); - } + n = 0; + for (;;) { + struct hostent *hp = gethostbyname(config[conf].ip); + if (hp != NULL) { + if (hp->h_length > 0) { + /* Got it; use the resulting IP address */ + server.sin_family = (short unsigned int) (hp->h_addrtype & 0xFFFF); + memcpy(&(server.sin_addr.s_addr), hp->h_addr, (size_t) hp->h_length); + break; + } + else { + fprintf(stderr, "gethostbyname(): empty response"); + exit(EXIT_FAILURE); + } + } - /* Got it; use the resulting IP address */ - server.sin_family = (short unsigned int) (hp->h_addrtype & 0xFFFF); - memcpy(&(server.sin_addr.s_addr), hp->h_addr, (size_t) hp->h_length); + if ((h_errno != TRY_AGAIN) && (h_errno != NO_RECOVERY)) { + herror("gethostbyname()"); + exit(EXIT_FAILURE); + } + nanosleep(&ts, NULL); + if (++n > 2000) { /* 100 secs */ + herror("gethostbyname()"); + exit(EXIT_FAILURE); + } + } } - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 50000000; - for (;;) { int r = connect(sockfd, (struct sockaddr *)&server, sizeof(server)); if (r == 0) { @@ -291,7 +309,7 @@ int main(int argc, char *argv[]) { FAIL("connect()"); } nanosleep(&ts, NULL); - if (++n > 200) { /* 10 secs */ + if (++n > 2400) { /* 120 secs - any used up in gethostbyname */ FAIL("timeout in connect()"); } }