diff --git a/agent/azure.c b/agent/azure.c index 40efd03..990d973 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "main.h" @@ -719,7 +720,7 @@ azure_getovfenv(struct system_config *sc) } if ((xe = xml_findl(&xp->xe_head, "UserPassword", NULL)) != NULL) { - if ((sc->sc_password = calloc(1, 128)) == NULL) { + if ((sc->sc_password_hash = calloc(1, _PASSWORD_LEN)) == NULL) { log_debug("%s: password failed", __func__); goto done; } @@ -727,13 +728,14 @@ azure_getovfenv(struct system_config *sc) str = strndup(xe->xe_data, xe->xe_datalen); if (str == NULL || crypt_newhash(str, "bcrypt,a", - sc->sc_password, 128) != 0) { + sc->sc_password_hash, _PASSWORD_LEN) != 0) { log_debug("%s: password hashing failed", __func__); - free(sc->sc_password); - sc->sc_password = NULL; + free(sc->sc_password_hash); + sc->sc_password_hash = NULL; free(str); goto done; } + explicit_bzero(str, xe->xe_datalen); free(str); /* Replace unencrypted password with hash */ @@ -743,11 +745,11 @@ azure_getovfenv(struct system_config *sc) /* Update element for xml_print() below */ explicit_bzero(xe->xe_data, xe->xe_datalen); free(xe->xe_data); - xe->xe_data = strdup(sc->sc_password); - xe->xe_datalen = strlen(sc->sc_password); + xe->xe_data = strdup(sc->sc_password_hash); + xe->xe_datalen = strlen(sc->sc_password_hash); } else if ((xe = xml_findl(&xp->xe_head, "UserPasswordHash", NULL)) != NULL) { - if ((sc->sc_password = + if ((sc->sc_password_hash = get_word(xe->xe_data, xe->xe_datalen)) != NULL) { log_debug("%s: password hash failed", __func__); goto done; diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index 0cec012..b6990f3 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -23,6 +23,7 @@ .Sh SYNOPSIS .Nm cloud-agent .Op Fl nuv +.Op Fl p Ar length .Op Fl r Ar rootdisk .Op Fl t Ar timeout .Op Fl U Ar username @@ -35,6 +36,17 @@ environments, including Microsoft Azure and Amazon AWS. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl p Ar length +Generate and set a random password for the default user. +The password will be written in its plain form into the +.Pa ~/.ssh/authorized_keys +file. +This allows to use the +.Xr doas 1 +command to gain root privileges. +The minimum +.Ar length +is 8 characters and the default is an empty password. .It Fl n Do not configure the system and skip the provisioning step. .It Fl t Ar timeout @@ -78,6 +90,8 @@ dhcp .Ed .Sh FILES .Bl -tag -width "/usr/local/libexec/cloud-agentX" -compact +.It Pa ~/.ssh/authorized_keys +The location of the agent-configured SSH public keys and optional password. .It Pa /usr/local/libexec/cloud-agent The agent itself. .It Pa /usr/local/bin/cms diff --git a/agent/main.c b/agent/main.c index 2ac73b1..e4c30cd 100644 --- a/agent/main.c +++ b/agent/main.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include "main.h" @@ -360,13 +361,19 @@ agent_free(struct system_config *sc) log_debug("%s: unmounted %s", __func__, sc->sc_cdrom); } - free(sc->sc_args); + if (sc->sc_password_plain != NULL) { + /* XXX can be removed with calloc_conceal() post-6.6 */ + explicit_bzero(sc->sc_password_plain, + strlen(sc->sc_password_plain)); + free(sc->sc_password_plain); + } + free(sc->sc_password_hash); free(sc->sc_hostname); free(sc->sc_username); - free(sc->sc_password); free(sc->sc_userdata); free(sc->sc_endpoint); free(sc->sc_instance); + free(sc->sc_args); close(sc->sc_nullfd); while ((ssh = TAILQ_FIRST(&sc->sc_pubkeys))) { @@ -628,6 +635,7 @@ agent_pf(struct system_config *sc, int open) static int agent_configure(struct system_config *sc) { + char pwbuf[_PASSWORD_LEN + 2]; struct ssh_pubkey *ssh; char *str1, *str2; @@ -669,18 +677,30 @@ agent_configure(struct system_config *sc) } /* password */ - if (sc->sc_password == NULL) { + if (sc->sc_password_hash == NULL) { if (asprintf(&str2, "permit keepenv nopass %s as root\n" "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; } else { - if (shell("usermod", "-p", sc->sc_password, + if (shell("usermod", "-p", sc->sc_password_hash, sc->sc_username, NULL) != 0) log_warnx("password failed"); if (asprintf(&str2, "permit keepenv persist %s as root\n" "permit keepenv nopass root\n", sc->sc_username) == -1) str2 = NULL; + + /* write generated password as comment to authorized_keys */ + if (sc->sc_password_plain != NULL) { + snprintf(pwbuf, sizeof(pwbuf), "# %s", + sc->sc_password_plain); + if (fileout(pwbuf, "w", + "%s/%s/.ssh/authorized_keys", + strcmp("root", sc->sc_username) == 0 ? "" : "/home", + sc->sc_username) != 0) + log_warnx("password comment failed"); + explicit_bzero(pwbuf, sizeof(pwbuf)); + } } /* doas */ @@ -690,7 +710,7 @@ agent_configure(struct system_config *sc) free(str2); /* ssh configuration */ - if (sc->sc_password == NULL && !TAILQ_EMPTY(&sc->sc_pubkeys)) + if (sc->sc_password_hash == NULL && !TAILQ_EMPTY(&sc->sc_pubkeys)) str1 = "/PasswordAuthentication/" "s/.*/PasswordAuthentication no/"; else @@ -1096,8 +1116,8 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-nuv] [-r rootdisk] [-t 3] [-U puffy] " - "interface\n", __progname); + fprintf(stderr, "usage: %s [-nuv] [-p length] [-r rootdisk] " + "[-t 3] [-U puffy] interface\n", __progname); exit(1); } @@ -1131,23 +1151,65 @@ get_args(int argc, char *const *argv) return (args); } +static char * +pwgen(size_t len, char **hash) +{ + char *password; + size_t i, alphabet_len; + const char *alphabet = + "0123456789_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + alphabet_len = strlen(alphabet); + *hash = NULL; + + /* XXX use calloc_conceal() post-6.6 */ + if ((password = calloc(1, len + 1)) == NULL) + return (NULL); + + /* Simple password generator */ + for (i = 0; i < len; i++) + password[i] = alphabet[arc4random_uniform(alphabet_len)]; + + if ((*hash = calloc(1, _PASSWORD_LEN)) == NULL) { + freezero(password, len + 1); + return (NULL); + } + + if (crypt_newhash(password, "bcrypt,a", + *hash, _PASSWORD_LEN) != 0) { + freezero(password, len + 1); + freezero(*hash, _PASSWORD_LEN); + password = NULL; + *hash = NULL; + } + + return (password); +} + int main(int argc, char *const *argv) { struct system_config *sc; int verbose = 0, dryrun = 0, unconfigure = 0; - int ch, ret, timeout = CONNECT_TIMEOUT; + int genpw = 0, ch, ret, timeout = CONNECT_TIMEOUT; const char *error = NULL, *rootdisk = NULL; char *args, *username = NULL; if ((args = get_args(argc, argv)) == NULL) fatalx("failed to save args"); - while ((ch = getopt(argc, argv, "nr:t:U:uv")) != -1) { + while ((ch = getopt(argc, argv, "np:r:t:U:uv")) != -1) { switch (ch) { case 'n': dryrun = 1; break; + case 'p': + genpw = strtonum(optarg, 8, 8192, &error); + if (error != NULL) + fatalx("invalid password length: %s", error); + break; case 'r': rootdisk = optarg; break; @@ -1217,6 +1279,19 @@ main(int argc, char *const *argv) else ret = openstack(sc); + if (genpw) { + if (sc->sc_password_hash != NULL) + log_debug("%s: user password hash: %s", __func__, + sc->sc_password_hash); + else if ((sc->sc_password_plain = pwgen(genpw, + &sc->sc_password_hash)) != NULL) { + if (log_getverbose() > 2) + log_debug("%s: generated password: %s", + __func__, sc->sc_password_plain); + } else + log_warnx("failed to generate password"); + } + /* Debug userdata */ if (sc->sc_dryrun && sc->sc_userdata) { if (agent_userdata(sc, sc->sc_userdata, diff --git a/agent/main.h b/agent/main.h index 3ae86f7..93b5f92 100644 --- a/agent/main.h +++ b/agent/main.h @@ -72,7 +72,8 @@ struct system_config { char *sc_hostname; char *sc_username; - char *sc_password; + char *sc_password_plain; + char *sc_password_hash; char *sc_pubkey; char *sc_userdata; char *sc_endpoint;