Move JcloudsLocation's logic for creating users to separate class
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/c93a79de Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/c93a79de Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/c93a79de Branch: refs/heads/master Commit: c93a79de41d32616e5cec03d07632daefe0d28ca Parents: 2cbe309 Author: Sam Corbett <sam.corb...@cloudsoftcorp.com> Authored: Wed Dec 7 11:56:40 2016 +0000 Committer: Sam Corbett <sam.corb...@cloudsoftcorp.com> Committed: Tue Dec 20 15:00:26 2016 +0000 ---------------------------------------------------------------------- .../location/jclouds/CreateUserStatements.java | 303 ++++++++++++++ .../location/jclouds/JcloudsLocation.java | 405 ++++--------------- .../location/jclouds/JcloudsLocationTest.java | 6 +- 3 files changed, 393 insertions(+), 321 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c93a79de/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java new file mode 100644 index 0000000..d275fc1 --- /dev/null +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/CreateUserStatements.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.brooklyn.location.jclouds; + +import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth; + +import java.security.KeyPair; +import java.util.List; +import javax.annotation.Nullable; + +import org.apache.brooklyn.core.location.LocationConfigUtils; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.crypto.SecureKeys; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.functions.Sha512Crypt; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.scriptbuilder.domain.LiteralStatement; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.statements.login.AdminAccess; +import org.jclouds.scriptbuilder.statements.login.ReplaceShadowPasswordEntry; +import org.jclouds.scriptbuilder.statements.ssh.AuthorizeRSAPublicKeys; +import org.jclouds.scriptbuilder.statements.ssh.SshStatements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +public class CreateUserStatements { + + private static final Logger LOG = LoggerFactory.getLogger(CreateUserStatements.class); + + private final LoginCredentials createdUserCredentials; + private final List<Statement> statements; + + CreateUserStatements(LoginCredentials creds, List<Statement> statements) { + this.createdUserCredentials = creds; + this.statements = statements; + } + + public LoginCredentials credentials() { + return createdUserCredentials; + } + + public List<Statement> statements() { + return statements; + } + + /** + * Returns the commands required to create the user, to be used for connecting (e.g. over ssh) + * to the machine; also returns the expected login credentials. + * <p> + * The returned login credentials may be null if we haven't done any user-setup and no specific + * user was supplied (i.e. if {@code dontCreateUser} was true and {@code user} was null or blank). + * In which case, the caller should use the jclouds node's login credentials. + * <p> + * There are quite a few configuration options. Depending on their values, the user-creation + * behaves differently: + * <ul> + * <li>{@code dontCreateUser} says not to run any user-setup commands at all. If {@code user} is + * non-empty (including with the default value), then that user will subsequently be used, + * otherwise the (inferred) {@code loginUser} will be used. + * <li>{@code loginUser} refers to the existing user that jclouds should use when setting up the VM. + * Normally this will be inferred from the image (i.e. doesn't need to be explicitly set), but sometimes + * the image gets it wrong so this can be a handy override. + * <li>{@code user} is the username for brooklyn to subsequently use when ssh'ing to the machine. + * If not explicitly set, its value will default to the username of the user running brooklyn. + * <ul> + * <li>If the {@code user} value is null or empty, then the (inferred) {@code loginUser} will + * subsequently be used, setting up the password/authorizedKeys for that loginUser. + * <li>If the {@code user} is "root", then setup the password/authorizedKeys for root. + * <li>If the {@code user} equals the (inferred) {@code loginUser}, then don't try to create this + * user but instead just setup the password/authorizedKeys for the user. + * <li>Otherwise create the given user, setting up the password/authorizedKeys (unless + * {@code dontCreateUser} is set, obviously). + * </ul> + * <li>{@code publicKeyData} is the key to authorize (i.e. add to .ssh/authorized_keys), + * if not null or blank. Note the default is to use {@code ~/.ssh/id_rsa.pub} or {@code ~/.ssh/id_dsa.pub} + * if either of those files exist for the user running brooklyn. + * Related is {@code publicKeyFile}, which is used to populate publicKeyData. + * <li>{@code password} is the password to set for the user. If null or blank, then a random password + * will be auto-generated and set. + * <li>{@code privateKeyData} is the key to use when subsequent ssh'ing, if not null or blank. + * Note the default is to use {@code ~/.ssh/id_rsa} or {@code ~/.ssh/id_dsa}. + * The subsequent preferences for ssh'ing are: + * <ul> + * <li>Use the {@code privateKeyData} if not null or blank (including if using default) + * <li>Use the {@code password} (or the auto-generated password if that is blank). + * </ul> + * <li>{@code grantUserSudo} determines whether or not the created user may run the sudo command.</li> + * </ul> + * + * @param image The image being used to create the VM + * @param config Configuration for creating the VM + * @return The commands required to create the user, along with the expected login credentials for that user, + * or null if we are just going to use those from jclouds. + */ + public static CreateUserStatements get(JcloudsLocation location, @Nullable Image image, ConfigBag config) { + //NB: private key is not installed remotely, just used to get/validate the public key + Preconditions.checkNotNull(location, "location argument required"); + String user = Preconditions.checkNotNull(location.getUser(config), "user required"); + final boolean isWindows = location.isWindows(image, config); + final String explicitLoginUser = config.get(JcloudsLocation.LOGIN_USER); + final String loginUser = groovyTruth(explicitLoginUser) + ? explicitLoginUser + : (image != null && image.getDefaultCredentials() != null) + ? image.getDefaultCredentials().identity + : null; + final boolean dontCreateUser = config.get(JcloudsLocation.DONT_CREATE_USER); + final boolean grantUserSudo = config.get(JcloudsLocation.GRANT_USER_SUDO); + final LocationConfigUtils.OsCredential credential = LocationConfigUtils.getOsCredential(config); + credential.checkNoErrors().logAnyWarnings(); + final String passwordToSet = + Strings.isNonBlank(credential.getPassword()) ? credential.getPassword() : Identifiers.makeRandomId(12); + final List<Statement> statements = Lists.newArrayList(); + LoginCredentials createdUserCreds = null; + + if (dontCreateUser) { + // dontCreateUser: + // if caller has not specified a user, we'll just continue to use the loginUser; + // if caller *has*, we set up our credentials assuming that user and credentials already exist + + if (Strings.isBlank(user)) { + // createdUserCreds returned from this method will be null; + // we will use the creds returned by jclouds on the node + LOG.info("Not setting up user {} (subsequently using loginUser {})", user, loginUser); + config.put(JcloudsLocation.USER, loginUser); + + } else { + LOG.info("Not creating user {}, and not installing its password or authorizing keys (assuming it exists)", user); + + if (credential.isUsingPassword()) { + createdUserCreds = LoginCredentials.builder().user(user).password(credential.getPassword()).build(); + if (Boolean.FALSE.equals(config.get(JcloudsLocation.DISABLE_ROOT_AND_PASSWORD_SSH))) { + statements.add(SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes"))); + } + } else if (credential.hasKey()) { + createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build(); + } + } + + } else if (isWindows) { + // TODO Generate statements to create the user. + // createdUserCreds returned from this method will be null; + // we will use the creds returned by jclouds on the node + LOG.warn("Not creating or configuring user on Windows VM, despite " + JcloudsLocation.DONT_CREATE_USER.getName() + " set to false"); + + // TODO extractVmCredentials() will use user:publicKeyData defaults, if we don't override this. + // For linux, how would we configure Brooklyn to use the node.getCredentials() - i.e. the version + // that the cloud automatically generated? + if (config.get(JcloudsLocation.USER) != null) config.put(JcloudsLocation.USER, ""); + if (config.get(JcloudsLocation.PASSWORD) != null) config.put(JcloudsLocation.PASSWORD, ""); + if (config.get(JcloudsLocation.PRIVATE_KEY_DATA) != null) config.put(JcloudsLocation.PRIVATE_KEY_DATA, ""); + if (config.get(JcloudsLocation.PRIVATE_KEY_FILE) != null) config.put(JcloudsLocation.PRIVATE_KEY_FILE, ""); + if (config.get(JcloudsLocation.PUBLIC_KEY_DATA) != null) config.put(JcloudsLocation.PUBLIC_KEY_DATA, ""); + if (config.get(JcloudsLocation.PUBLIC_KEY_FILE) != null) config.put(JcloudsLocation.PUBLIC_KEY_FILE, ""); + + } else if (Strings.isBlank(user) || user.equals(loginUser) || user.equals(JcloudsLocation.ROOT_USERNAME)) { + boolean useKey = Strings.isNonBlank(credential.getPublicKeyData()); + + // For subsequent ssh'ing, we'll be using the loginUser + if (Strings.isBlank(user)) { + user = loginUser; + config.put(JcloudsLocation.USER, user); + } + + // Using the pre-existing loginUser; setup the publicKey/password so can login as expected + + // *Always* change the password (unless dontCreateUser was specified) + statements.add(new ReplaceShadowPasswordEntry(Sha512Crypt.function(), user, passwordToSet)); + createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build(); + + if (useKey) { + // NB: further keys are added from config *after* user creation + statements.add(new AuthorizeRSAPublicKeys("~" + user + "/.ssh", ImmutableList.of(credential.getPublicKeyData()), null)); + if (Strings.isNonBlank(credential.getPrivateKeyData())) { + createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build(); + } + } + + if (!useKey || Boolean.FALSE.equals(config.get(JcloudsLocation.DISABLE_ROOT_AND_PASSWORD_SSH))) { + // ensure password is permitted for ssh + statements.add(SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes"))); + if (user.equals(JcloudsLocation.ROOT_USERNAME)) { + statements.add(SshStatements.sshdConfig(ImmutableMap.of("PermitRootLogin", "yes"))); + } + } + + } else { + String pubKey = credential.getPublicKeyData(); + String privKey = credential.getPrivateKeyData(); + + if (credential.isEmpty()) { + /* + * TODO have an explicit `create_new_key_per_machine` config key. + * error if privateKeyData is set in this case. + * publicKeyData automatically added to EXTRA_SSH_KEY_URLS_TO_AUTH. + * + * if this config key is not set, use a key `brooklyn_id_rsa` and `.pub` in `MGMT_BASE_DIR`, + * with permission 0600, creating it if necessary, and logging the fact that this was created. + */ + // TODO JcloudsLocation used to log this once only: loggedSshKeysHint.compareAndSet(false, true). + if (!config.containsKey(JcloudsLocation.PRIVATE_KEY_FILE)) { + LOG.info("Default SSH keys not found or not usable; will create new keys for each machine. " + + "Create ~/.ssh/id_rsa or set {} / {} / {} as appropriate for this location " + + "if you wish to be able to log in without Brooklyn.", + new Object[]{JcloudsLocation.PRIVATE_KEY_FILE.getName(), JcloudsLocation.PRIVATE_KEY_PASSPHRASE.getName(), JcloudsLocation.PASSWORD.getName()}); + } + KeyPair newKeyPair = SecureKeys.newKeyPair(); + pubKey = SecureKeys.toPub(newKeyPair); + privKey = SecureKeys.toPem(newKeyPair); + LOG.debug("Brooklyn key being created for " + user + " at new machine " + location + " is:\n" + privKey); + } + + // Create the user + // note AdminAccess requires _all_ fields set, due to http://code.google.com/p/jclouds/issues/detail?id=1095 + AdminAccess.Builder adminBuilder = AdminAccess.builder() + .adminUsername(user) + .grantSudoToAdminUser(grantUserSudo); + adminBuilder.cryptFunction(Sha512Crypt.function()); + + boolean useKey = Strings.isNonBlank(pubKey); + adminBuilder.cryptFunction(Sha512Crypt.function()); + + // always set this password; if not supplied, it will be a random string + adminBuilder.adminPassword(passwordToSet); + // log the password also, in case we need it + LOG.debug("Password '{}' being created for user '{}' at the machine we are about to provision in {}; {}", + new Object[]{passwordToSet, user, location, useKey ? "however a key will be used to access it" : "this will be the only way to log in"}); + + if (grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH)) { + // the default - set root password which we forget, because we have sudo acct + // (and lock out root and passwords from ssh) + adminBuilder.resetLoginPassword(true); + adminBuilder.loginPassword(Identifiers.makeRandomId(12)); + } else { + adminBuilder.resetLoginPassword(false); + adminBuilder.loginPassword(Identifiers.makeRandomId(12) + "-ignored"); + } + + if (useKey) { + adminBuilder.authorizeAdminPublicKey(true).adminPublicKey(pubKey); + } else { + adminBuilder.authorizeAdminPublicKey(false).adminPublicKey(Identifiers.makeRandomId(12) + "-ignored"); + } + + // jclouds wants us to give it the private key, otherwise it might refuse to authorize the public key + // (in AdminAccess.build, if adminUsername != null && adminPassword != null); + // we don't want to give it the private key, but we *do* want the public key authorized; + // this code seems to trigger that. + // (we build the creds below) + adminBuilder.installAdminPrivateKey(false).adminPrivateKey(Identifiers.makeRandomId(12) + "-ignored"); + + // lock SSH means no root login and no passwordless login + // if we're using a password or we don't have sudo, then don't do this! + adminBuilder.lockSsh(useKey && grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH)); + + statements.add(adminBuilder.build()); + + if (useKey) { + createdUserCreds = LoginCredentials.builder().user(user).privateKey(privKey).build(); + } else if (passwordToSet != null) { + createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build(); + } + + if (!useKey || Boolean.FALSE.equals(config.get(JcloudsLocation.DISABLE_ROOT_AND_PASSWORD_SSH))) { + // ensure password is permitted for ssh + statements.add(SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes"))); + } + } + + String customTemplateOptionsScript = config.get(JcloudsLocation.CUSTOM_TEMPLATE_OPTIONS_SCRIPT_CONTENTS); + if (Strings.isNonBlank(customTemplateOptionsScript)) { + statements.add(new LiteralStatement(customTemplateOptionsScript)); + } + + LOG.debug("Machine we are about to create in {} will be customized with: {}", location, statements); + + return new CreateUserStatements(createdUserCreds, statements); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c93a79de/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java index 14bab2d..c680815 100644 --- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.security.KeyPair; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -45,7 +44,6 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; - import javax.annotation.Nullable; import javax.xml.ws.WebServiceException; @@ -92,7 +90,6 @@ import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.config.ResolvingConfigBag; -import org.apache.brooklyn.util.core.crypto.SecureKeys; import org.apache.brooklyn.util.core.flags.MethodCoercions; import org.apache.brooklyn.util.core.flags.SetFromFlag; import org.apache.brooklyn.util.core.flags.TypeCoercions; @@ -125,7 +122,6 @@ import org.apache.brooklyn.util.ssh.IptablesCommands.Chain; import org.apache.brooklyn.util.ssh.IptablesCommands.Policy; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.ByteSizeStrings; -import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.KeyValueParser; import org.apache.brooklyn.util.text.StringPredicates; import org.apache.brooklyn.util.text.Strings; @@ -149,7 +145,6 @@ import org.jclouds.compute.domain.OsFamily; import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.compute.domain.TemplateBuilderSpec; -import org.jclouds.compute.functions.Sha512Crypt; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.domain.Credentials; import org.jclouds.domain.LocationScope; @@ -157,12 +152,9 @@ import org.jclouds.domain.LoginCredentials; import org.jclouds.ec2.compute.options.EC2TemplateOptions; import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; import org.jclouds.rest.AuthorizationException; -import org.jclouds.scriptbuilder.domain.LiteralStatement; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.StatementList; import org.jclouds.scriptbuilder.functions.InitAdminAccess; -import org.jclouds.scriptbuilder.statements.login.AdminAccess; -import org.jclouds.scriptbuilder.statements.login.ReplaceShadowPasswordEntry; import org.jclouds.scriptbuilder.statements.ssh.AuthorizeRSAPublicKeys; import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions; import org.jclouds.util.Predicates2; @@ -225,14 +217,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im @VisibleForTesting static final String AWS_VPC_HELP_URL = "http://brooklyn.apache.org/v/latest/ops/locations/index.html#ec2-classic-problems-with-vpc-only-hardware-instance-types"; - private final AtomicBoolean loggedSshKeysHint = new AtomicBoolean(false); private final AtomicBoolean listedAvailableTemplatesOnNoSuchTemplate = new AtomicBoolean(false); private final Map<String,Map<String, ? extends Object>> tagMapping = Maps.newLinkedHashMap(); @SetFromFlag // so it's persisted private final Map<MachineLocation,String> vmInstanceIds = Maps.newLinkedHashMap(); - + static { Networking.init(); } public JcloudsLocation() { @@ -341,52 +332,52 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im public boolean isWindows(Template template, ConfigBag config) { return isWindows(template.getImage(), config); } - + /** * Whether VMs provisioned from this image will be Windows. Assume windows if the image - * explicitly says so, or if image does not tell us then fall back to whether the config + * explicitly says so, or if image does not tell us then fall back to whether the config * explicitly says windows in {@link JcloudsLocationConfig#OS_FAMILY}. - * - * Will first look at {@link JcloudsLocationConfig#OS_FAMILY_OVERRIDE}, to check if that + * + * Will first look at {@link JcloudsLocationConfig#OS_FAMILY_OVERRIDE}, to check if that * is set. If so, no further checks are done: the value is compared against {@link OsFamily#WINDOWS}. - * - * We believe the config (e.g. from brooklyn.properties) because for some clouds there is + * + * We believe the config (e.g. from brooklyn.properties) because for some clouds there is * insufficient meta-data so the Image might not tell us. Thus a user can work around it - * by explicitly supplying configuration. + * by explicitly supplying configuration. */ public boolean isWindows(Image image, ConfigBag config) { OsFamily override = config.get(OS_FAMILY_OVERRIDE); if (override != null) return override == OsFamily.WINDOWS; - + OsFamily confFamily = config.get(OS_FAMILY); OperatingSystem os = (image != null) ? image.getOperatingSystem() : null; - return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) - ? (OsFamily.WINDOWS == os.getFamily()) + return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) + ? (OsFamily.WINDOWS == os.getFamily()) : (OsFamily.WINDOWS == confFamily); } /** * Whether the given VM is Windows. - * - * @see {@link #isWindows(Image, ConfigBag)} + * + * @see #isWindows(Image, ConfigBag) */ public boolean isWindows(NodeMetadata node, ConfigBag config) { OsFamily override = config.get(OS_FAMILY_OVERRIDE); if (override != null) return override == OsFamily.WINDOWS; - + OsFamily confFamily = config.get(OS_FAMILY); OperatingSystem os = (node != null) ? node.getOperatingSystem() : null; - return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) - ? (OsFamily.WINDOWS == os.getFamily()) + return (os != null && os.getFamily() != OsFamily.UNRECOGNIZED) + ? (OsFamily.WINDOWS == os.getFamily()) : (OsFamily.WINDOWS == confFamily); } public boolean isLocationFirewalldEnabled(SshMachineLocation location) { - int result = location.execCommands("checking if firewalld is active", + int result = location.execCommands("checking if firewalld is active", ImmutableList.of(IptablesCommands.firewalldServiceIsActive())); return result == 0; } - + protected Semaphore getMachineCreationSemaphore() { return checkNotNull(getConfig(MACHINE_CREATION_SEMAPHORE), MACHINE_CREATION_SEMAPHORE.getName()); } @@ -615,7 +606,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im Map<String, Object> baseTemplateOptions = config().get(TEMPLATE_OPTIONS); Map<String, Object> templateOptions = (Map<String, Object>) shallowMerge(Maybe.fromNullable(flagTemplateOptions), Maybe.fromNullable(baseTemplateOptions), TEMPLATE_OPTIONS).orNull(); setup.put(TEMPLATE_OPTIONS, templateOptions); - + Integer attempts = setup.get(MACHINE_CREATE_ATTEMPTS); List<Exception> exceptions = Lists.newArrayList(); if (attempts == null || attempts < 1) attempts = 1; @@ -667,7 +658,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im Duration usableTimestamp = null; Duration customizedTimestamp = null; Stopwatch provisioningStopwatch = Stopwatch.createStarted(); - + try { LOG.info("Creating VM "+setup.getDescription()+" in "+this); @@ -688,7 +679,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im Template template; Collection<JcloudsLocationCustomizer> customizers = getCustomizers(setup); Collection<MachineLocationCustomizer> machineCustomizers = getMachineCustomizers(setup); - + try { // Setup the template template = buildTemplate(computeService, setup, customizers); @@ -708,10 +699,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im // TODO it would be nice if this salt comes from the location's ID (but we don't know that yet as the ssh machine location isn't created yet) // TODO in softlayer we want to control the suffix of the hostname which is 3 random hex digits template.getOptions().getUserMetadata().put("Name", cloudMachineNamer.generateNewMachineUniqueNameFromGroupId(setup, groupId)); - + if (setup.get(JcloudsLocationConfig.INCLUDE_BROOKLYN_USER_METADATA)) { template.getOptions().getUserMetadata().put("brooklyn-user", System.getProperty("user.name")); - + Object context = setup.get(CALLER_CONTEXT); if (context instanceof Entity) { Entity entity = (Entity)context; @@ -722,9 +713,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im template.getOptions().getUserMetadata().put("brooklyn-server-creation-date", Time.makeDateSimpleStampString()); } } - + customizeTemplate(computeService, template, customizers); - + LOG.debug("jclouds using template {} / options {} to provision machine in {}", new Object[] {template, template.getOptions(), setup.getDescription()}); @@ -741,7 +732,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im boolean windows = isWindows(node, setup); boolean waitForConnectable = (windows) ? waitForWinRmable : waitForSshable; - + if (windows) { int newLoginPort = node.getLoginPort() == 22 ? (getConfig(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985) @@ -771,7 +762,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } LoginCredentials initialCredentials = node.getCredentials(); - + final HostAndPort managementHostAndPort = resolveManagementHostAndPort( node, Optional.fromNullable(userCredentials), sshHostAndPortOverride, setup, new ResolveOptions() @@ -779,7 +770,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im .expectConnectable(waitForConnectable) .pollForFirstReachableAddress(!"false".equalsIgnoreCase(setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS))) .propagatePollForReachableFailure(true)); - + if (skipJcloudsSshing) { if (waitForConnectable) { if (windows) { @@ -895,7 +886,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } } } - + Boolean dontRequireTtyForSudo = setup.get(JcloudsLocationConfig.DONT_REQUIRE_TTY_FOR_SUDO); if (Boolean.TRUE.equals(dontRequireTtyForSudo) || (dontRequireTtyForSudo == null && setup.get(DONT_CREATE_USER))) { @@ -919,7 +910,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im (SshMachineLocation)machineLocation, "using urandom instead of random", Arrays.asList( - BashCommands.sudo("mv /dev/random /dev/random-real"), + BashCommands.sudo("mv /dev/random /dev/random-real"), BashCommands.sudo("ln -s /dev/urandom /dev/random"))); } } @@ -948,7 +939,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im LOG.warn("Ignoring DEPRECATED flag OPEN_IPTABLES on Windows location {}", machineLocation); } else { LOG.warn("Using DEPRECATED flag OPEN_IPTABLES (will not be supported in future versions) for {} at {}", machineLocation, this); - + @SuppressWarnings("unchecked") Iterable<Integer> inboundPorts = (Iterable<Integer>) setup.get(INBOUND_PORTS); @@ -1002,7 +993,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im LOG.warn("Ignoring DEPRECATED flag OPEN_IPTABLES on Windows location {}", machineLocation); } else { LOG.warn("Using DEPRECATED flag STOP_IPTABLES (will not be supported in future versions) for {} at {}", machineLocation, this); - + customisationForLogging.add("stop iptables"); List<String> cmds = ImmutableList.<String>of(); @@ -1032,7 +1023,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im ImmutableList.of(new AuthorizeRSAPublicKeys(extraKeyDataToAuth).render(org.jclouds.scriptbuilder.domain.OsFamily.UNIX))); } } - + String extraKeyDataToAuth = setup.get(EXTRA_PUBLIC_KEY_DATA_TO_AUTH); if (extraKeyDataToAuth!=null && !extraKeyDataToAuth.isEmpty()) { if (windows) { @@ -1084,7 +1075,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } return machineLocation; - + } catch (Exception e) { if (e instanceof RunNodesException && ((RunNodesException)e).getNodeErrors().size() > 0) { node = Iterables.get(((RunNodesException)e).getNodeErrors().keySet(), 0); @@ -1101,7 +1092,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im LOG.error(message); e = new UserFacingException(message, e); } - + LOG.error("Failed to start VM for "+setup.getDescription() + (destroyNode ? " (destroying)" : "") + (node != null ? "; node "+node : "") + " after "+Duration.of(provisioningStopwatch).toStringRounded() @@ -1130,7 +1121,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im throw Exceptions.propagate(e); } } - + private void executeCommandThrowingOnError(SshMachineLocation loc, String name, List<String> commands) { executeCommandThrowingOnError(ImmutableMap.<String, Object>of(), loc, name, commands); } @@ -1412,13 +1403,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im }}) .put(EXTRA_PUBLIC_KEY_DATA_TO_AUTH, new CustomizeTemplateOptions() { public void apply(TemplateOptions t, ConfigBag props, Object v) { - // this is unreliable: + // this is unreliable: // * seems now (Aug 2016) to be run *before* the TO.runScript which creates the user, // so is installed for the initial login user not the created user // * not supported in GCE (it uses it as the login public key, see email to jclouds list, 29 Aug 2015) // so only works if you also overrideLoginPrivateKey // -- - // for this reason we also inspect these ourselves + // for this reason we also inspect these ourselves // along with EXTRA_PUBLIC_KEY_URLS_TO_AUTH // and install after creation; // -- @@ -1514,12 +1505,12 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im if (t instanceof AWSEC2TemplateOptions) { // subnet ID is the sensible interpretation of network name in EC2 ((AWSEC2TemplateOptions)t).subnetId((String)v); - + } else { if (isGoogleComputeTemplateOptions(t)) { // no warning needed // we think this is the only jclouds endpoint which supports this option - + } else if (t instanceof SoftLayerTemplateOptions) { LOG.warn("networkName is not be supported in SoftLayer; use `templateOptions` with `primaryNetworkComponentNetworkVlanId` or `primaryNetworkBackendComponentNetworkVlanId`"); } else if (!(t instanceof CloudStackTemplateOptions) && !(t instanceof NovaTemplateOptions)) { @@ -1529,7 +1520,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im // Openstack Nova uses securityGroupNames which is marked as @deprecated (suggests to use groups which is maybe even more confusing) // Azure supports the custom networkSecurityGroupName } - + t.networks((String)v); } }}) @@ -1538,7 +1529,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im if (t instanceof SoftLayerTemplateOptions) { ((SoftLayerTemplateOptions)t).domainName(TypeCoercions.coerce(v, String.class)); } else { - LOG.info("ignoring domain-name({}) in VM creation because not supported for cloud/type ({})", v, t); + LOG.info("ignoring domain-name({}) in VM creation because not supported for cloud/type ({})", v, t); } }}) .put(TEMPLATE_OPTIONS, new CustomizeTemplateOptions() { @@ -1610,7 +1601,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } /** - * If the ImageChooser is a string, then try instantiating a class with that name (in the same + * If the ImageChooser is a string, then try instantiating a class with that name (in the same * way as we do for {@link #getCloudMachineNamer(ConfigBag)}, for example). Otherwise, assume * that convention TypeCoercions will work. */ @@ -1769,7 +1760,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } } } - + for (Map.Entry<ConfigKey<?>, CustomizeTemplateOptions> entry : SUPPORTED_TEMPLATE_OPTIONS_PROPERTIES.entrySet()) { ConfigKey<?> key = entry.getKey(); CustomizeTemplateOptions code = entry.getValue(); @@ -1786,7 +1777,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im if (Strings.isBlank(s)) s = config().get(NAMED_SPEC_NAME); if (Strings.isBlank(s)) s = config().get(FINAL_SPEC); if (Strings.isBlank(s)) s = getDisplayName(); - + String s2 = ""; String provider = getProvider(); if (Strings.isBlank(s) || (Strings.isNonBlank(provider) && !s.toLowerCase().contains(provider.toLowerCase()))) @@ -1810,7 +1801,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im // things are bad if we get to this point! return toString(); } - + protected void logAvailableTemplates(ConfigBag config) { LOG.info("Loading available images at "+this+" for reference..."); ConfigBag m1 = ConfigBag.newInstanceCopying(config); @@ -1860,7 +1851,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im sshProps.remove("privateKeyData"); sshProps.remove("privateKeyFile"); sshProps.remove("privateKeyPassphrase"); - + if (initialPassword.isPresent()) sshProps.put("password", initialPassword.get()); if (initialPrivateKey.isPresent()) sshProps.put("privateKeyData", initialPrivateKey.get()); @@ -1904,11 +1895,13 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im /** * Create the user immediately - executing ssh commands as required. */ - protected LoginCredentials createUser(ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort, LoginCredentials initialCredentials, ConfigBag config) { + protected LoginCredentials createUser( + ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort, + LoginCredentials initialCredentials, ConfigBag config) { Image image = (node.getImageId() != null) ? computeService.getImage(node.getImageId()) : null; - UserCreation userCreation = createUserStatements(image, config); + CreateUserStatements userCreation = createUserStatements(image, config); - if (!userCreation.statements.isEmpty()) { + if (!userCreation.statements().isEmpty()) { // If unsure of OS family, default to unix for rendering statements. org.jclouds.scriptbuilder.domain.OsFamily scriptOsFamily; if (isWindows(node, config)) { @@ -1920,10 +1913,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im boolean windows = isWindows(node, config); if (windows) { - LOG.warn("Unable to execute statements on WinRM in JcloudsLocation; skipping for "+node+": "+userCreation.statements); + LOG.warn("Unable to execute statements on WinRM in JcloudsLocation; skipping for "+node+": "+userCreation.statements()); } else { List<String> commands = Lists.newArrayList(); - for (Statement statement : userCreation.statements) { + for (Statement statement : userCreation.statements()) { InitAdminAccess initAdminAccess = new InitAdminAccess(new AdminAccessConfiguration.Default()); initAdminAccess.visit(statement); commands.add(statement.render(scriptOsFamily)); @@ -1960,7 +1953,21 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } } - return userCreation.createdUserCredentials; + return userCreation.credentials(); + } + + /** + * Set up the TemplateOptions to create the user. + */ + protected LoginCredentials initTemplateForCreateUser(Template template, ConfigBag config) { + CreateUserStatements userCreation = createUserStatements(template.getImage(), config); + + if (!userCreation.statements().isEmpty()) { + TemplateOptions options = template.getOptions(); + options.runScript(new StatementList(userCreation.statements())); + } + + return userCreation.credentials(); } private static class ResolveOptions { @@ -1968,7 +1975,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im boolean expectConnectable; boolean isWindows; boolean propagatePollForReachableFailure; - + ResolveOptions pollForFirstReachableAddress(boolean val) { pollForFirstReachableAddress = val; return this; @@ -1986,17 +1993,17 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im return this; } } - + /** - * Infers the hostAndPort to use for subsequent creation of the + * Infers the hostAndPort to use for subsequent creation of the * {@link JcloudsSshMachineLocation} or {@link JcloudsWinRmMachineLocation}. * This is expected to be the login host:port, for connecting to the VM * via ssh or WinRM. - * - * However, some VMs provisioned will not be sshable or reachable at all. - * In such cases, this method will likely return the first address returned by + * + * However, some VMs provisioned will not be sshable or reachable at all. + * In such cases, this method will likely return the first address returned by * jclouds. - * + * * For AWS, if we are allowed to SSH to the VM to find out its DNS name, then we'll * return that fully qualified name (which we expect to be reachable from inside * and outside the AWS region). @@ -2020,8 +2027,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im return HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port); } if (options.expectConnectable && userCredentials.isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) { - // Treat AWS as a special case because the DNS fully qualified hostname in AWS is - // (normally?!) a good way to refer to the VM from both inside and outside of the + // Treat AWS as a special case because the DNS fully qualified hostname in AWS is + // (normally?!) a good way to refer to the VM from both inside and outside of the // region. Maybe<String> result = getHostnameAws(node, hostAndPortOverride, Suppliers.ofInstance(userCredentials.get()), config); if (result.isPresent()) { @@ -2064,261 +2071,26 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } } - /** - * Setup the TemplateOptions to create the user. - */ - protected LoginCredentials initTemplateForCreateUser(Template template, ConfigBag config) { - UserCreation userCreation = createUserStatements(template.getImage(), config); - - if (userCreation.statements.size() > 0) { - TemplateOptions options = template.getOptions(); - options.runScript(new StatementList(userCreation.statements)); - } - - return userCreation.createdUserCredentials; - } - - protected static class UserCreation { + /** @deprecated since 0.11.0 use {@link CreateUserStatements} instead. */ + @Deprecated + protected static class UserCreation extends CreateUserStatements { public final LoginCredentials createdUserCredentials; public final List<Statement> statements; public UserCreation(LoginCredentials creds, List<Statement> statements) { - this.createdUserCredentials = creds; - this.statements = statements; + super(creds, statements); + this.createdUserCredentials = super.credentials(); + this.statements = super.statements(); } } - /** - * Returns the commands required to create the user, to be used for connecting (e.g. over ssh) - * to the machine; also returns the expected login credentials. - * <p> - * The returned login credentials may be null if we haven't done any user-setup and no specific - * user was supplied (i.e. if {@code dontCreateUser} was true and {@code user} was null or blank). - * In which case, the caller should use the jclouds node's login credentials. - * <p> - * There are quite a few configuration options. Depending on their values, the user-creation - * behaves differently: - * <ul> - * <li>{@code dontCreateUser} says not to run any user-setup commands at all. If {@code user} is - * non-empty (including with the default value), then that user will subsequently be used, - * otherwise the (inferred) {@code loginUser} will be used. - * <li>{@code loginUser} refers to the existing user that jclouds should use when setting up the VM. - * Normally this will be inferred from the image (i.e. doesn't need to be explicitly set), but sometimes - * the image gets it wrong so this can be a handy override. - * <li>{@code user} is the username for brooklyn to subsequently use when ssh'ing to the machine. - * If not explicitly set, its value will default to the username of the user running brooklyn. - * <ul> - * <li>If the {@code user} value is null or empty, then the (inferred) {@code loginUser} will - * subsequently be used, setting up the password/authorizedKeys for that loginUser. - * <li>If the {@code user} is "root", then setup the password/authorizedKeys for root. - * <li>If the {@code user} equals the (inferred) {@code loginUser}, then don't try to create this - * user but instead just setup the password/authorizedKeys for the user. - * <li>Otherwise create the given user, setting up the password/authorizedKeys (unless - * {@code dontCreateUser} is set, obviously). - * </ul> - * <li>{@code publicKeyData} is the key to authorize (i.e. add to .ssh/authorized_keys), - * if not null or blank. Note the default is to use {@code ~/.ssh/id_rsa.pub} or {@code ~/.ssh/id_dsa.pub} - * if either of those files exist for the user running brooklyn. - * Related is {@code publicKeyFile}, which is used to populate publicKeyData. - * <li>{@code password} is the password to set for the user. If null or blank, then a random password - * will be auto-generated and set. - * <li>{@code privateKeyData} is the key to use when subsequent ssh'ing, if not null or blank. - * Note the default is to use {@code ~/.ssh/id_rsa} or {@code ~/.ssh/id_dsa}. - * The subsequent preferences for ssh'ing are: - * <ul> - * <li>Use the {@code privateKeyData} if not null or blank (including if using default) - * <li>Use the {@code password} (or the auto-generated password if that is blank). - * </ul> - * <li>{@code grantUserSudo} determines whether or not the created user may run the sudo command.</li> - * </ul> - * - * @param image The image being used to create the VM - * @param config Configuration for creating the VM - * @return The commands required to create the user, along with the expected login credentials for that user, - * or null if we are just going to use those from jclouds. - */ + /** @deprecated since 0.11.0 return type will be changed to {@link CreateUserStatements} in a future release. */ + @Deprecated protected UserCreation createUserStatements(@Nullable Image image, ConfigBag config) { - //NB: private key is not installed remotely, just used to get/validate the public key - - boolean windows = isWindows(image, config); - String user = getUser(config); - String explicitLoginUser = config.get(LOGIN_USER); - String loginUser = groovyTruth(explicitLoginUser) ? explicitLoginUser : (image != null && image.getDefaultCredentials() != null) ? image.getDefaultCredentials().identity : null; - boolean dontCreateUser = config.get(DONT_CREATE_USER); - boolean grantUserSudo = config.get(GRANT_USER_SUDO); - OsCredential credential = LocationConfigUtils.getOsCredential(config); - credential.checkNoErrors().logAnyWarnings(); - String passwordToSet = Strings.isNonBlank(credential.getPassword()) ? credential.getPassword() : Identifiers.makeRandomId(12); - List<Statement> statements = Lists.newArrayList(); - LoginCredentials createdUserCreds = null; - - if (dontCreateUser) { - // dontCreateUser: - // if caller has not specified a user, we'll just continue to use the loginUser; - // if caller *has*, we set up our credentials assuming that user and credentials already exist - - if (Strings.isBlank(user)) { - // createdUserCreds returned from this method will be null; - // we will use the creds returned by jclouds on the node - LOG.info("Not setting up any user (subsequently using loginUser {})", user, loginUser); - config.put(USER, loginUser); - - } else { - LOG.info("Not creating user {}, and not installing its password or authorizing keys (assuming it exists)", user); - - if (credential.isUsingPassword()) { - createdUserCreds = LoginCredentials.builder().user(user).password(credential.getPassword()).build(); - if (Boolean.FALSE.equals(config.get(DISABLE_ROOT_AND_PASSWORD_SSH))) { - statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes"))); - } - } else if (credential.hasKey()) { - createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build(); - } - } - - } else if (windows) { - // TODO Generate statements to create the user. - // createdUserCreds returned from this method will be null; - // we will use the creds returned by jclouds on the node - LOG.warn("Not creating or configuring user on Windows VM, despite "+DONT_CREATE_USER.getName()+" set to false"); - - // TODO extractVmCredentials() will use user:publicKeyData defaults, if we don't override this. - // For linux, how would we configure Brooklyn to use the node.getCredentials() - i.e. the version - // that the cloud automatically generated? - if (config.get(USER) != null) config.put(USER, ""); - if (config.get(PASSWORD) != null) config.put(PASSWORD, ""); - if (config.get(PRIVATE_KEY_DATA) != null) config.put(PRIVATE_KEY_DATA, ""); - if (config.get(PRIVATE_KEY_FILE) != null) config.put(PRIVATE_KEY_FILE, ""); - if (config.get(PUBLIC_KEY_DATA) != null) config.put(PUBLIC_KEY_DATA, ""); - if (config.get(PUBLIC_KEY_FILE) != null) config.put(PUBLIC_KEY_FILE, ""); - - } else if (Strings.isBlank(user) || user.equals(loginUser) || user.equals(ROOT_USERNAME)) { - boolean useKey = Strings.isNonBlank(credential.getPublicKeyData()); - - // For subsequent ssh'ing, we'll be using the loginUser - if (Strings.isBlank(user)) { - user = loginUser; - config.put(USER, user); - } - - // Using the pre-existing loginUser; setup the publicKey/password so can login as expected - - // *Always* change the password (unless dontCreateUser was specified) - statements.add(new ReplaceShadowPasswordEntry(Sha512Crypt.function(), user, passwordToSet)); - createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build(); - - if (useKey) { - // NB: further keys are added from config *after* user creation - statements.add(new AuthorizeRSAPublicKeys("~"+user+"/.ssh", ImmutableList.of(credential.getPublicKeyData()), null)); - if (Strings.isNonBlank(credential.getPrivateKeyData())) { - createdUserCreds = LoginCredentials.builder().user(user).privateKey(credential.getPrivateKeyData()).build(); - } - } - - if (!useKey || Boolean.FALSE.equals(config.get(DISABLE_ROOT_AND_PASSWORD_SSH))) { - // ensure password is permitted for ssh - statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes"))); - if (user.equals(ROOT_USERNAME)) { - statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PermitRootLogin", "yes"))); - } - } - - } else { - String pubKey = credential.getPublicKeyData(); - String privKey = credential.getPrivateKeyData(); - - if (credential.isEmpty()) { - /* - * TODO have an explicit `create_new_key_per_machine` config key. - * error if privateKeyData is set in this case. - * publicKeyData automatically added to EXTRA_SSH_KEY_URLS_TO_AUTH. - * - * if this config key is not set, use a key `brooklyn_id_rsa` and `.pub` in `MGMT_BASE_DIR`, - * with permission 0600, creating it if necessary, and logging the fact that this was created. - */ - if (!config.containsKey(PRIVATE_KEY_FILE) && loggedSshKeysHint.compareAndSet(false, true)) { - LOG.info("Default SSH keys not found or not usable; will create new keys for each machine. " - + "Create ~/.ssh/id_rsa or " - + "set "+PRIVATE_KEY_FILE.getName()+" / "+PRIVATE_KEY_PASSPHRASE.getName()+" / "+PASSWORD.getName()+" " - + "as appropriate for this location if you wish to be able to log in without Brooklyn."); - } - KeyPair newKeyPair = SecureKeys.newKeyPair(); - pubKey = SecureKeys.toPub(newKeyPair); - privKey = SecureKeys.toPem(newKeyPair); - LOG.debug("Brooklyn key being created for "+user+" at new machine "+this+" is:\n"+privKey); - } - // ensure credential is not used any more, as we have extracted all useful info - credential = null; - - // Create the user - // note AdminAccess requires _all_ fields set, due to http://code.google.com/p/jclouds/issues/detail?id=1095 - AdminAccess.Builder adminBuilder = AdminAccess.builder() - .adminUsername(user) - .grantSudoToAdminUser(grantUserSudo); - adminBuilder.cryptFunction(Sha512Crypt.function()); - - boolean useKey = Strings.isNonBlank(pubKey); - adminBuilder.cryptFunction(Sha512Crypt.function()); - - // always set this password; if not supplied, it will be a random string - adminBuilder.adminPassword(passwordToSet); - // log the password also, in case we need it - LOG.debug("Password '"+passwordToSet+"' being created for user '"+user+"' at the machine we are about to provision in "+this+"; "+ - (useKey ? "however a key will be used to access it" : "this will be the only way to log in")); - - if (grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH)) { - // the default - set root password which we forget, because we have sudo acct - // (and lock out root and passwords from ssh) - adminBuilder.resetLoginPassword(true); - adminBuilder.loginPassword(Identifiers.makeRandomId(12)); - } else { - adminBuilder.resetLoginPassword(false); - adminBuilder.loginPassword(Identifiers.makeRandomId(12)+"-ignored"); - } - - if (useKey) { - adminBuilder.authorizeAdminPublicKey(true).adminPublicKey(pubKey); - } else { - adminBuilder.authorizeAdminPublicKey(false).adminPublicKey(Identifiers.makeRandomId(12)+"-ignored"); - } - - // jclouds wants us to give it the private key, otherwise it might refuse to authorize the public key - // (in AdminAccess.build, if adminUsername != null && adminPassword != null); - // we don't want to give it the private key, but we *do* want the public key authorized; - // this code seems to trigger that. - // (we build the creds below) - adminBuilder.installAdminPrivateKey(false).adminPrivateKey(Identifiers.makeRandomId(12)+"-ignored"); - - // lock SSH means no root login and no passwordless login - // if we're using a password or we don't have sudo, then don't do this! - adminBuilder.lockSsh(useKey && grantUserSudo && config.get(JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH)); - - statements.add(adminBuilder.build()); - - if (useKey) { - createdUserCreds = LoginCredentials.builder().user(user).privateKey(privKey).build(); - } else if (passwordToSet!=null) { - createdUserCreds = LoginCredentials.builder().user(user).password(passwordToSet).build(); - } - - if (!useKey || Boolean.FALSE.equals(config.get(DISABLE_ROOT_AND_PASSWORD_SSH))) { - // ensure password is permitted for ssh - statements.add(org.jclouds.scriptbuilder.statements.ssh.SshStatements.sshdConfig(ImmutableMap.of("PasswordAuthentication", "yes"))); - } - } - - String customTemplateOptionsScript = config.get(CUSTOM_TEMPLATE_OPTIONS_SCRIPT_CONTENTS); - if (Strings.isNonBlank(customTemplateOptionsScript)) { - statements.add(new LiteralStatement(customTemplateOptionsScript)); - } - - LOG.debug("Machine we are about to create in "+this+" will be customized with: "+ - statements); - - return new UserCreation(createdUserCreds, statements); + CreateUserStatements userStatements = CreateUserStatements.get(this, image, config); + return new UserCreation(userStatements.credentials(), userStatements.statements()); } - // ----------------- registering existing machines ------------------------ /** @@ -3537,7 +3309,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im return o.toString(); } - protected static double toDouble(Object v) { if (v instanceof Number) { return ((Number)v).doubleValue(); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c93a79de/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java index 8b04f93..119c19b 100644 --- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java +++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; - import javax.annotation.Nullable; import org.apache.brooklyn.api.location.LocationSpec; @@ -41,7 +40,6 @@ import org.apache.brooklyn.core.location.cloud.names.CustomMachineNamer; import org.apache.brooklyn.core.location.geo.HostGeoInfo; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; -import org.apache.brooklyn.location.jclouds.JcloudsLocation.UserCreation; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; @@ -580,8 +578,8 @@ public class JcloudsLocationTest implements JcloudsLocationConfig { .put(JcloudsLocationConfig.USER, "bob").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "b0b") .putAll(config).build()); - UserCreation creation = jl.createUserStatements(null, jl.config().getBag()); - return new StatementList(creation.statements).render(OsFamily.UNIX); + CreateUserStatements creation = CreateUserStatements.get(jl, null, jl.config().getBag()); + return new StatementList(creation.statements()).render(OsFamily.UNIX); } @Test