mirror of https://github.com/apache/jclouds.git
unwound dependencies relating to ssh keys and crypt
This commit is contained in:
parent
6a8ac673aa
commit
3ac6f475e7
|
@ -29,13 +29,13 @@ import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
|
|||
import org.jclouds.cloudstack.filters.QuerySigner;
|
||||
import org.jclouds.cloudstack.internal.BaseCloudStackAsyncClientTest;
|
||||
import org.jclouds.cloudstack.options.ListSSHKeyPairsOptions;
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
|
||||
import org.jclouds.functions.IdentityFunction;
|
||||
import org.jclouds.http.functions.ParseFirstJsonValueNamed;
|
||||
import org.jclouds.http.functions.ReleasePayloadAndReturn;
|
||||
import com.google.common.reflect.Invokable;
|
||||
import org.jclouds.rest.internal.GeneratedHttpRequest;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Functions;
|
||||
|
|
|
@ -27,9 +27,9 @@ import java.net.URI;
|
|||
import org.jclouds.cloudstack.CloudStackContext;
|
||||
import org.jclouds.cloudstack.domain.SshKeyPair;
|
||||
import org.jclouds.cloudstack.internal.BaseCloudStackExpectTest;
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.util.Strings2;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import java.util.Set;
|
|||
|
||||
import org.jclouds.cloudstack.domain.SshKeyPair;
|
||||
import org.jclouds.cloudstack.internal.BaseCloudStackClientLiveTest;
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
|
|
@ -21,8 +21,8 @@ package org.jclouds.ec2.compute.strategy;
|
|||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.crypto.SshKeys.sha1PrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.sha1PrivateKey;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
|
|
@ -20,8 +20,8 @@ package org.jclouds.ec2.domain;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.javax.annotation.Nullable;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -22,9 +22,9 @@ import static org.testng.Assert.assertEquals;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.ec2.domain.KeyPair;
|
||||
import org.jclouds.http.functions.ParseSax;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,9 +30,9 @@ import java.util.SortedSet;
|
|||
|
||||
import org.jclouds.compute.ComputeTestUtils;
|
||||
import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest;
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.ec2.domain.KeyPair;
|
||||
import org.jclouds.openstack.nova.ec2.NovaEC2ApiMetadata;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.jclouds.openstack.nova.v2_0.compute.strategy;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
|
@ -26,9 +26,9 @@ import java.util.Map;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.domain.Credentials;
|
||||
import org.jclouds.domain.LoginCredentials;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.trmk.vcloud_0_8.compute.domain.OrgAndName;
|
||||
import org.jclouds.trmk.vcloud_0_8.compute.functions.CreateUniqueKeyPair;
|
||||
import org.jclouds.trmk.vcloud_0_8.compute.options.TerremarkVCloudTemplateOptions;
|
||||
|
|
|
@ -24,8 +24,8 @@ import java.io.InputStream;
|
|||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.http.functions.BaseHandlerTest;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.trmk.vcloud_0_8.domain.KeyPair;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds 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.jclouds.compute.config;
|
||||
|
||||
import static com.google.common.base.Charsets.UTF_8;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.compute.config.AdminAccessConfiguration.Default;
|
||||
import org.jclouds.compute.functions.Sha512Crypt;
|
||||
import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.ImplementedBy;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*
|
||||
*/
|
||||
@ImplementedBy(Default.class)
|
||||
public interface AdminAccessConfiguration extends Configuration {
|
||||
@Singleton
|
||||
static class Default implements AdminAccessConfiguration {
|
||||
|
||||
private final Supplier<String> defaultAdminUsername = Suppliers.ofInstance(System.getProperty("user.name"));
|
||||
private final Supplier<Map<String, String>> defaultAdminSshKeys = new Supplier<Map<String, String>>() {
|
||||
public Map<String, String> get() {
|
||||
try {
|
||||
return ImmutableMap.of("public",
|
||||
Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa.pub"), UTF_8), "private",
|
||||
Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa"), UTF_8));
|
||||
} catch (IOException e) {
|
||||
return SshKeys.generate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cheap, lightweight, low-security password generator.
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://www.java-forums.org/java-lang/7355-how-create-lightweight-low-security-password-generator.html" />
|
||||
*/
|
||||
enum PasswordGenerator implements Supplier<String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/** Minimum length for a decent password */
|
||||
public static final int MIN_LENGTH = 10;
|
||||
|
||||
/** The random number generator. */
|
||||
protected static final SecureRandom r = new SecureRandom();
|
||||
|
||||
/*
|
||||
* Set of characters that is valid. Must be printable, memorable, and "won't break HTML" (i.e., not ' <', '>',
|
||||
* '&', '=', ...). or break shell commands (i.e., not ' <', '>', '$', '!', ...). I, L and O are good to leave
|
||||
* out, as are numeric zero and one.
|
||||
*/
|
||||
public static final char[] goodChar = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q',
|
||||
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-',
|
||||
'@', };
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < MIN_LENGTH; i++) {
|
||||
sb.append(goodChar[r.nextInt(goodChar.length)]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final Function<String, String> cryptFunction = Sha512Crypt.function();
|
||||
|
||||
@Override
|
||||
public Supplier<String> defaultAdminUsername() {
|
||||
return defaultAdminUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Map<String, String>> defaultAdminSshKeys() {
|
||||
return defaultAdminSshKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<String> passwordGenerator() {
|
||||
return PasswordGenerator.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<String, String> cryptFunction() {
|
||||
return cryptFunction;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,6 +61,7 @@ import org.jclouds.location.Provider;
|
|||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.statements.login.AdminAccess;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -85,6 +86,7 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
|||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(AdminAccess.Configuration.class).to(AdminAccessConfiguration.class);
|
||||
install(new ComputeServiceTimeoutsModule());
|
||||
bind(new TypeLiteral<Function<NodeMetadata, SshClient>>() {
|
||||
}).to(CreateSshClientOncePortIsListeningOnNode.class);
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
|
||||
*/
|
||||
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.compute.functions;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
|
@ -16,10 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.ssh;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.ssh.internal.RsaSshKeyPairGenerator;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.inject.ImplementedBy;
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.ssh;
|
||||
|
||||
import static com.google.common.base.Joiner.on;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
@ -48,6 +48,8 @@ import java.security.spec.RSAPrivateCrtKeySpec;
|
|||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.crypto.Crypto;
|
||||
import org.jclouds.crypto.Pems;
|
||||
import org.jclouds.io.InputSuppliers;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.ssh;
|
||||
package org.jclouds.ssh.config;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.ssh.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
@ -25,10 +25,14 @@ import java.util.Map;
|
|||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.crypto.Crypto;
|
||||
import org.jclouds.ssh.SshKeyPairGenerator;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@Singleton
|
||||
class RsaSshKeyPairGenerator implements SshKeyPairGenerator {
|
||||
public class RsaSshKeyPairGenerator implements SshKeyPairGenerator {
|
||||
private final Crypto crypto;
|
||||
private final SecureRandom secureRandom;
|
||||
|
|
@ -32,6 +32,7 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.easymock.IArgumentMatcher;
|
||||
import org.jclouds.compute.config.AdminAccessConfiguration;
|
||||
import org.jclouds.compute.domain.ExecResponse;
|
||||
import org.jclouds.compute.domain.NodeMetadata;
|
||||
import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
|
||||
|
@ -42,8 +43,6 @@ import org.jclouds.io.Payload;
|
|||
import org.jclouds.predicates.RetryablePredicate;
|
||||
import org.jclouds.predicates.SocketOpen;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.scriptbuilder.statements.login.AdminAccess;
|
||||
import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.util.Strings2;
|
||||
import org.testng.annotations.Test;
|
||||
|
@ -104,31 +103,23 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
@Override
|
||||
protected Module getSshModule() {
|
||||
return new AbstractModule() {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(AdminAccess.Configuration.class).toInstance(new Configuration() {
|
||||
|
||||
@Override
|
||||
bind(AdminAccessConfiguration.class).toInstance(new AdminAccessConfiguration() {
|
||||
public Supplier<String> defaultAdminUsername() {
|
||||
return Suppliers.ofInstance("defaultAdminUsername");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Map<String, String>> defaultAdminSshKeys() {
|
||||
return Suppliers.<Map<String, String>> ofInstance(ImmutableMap.of("public", "publicKey", "private",
|
||||
Pems.PRIVATE_PKCS1_MARKER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<String, String> cryptFunction() {
|
||||
return new Function<String, String>() {
|
||||
|
||||
@Override
|
||||
public String apply(String input) {
|
||||
return String.format("crypt(%s)", input);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.compute.functions;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import org.jclouds.compute.functions.Sha512Crypt;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
|
@ -16,14 +16,14 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.ssh;
|
||||
|
||||
import static org.jclouds.crypto.SshKeys.fingerprint;
|
||||
import static org.jclouds.crypto.SshKeys.generate;
|
||||
import static org.jclouds.crypto.SshKeys.privateKeyHasFingerprint;
|
||||
import static org.jclouds.crypto.SshKeys.privateKeyHasSha1;
|
||||
import static org.jclouds.crypto.SshKeys.privateKeyMatchesPublicKey;
|
||||
import static org.jclouds.crypto.SshKeys.publicKeySpecFromOpenSSH;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprint;
|
||||
import static org.jclouds.ssh.SshKeys.generate;
|
||||
import static org.jclouds.ssh.SshKeys.privateKeyHasFingerprint;
|
||||
import static org.jclouds.ssh.SshKeys.privateKeyHasSha1;
|
||||
import static org.jclouds.ssh.SshKeys.privateKeyMatchesPublicKey;
|
||||
import static org.jclouds.ssh.SshKeys.publicKeySpecFromOpenSSH;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -35,7 +35,9 @@ import java.security.spec.RSAPrivateCrtKeySpec;
|
|||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.crypto.Pems;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.util.Strings2;
|
||||
import org.testng.annotations.Test;
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jclouds.crypto;
|
||||
package org.jclouds.ssh.config;
|
||||
|
||||
import static org.easymock.EasyMock.createMock;
|
||||
import static org.easymock.EasyMock.expect;
|
||||
|
@ -37,7 +37,11 @@ import java.security.SecureRandom;
|
|||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import org.jclouds.crypto.Crypto;
|
||||
import org.jclouds.crypto.Pems;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.ssh.internal.RsaSshKeyPairGenerator;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
|
@ -19,6 +19,9 @@
|
|||
package org.jclouds.rest.internal;
|
||||
|
||||
import static com.google.common.base.Optional.fromNullable;
|
||||
import static com.google.common.collect.ObjectArrays.concat;
|
||||
import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
|
||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -39,6 +42,8 @@ import com.google.common.base.Optional;
|
|||
import com.google.common.reflect.Invokable;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.common.util.concurrent.UncheckedTimeoutException;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
public class BlockOnFuture implements Function<ListenableFuture<?>, Result> {
|
||||
|
@ -77,20 +82,40 @@ public class BlockOnFuture implements Function<ListenableFuture<?>, Result> {
|
|||
try {
|
||||
if (timeoutNanos.isPresent()) {
|
||||
logger.debug(">> blocking on %s for %s", future, timeoutNanos);
|
||||
return Result.success(future.get(timeoutNanos.get(), TimeUnit.NANOSECONDS));
|
||||
return Result.success(getUninterruptibly(future, timeoutNanos.get(), NANOSECONDS));
|
||||
} else {
|
||||
logger.debug(">> blocking on %s", future);
|
||||
return Result.success(future.get());
|
||||
return Result.success(getUninterruptibly(future));
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
return Result.fail(e.getCause());
|
||||
} catch (InterruptedException e) {
|
||||
return Result.fail(e); // TODO: should we kill the future?
|
||||
throw propagateCause(e);
|
||||
} catch (TimeoutException e) {
|
||||
return Result.fail(e);
|
||||
future.cancel(true);
|
||||
throw new UncheckedTimeoutException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException propagateCause(Exception e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause == null) {
|
||||
UncheckedExecutionException unchecked = new UncheckedExecutionException(e.getMessage()) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
};
|
||||
unchecked.setStackTrace(e.getStackTrace());
|
||||
throw unchecked;
|
||||
}
|
||||
StackTraceElement[] combined = concat(cause.getStackTrace(), e.getStackTrace(), StackTraceElement.class);
|
||||
cause.setStackTrace(combined);
|
||||
if (cause instanceof RuntimeException) {
|
||||
throw (RuntimeException) cause;
|
||||
}
|
||||
if (cause instanceof Error) {
|
||||
throw (Error) cause;
|
||||
}
|
||||
// The cause is a weird kind of Throwable, so throw the outer exception.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// override timeout by values configured in properties(in ms)
|
||||
private Optional<Long> timeoutInNanos(Invokable<?, ?> invoked, Map<String, Long> timeouts) {
|
||||
String className = enclosingType.getRawType().getSimpleName();
|
||||
|
|
|
@ -28,8 +28,8 @@ import static com.google.common.base.Throwables.getCausalChain;
|
|||
import static com.google.common.collect.Iterables.any;
|
||||
import static com.google.common.hash.Hashing.md5;
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.crypto.SshKeys.sha1PrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.sha1PrivateKey;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
|
|
|
@ -24,8 +24,8 @@ import org.jclouds.Constants;
|
|||
import org.jclouds.domain.Credentials;
|
||||
import org.jclouds.domain.LoginCredentials;
|
||||
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||
import org.jclouds.ssh.ConfiguresSshClient;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.config.ConfiguresSshClient;
|
||||
import org.jclouds.ssh.jsch.JschSshClient;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
|
|
|
@ -28,8 +28,8 @@ import static com.google.common.base.Throwables.getCausalChain;
|
|||
import static com.google.common.collect.Iterables.any;
|
||||
import static com.google.common.hash.Hashing.md5;
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static org.jclouds.crypto.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.crypto.SshKeys.sha1PrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
|
||||
import static org.jclouds.ssh.SshKeys.sha1PrivateKey;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FilterInputStream;
|
||||
|
|
|
@ -24,8 +24,8 @@ import org.jclouds.Constants;
|
|||
import org.jclouds.domain.Credentials;
|
||||
import org.jclouds.domain.LoginCredentials;
|
||||
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||
import org.jclouds.ssh.ConfiguresSshClient;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.config.ConfiguresSshClient;
|
||||
import org.jclouds.sshj.SshjSshClient;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
|
|
|
@ -28,12 +28,12 @@ import javax.inject.Singleton;
|
|||
|
||||
import org.jclouds.compute.functions.GroupNamingConvention;
|
||||
import org.jclouds.compute.reference.ComputeServiceConstants;
|
||||
import org.jclouds.crypto.SshKeyPairGenerator;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.JoyentCloudApi;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.compute.internal.KeyAndPrivateKey;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.Key;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.datacenterscoped.DatacenterAndName;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.ssh.SshKeyPairGenerator;
|
||||
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.inject.Inject;
|
||||
|
|
|
@ -28,7 +28,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import org.jclouds.compute.ComputeService;
|
||||
import org.jclouds.compute.domain.NodeMetadata;
|
||||
import org.jclouds.compute.options.TemplateOptions;
|
||||
import org.jclouds.crypto.SshKeyPairGenerator;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.compute.internal.BaseJoyentCloudComputeServiceExpectTest;
|
||||
|
@ -37,6 +36,7 @@ import org.jclouds.joyent.cloudapi.v6_5.features.DatasetApiExpectTest;
|
|||
import org.jclouds.joyent.cloudapi.v6_5.features.MachineApiExpectTest;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.features.PackageApiExpectTest;
|
||||
import org.jclouds.location.reference.LocationConstants;
|
||||
import org.jclouds.ssh.SshKeyPairGenerator;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
|
|
@ -31,12 +31,12 @@ import java.util.Map;
|
|||
|
||||
import org.jclouds.compute.functions.GroupNamingConvention;
|
||||
import org.jclouds.compute.functions.GroupNamingConvention.Factory;
|
||||
import org.jclouds.crypto.SshKeyPairGenerator;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.JoyentCloudApi;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.compute.internal.KeyAndPrivateKey;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.Key;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.datacenterscoped.DatacenterAndName;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.features.KeyApi;
|
||||
import org.jclouds.ssh.SshKeyPairGenerator;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
|
@ -22,10 +22,10 @@ import static org.testng.Assert.assertEquals;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.Key;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.features.KeyApi;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.internal.BaseJoyentCloudApiLiveTest;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import java.util.Set;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jclouds.compute.domain.ExecResponse;
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.domain.LoginCredentials;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.Key;
|
||||
import org.jclouds.joyent.cloudapi.v6_5.domain.Machine;
|
||||
|
@ -37,6 +36,7 @@ import org.jclouds.joyent.cloudapi.v6_5.reference.Metadata;
|
|||
import org.jclouds.predicates.InetSocketAddressConnect;
|
||||
import org.jclouds.predicates.RetryablePredicate;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.sshj.config.SshjSshClientModule;
|
||||
import org.jclouds.util.InetAddresses2;
|
||||
import org.testng.annotations.AfterGroups;
|
||||
|
|
|
@ -38,9 +38,9 @@ import java.net.URISyntaxException;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.json.Json;
|
||||
import org.jclouds.ssh.SshKeys;
|
||||
import org.jclouds.vcloud.director.v1_5.VCloudDirectorMediaType;
|
||||
import org.jclouds.vcloud.director.v1_5.domain.Checks;
|
||||
import org.jclouds.vcloud.director.v1_5.domain.File;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
package org.jclouds.aws.ec2.functions;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.jclouds.crypto.SshKeys.fingerprintPublicKey;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprintPublicKey;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
|
|
|
@ -22,7 +22,7 @@ import static org.easymock.EasyMock.createMock;
|
|||
import static org.easymock.EasyMock.expect;
|
||||
import static org.easymock.EasyMock.replay;
|
||||
import static org.easymock.EasyMock.verify;
|
||||
import static org.jclouds.crypto.SshKeys.fingerprintPublicKey;
|
||||
import static org.jclouds.ssh.SshKeys.fingerprintPublicKey;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import org.jclouds.aws.ec2.AWSEC2Client;
|
||||
|
|
|
@ -21,30 +21,30 @@ package org.jclouds.scriptbuilder.statements.login;
|
|||
import static com.google.common.base.Charsets.UTF_8;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Predicates.isNull;
|
||||
import static com.google.common.base.Throwables.propagate;
|
||||
import static com.google.common.collect.Iterables.any;
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static org.jclouds.scriptbuilder.statements.login.ShadowStatements.resetLoginUserPasswordTo;
|
||||
import static org.jclouds.scriptbuilder.statements.ssh.SshStatements.lockSshd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.crypto.Sha512Crypt;
|
||||
import org.jclouds.domain.Credentials;
|
||||
import org.jclouds.javax.annotation.Nullable;
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.StatementList;
|
||||
import org.jclouds.scriptbuilder.statements.ssh.SshStatements;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.ImplementedBy;
|
||||
|
||||
/**
|
||||
* Controls the administrative access to a node. By default, it will perform the following:
|
||||
|
@ -74,19 +74,14 @@ import com.google.inject.ImplementedBy;
|
|||
* @author Adrian Cole
|
||||
*/
|
||||
public class AdminAccess implements Statement {
|
||||
public static AdminAccess.Builder builder() {
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static AdminAccess.Builder builder(Function<String, String> cryptFunction) {
|
||||
return new Builder(cryptFunction);
|
||||
}
|
||||
|
||||
public static AdminAccess standard() {
|
||||
return new Builder().build();
|
||||
}
|
||||
|
||||
@ImplementedBy(DefaultConfiguration.class)
|
||||
public static interface Configuration {
|
||||
Supplier<String> defaultAdminUsername();
|
||||
|
||||
|
@ -98,16 +93,7 @@ public class AdminAccess implements Statement {
|
|||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Function<String, String> cryptFunction;
|
||||
|
||||
public Builder() {
|
||||
this(Sha512Crypt.function());
|
||||
}
|
||||
|
||||
public Builder(Function<String, String> cryptFunction) {
|
||||
this.cryptFunction = cryptFunction;
|
||||
}
|
||||
|
||||
private Function<String, String> cryptFunction;
|
||||
private String adminUsername;
|
||||
private String adminFullName;
|
||||
private String adminHome;
|
||||
|
@ -123,86 +109,91 @@ public class AdminAccess implements Statement {
|
|||
private boolean installAdminPrivateKey = false;
|
||||
private boolean resetLoginPassword = true;
|
||||
|
||||
public AdminAccess.Builder adminUsername(String adminUsername) {
|
||||
this.adminUsername = adminUsername;
|
||||
public Builder cryptFunction(Function<String, String> cryptFunction) {
|
||||
this.cryptFunction = cryptFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder adminFullName(String adminFullName) {
|
||||
public Builder adminUsername(String adminUsername) {
|
||||
this.adminUsername = checkNotRoot(adminUsername);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder adminFullName(String adminFullName) {
|
||||
this.adminFullName = adminFullName;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public AdminAccess.Builder adminHome(String adminHome) {
|
||||
public Builder adminHome(String adminHome) {
|
||||
this.adminHome = adminHome;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder adminPassword(String adminPassword) {
|
||||
public Builder adminPassword(String adminPassword) {
|
||||
this.adminPassword = adminPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder loginPassword(String loginPassword) {
|
||||
public Builder loginPassword(String loginPassword) {
|
||||
this.loginPassword = loginPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder lockSsh(boolean lockSsh) {
|
||||
public Builder lockSsh(boolean lockSsh) {
|
||||
this.lockSsh = lockSsh;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder resetLoginPassword(boolean resetLoginPassword) {
|
||||
public Builder resetLoginPassword(boolean resetLoginPassword) {
|
||||
this.resetLoginPassword = resetLoginPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder authorizeAdminPublicKey(boolean authorizeAdminPublicKey) {
|
||||
public Builder authorizeAdminPublicKey(boolean authorizeAdminPublicKey) {
|
||||
this.authorizeAdminPublicKey = authorizeAdminPublicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder installAdminPrivateKey(boolean installAdminPrivateKey) {
|
||||
public Builder installAdminPrivateKey(boolean installAdminPrivateKey) {
|
||||
this.installAdminPrivateKey = installAdminPrivateKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder grantSudoToAdminUser(boolean grantSudoToAdminUser) {
|
||||
public Builder grantSudoToAdminUser(boolean grantSudoToAdminUser) {
|
||||
this.grantSudoToAdminUser = grantSudoToAdminUser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder adminPublicKey(File adminPublicKey) {
|
||||
public Builder adminPublicKey(File adminPublicKey) {
|
||||
this.adminPublicKeyFile = adminPublicKey;
|
||||
this.adminPublicKey = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder adminPublicKey(String adminPublicKey) {
|
||||
public Builder adminPublicKey(String adminPublicKey) {
|
||||
this.adminPublicKey = adminPublicKey;
|
||||
this.adminPublicKeyFile = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder adminPrivateKey(File adminPrivateKey) {
|
||||
public Builder adminPrivateKey(File adminPrivateKey) {
|
||||
this.adminPrivateKeyFile = adminPrivateKey;
|
||||
this.adminPrivateKey = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder adminPrivateKey(String adminPrivateKey) {
|
||||
public Builder adminPrivateKey(String adminPrivateKey) {
|
||||
this.adminPrivateKey = adminPrivateKey;
|
||||
this.adminPrivateKeyFile = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminAccess.Builder from(AdminAccessBuilderSpec spec) {
|
||||
public Builder from(AdminAccessBuilderSpec spec) {
|
||||
return spec.copyTo(this);
|
||||
}
|
||||
|
||||
public AdminAccess.Builder from(String spec) {
|
||||
public Builder from(String spec) {
|
||||
return from(AdminAccessBuilderSpec.parse(spec));
|
||||
}
|
||||
|
||||
|
@ -222,7 +213,7 @@ public class AdminAccess implements Statement {
|
|||
lockSsh, grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword,
|
||||
cryptFunction);
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
throw propagate(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +370,8 @@ public class AdminAccess implements Statement {
|
|||
}
|
||||
|
||||
public AdminAccess init(Configuration configuration) {
|
||||
Builder builder = AdminAccess.builder(configuration.cryptFunction());
|
||||
Builder builder = AdminAccess.builder();
|
||||
builder.cryptFunction(config.getCryptFunction() != null ? config.getCryptFunction() : configuration.cryptFunction());
|
||||
builder.adminUsername(config.getAdminUsername() != null ? config.getAdminUsername() : configuration
|
||||
.defaultAdminUsername().get());
|
||||
builder.adminFullName(config.getAdminFullName() != null ? config.getAdminFullName() : builder.adminUsername);
|
||||
|
@ -407,14 +399,10 @@ public class AdminAccess implements Statement {
|
|||
checkNotNull(family, "family");
|
||||
if (family == OsFamily.WINDOWS)
|
||||
throw new UnsupportedOperationException("windows not yet implemented");
|
||||
checkArgument(!"root".equals(config.getAdminUsername()), "cannot create admin user 'root'; " +
|
||||
"ensure jclouds is not running as root, or specify an explicit non-root username in AdminAccess");
|
||||
if (Iterables.any(
|
||||
Lists.newArrayList(config.getAdminUsername(), config.getAdminPassword(), config.getAdminPublicKey(),
|
||||
config.getAdminPrivateKey(), config.getLoginPassword()), Predicates.isNull()))
|
||||
init(new DefaultConfiguration());
|
||||
|
||||
checkNotNull(config.getAdminUsername(), "adminUsername");
|
||||
checkState(
|
||||
!any(newArrayList(config.getAdminUsername(), config.getAdminPassword(), config.getAdminPublicKey(),
|
||||
config.getAdminPrivateKey(), config.getLoginPassword()), isNull()), "please call init() first");
|
||||
checkNotRoot(config.getAdminUsername());
|
||||
checkNotNull(config.getAdminPassword(), "adminPassword");
|
||||
checkNotNull(config.getAdminPublicKey(), "adminPublicKey");
|
||||
checkNotNull(config.getAdminPrivateKey(), "adminPrivateKey");
|
||||
|
@ -435,16 +423,21 @@ public class AdminAccess implements Statement {
|
|||
statements.add(SudoStatements.createWheel());
|
||||
userBuilder.group("wheel");
|
||||
}
|
||||
statements.add(userBuilder.build().cryptFunction(config.getCryptFunction()));
|
||||
statements.add(userBuilder.cryptFunction(config.getCryptFunction()).build());
|
||||
if (config.shouldLockSsh())
|
||||
statements.add(SshStatements.lockSshd());
|
||||
statements.add(lockSshd());
|
||||
if (config.shouldResetLoginPassword()) {
|
||||
statements.add(ShadowStatements.resetLoginUserPasswordTo(config.getLoginPassword()).cryptFunction(
|
||||
config.getCryptFunction()));
|
||||
statements.add(resetLoginUserPasswordTo(config.getCryptFunction(), config.getLoginPassword()));
|
||||
}
|
||||
return new StatementList(statements.build()).render(family);
|
||||
}
|
||||
|
||||
private static String checkNotRoot(String user) {
|
||||
checkArgument(!"root".equals(checkNotNull(user, "adminUsername")), "cannot create admin user 'root'; "
|
||||
+ "ensure jclouds is not running as root, or specify an explicit non-root username in AdminAccess");
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder2 = new StringBuilder();
|
||||
|
@ -453,6 +446,4 @@ public class AdminAccess implements Statement {
|
|||
.append(shouldGrantSudoToAdminUser()).append("]");
|
||||
return builder2.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds 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.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import static com.google.common.base.Charsets.UTF_8;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.crypto.Sha512Crypt;
|
||||
import org.jclouds.crypto.SshKeys;
|
||||
import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*
|
||||
*/
|
||||
@Singleton
|
||||
public class DefaultConfiguration implements Configuration {
|
||||
|
||||
private final Supplier<String> defaultAdminUsername = Suppliers.ofInstance(System.getProperty("user.name"));
|
||||
private final Supplier<Map<String, String>> defaultAdminSshKeys = new Supplier<Map<String, String>>() {
|
||||
|
||||
@Override
|
||||
public Map<String, String> get() {
|
||||
try {
|
||||
return ImmutableMap.of(
|
||||
"public", Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa.pub"), UTF_8),
|
||||
"private", Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa"), UTF_8));
|
||||
} catch (IOException e) {
|
||||
return SshKeys.generate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cheap, lightweight, low-security password generator.
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://www.java-forums.org/java-lang/7355-how-create-lightweight-low-security-password-generator.html" />
|
||||
*/
|
||||
public enum PasswordGenerator implements Supplier<String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/** Minimum length for a decent password */
|
||||
public static final int MIN_LENGTH = 10;
|
||||
|
||||
/** The random number generator. */
|
||||
protected static final SecureRandom r = new SecureRandom();
|
||||
|
||||
/*
|
||||
* Set of characters that is valid. Must be printable, memorable, and "won't break HTML" (i.e., not ' <', '>',
|
||||
* '&', '=', ...). or break shell commands (i.e., not ' <', '>', '$', '!', ...). I, L and O are good to leave out,
|
||||
* as are numeric zero and one.
|
||||
*/
|
||||
public static final char[] goodChar = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q',
|
||||
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-',
|
||||
'@', };
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < MIN_LENGTH; i++) {
|
||||
sb.append(goodChar[r.nextInt(goodChar.length)]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final Function<String, String> cryptFunction = Sha512Crypt.function();
|
||||
|
||||
@Override
|
||||
public Supplier<String> defaultAdminUsername() {
|
||||
return defaultAdminUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Map<String, String>> defaultAdminSshKeys() {
|
||||
return defaultAdminSshKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<String> passwordGenerator() {
|
||||
return PasswordGenerator.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<String, String> cryptFunction() {
|
||||
return cryptFunction;
|
||||
}
|
||||
}
|
|
@ -23,17 +23,12 @@ import static com.google.common.base.Throwables.propagate;
|
|||
import static java.lang.String.format;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.exec;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.crypto.Sha512Crypt;
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.StatementList;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
/**
|
||||
* Replaces the password entry for a user in the shadow file, using SHA-512
|
||||
|
@ -43,20 +38,12 @@ import com.google.inject.Inject;
|
|||
*/
|
||||
public class ReplaceShadowPasswordEntry implements Statement {
|
||||
|
||||
private final Function<String, String> cryptFunction;
|
||||
private final String login;
|
||||
private final String password;
|
||||
|
||||
private Function<String, String> cryptFunction = Sha512Crypt.function();
|
||||
|
||||
@Inject(optional = true)
|
||||
@Named("CRYPT")
|
||||
@VisibleForTesting
|
||||
ReplaceShadowPasswordEntry cryptFunction(Function<String, String> cryptFunction) {
|
||||
this.cryptFunction = cryptFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplaceShadowPasswordEntry(String login, String password) {
|
||||
public ReplaceShadowPasswordEntry(Function<String, String> cryptFunction, String login, String password) {
|
||||
this.cryptFunction = checkNotNull(cryptFunction, "cryptFunction");
|
||||
this.login = checkNotNull(login, "login");
|
||||
this.password = checkNotNull(password, "password");
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
package org.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -25,8 +27,8 @@ package org.jclouds.scriptbuilder.statements.login;
|
|||
*/
|
||||
public class ReplaceShadowPasswordEntryOfLoginUser extends ReplaceShadowPasswordEntry {
|
||||
|
||||
public ReplaceShadowPasswordEntryOfLoginUser(String password) {
|
||||
super("${SUDO_USER:=${USER}}", password);
|
||||
public ReplaceShadowPasswordEntryOfLoginUser(Function<String, String> cryptFunction, String password) {
|
||||
super(cryptFunction, "${SUDO_USER:=${USER}}", password);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
package org.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* Statements used to manipulate the shadow file
|
||||
|
@ -27,10 +28,10 @@ package org.jclouds.scriptbuilder.statements.login;
|
|||
public class ShadowStatements {
|
||||
|
||||
/**
|
||||
* note must be run as root, and will either reset the root password, or
|
||||
* whoever sudoed to root.
|
||||
* note must be run as root, and will either reset the root password, or whoever sudoed to root.
|
||||
*/
|
||||
public static ReplaceShadowPasswordEntryOfLoginUser resetLoginUserPasswordTo(String password) {
|
||||
return new ReplaceShadowPasswordEntryOfLoginUser(password);
|
||||
public static ReplaceShadowPasswordEntryOfLoginUser resetLoginUserPasswordTo(Function<String, String> cryptFunction,
|
||||
String password) {
|
||||
return new ReplaceShadowPasswordEntryOfLoginUser(cryptFunction, password);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,17 +18,11 @@
|
|||
*/
|
||||
package org.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import org.jclouds.crypto.Sha512Crypt;
|
||||
import static com.google.common.base.Objects.equal;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jclouds.javax.annotation.Nullable;
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
|
@ -37,11 +31,14 @@ import org.jclouds.scriptbuilder.domain.Statements;
|
|||
import org.jclouds.scriptbuilder.statements.ssh.AuthorizeRSAPublicKeys;
|
||||
import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Objects.equal;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Creates a statement that will add a given user to a machine ("login"), with optional
|
||||
|
@ -60,6 +57,7 @@ public class UserAdd implements Statement {
|
|||
}
|
||||
|
||||
public static class Builder {
|
||||
private Function<String, String> cryptFunction;
|
||||
private String defaultHome = "/home/users";
|
||||
private String home;
|
||||
private String login;
|
||||
|
@ -70,6 +68,14 @@ public class UserAdd implements Statement {
|
|||
private String shell = "/bin/bash";
|
||||
private String fullName;
|
||||
|
||||
/**
|
||||
* @see org.jclouds.compute.functions.Sha512Crypt
|
||||
*/
|
||||
public Builder cryptFunction(Function<String, String> cryptFunction) {
|
||||
this.cryptFunction = cryptFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* See --home in `man useradd`.
|
||||
*/
|
||||
|
@ -132,20 +138,26 @@ public class UserAdd implements Statement {
|
|||
this.fullName = fullName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserAdd build() {
|
||||
return new UserAdd(login, groups, password, RSAPrivateKey, authorizeRSAPublicKeys, home, defaultHome, shell, fullName);
|
||||
return new UserAdd(cryptFunction, login, groups, password, RSAPrivateKey, authorizeRSAPublicKeys, home,
|
||||
defaultHome, shell, fullName);
|
||||
}
|
||||
}
|
||||
|
||||
public UserAdd(String login, List<String> groups, @Nullable String password, @Nullable String installRSAPrivateKey,
|
||||
List<String> authorizeRSAPublicKeys, String defaultHome, String shell) {
|
||||
this(login, groups, password, installRSAPrivateKey, authorizeRSAPublicKeys, null, defaultHome, shell, login);
|
||||
public UserAdd(Function<String, String> cryptFunction, String login, List<String> groups, @Nullable String password,
|
||||
@Nullable String installRSAPrivateKey, List<String> authorizeRSAPublicKeys, String defaultHome, String shell) {
|
||||
this(cryptFunction, login, groups, password, installRSAPrivateKey, authorizeRSAPublicKeys, null, defaultHome,
|
||||
shell, login);
|
||||
}
|
||||
|
||||
public UserAdd(String login, List<String> groups, @Nullable String password, @Nullable String installRSAPrivateKey,
|
||||
List<String> authorizeRSAPublicKeys, @Nullable String home, String defaultHome, String shell, String fullName) {
|
||||
public UserAdd(Function<String, String> cryptFunction, String login, List<String> groups, @Nullable String password,
|
||||
@Nullable String installRSAPrivateKey, List<String> authorizeRSAPublicKeys, @Nullable String home,
|
||||
String defaultHome, String shell, String fullName) {
|
||||
this.login = checkNotNull(login, "login");
|
||||
this.password = password;
|
||||
this.cryptFunction = password == null ? null : checkNotNull(cryptFunction,
|
||||
"cryptFunction must be set! ex. org.jclouds.compute.functions.Sha512Crypt.INSTANCE");
|
||||
this.groups = ImmutableList.copyOf(checkNotNull(groups, "groups"));
|
||||
this.installRSAPrivateKey = installRSAPrivateKey;
|
||||
this.authorizeRSAPublicKeys = ImmutableList
|
||||
|
@ -156,6 +168,7 @@ public class UserAdd implements Statement {
|
|||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
private final Function<String, String> cryptFunction;
|
||||
private final String home;
|
||||
private final String defaultHome;
|
||||
private final String login;
|
||||
|
@ -166,16 +179,6 @@ public class UserAdd implements Statement {
|
|||
private final String shell;
|
||||
private final String fullName;
|
||||
|
||||
private Function<String, String> cryptFunction = Sha512Crypt.function();
|
||||
|
||||
@Inject(optional = true)
|
||||
@Named("CRYPT")
|
||||
@VisibleForTesting
|
||||
UserAdd cryptFunction(Function<String, String> cryptFunction) {
|
||||
this.cryptFunction = cryptFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> functionDependencies(OsFamily family) {
|
||||
return ImmutableList.of();
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -50,8 +49,7 @@ public class AdminAccessTest {
|
|||
try {
|
||||
assertEquals(
|
||||
AdminAccess.builder().adminPassword("bar").adminPrivateKey("fooPrivateKey")
|
||||
.adminPublicKey("fooPublicKey").adminUsername("foo")
|
||||
.adminHome("/over/ridden/foo").build()
|
||||
.adminPublicKey("fooPublicKey").adminUsername("foo").adminHome("/over/ridden/foo").build()
|
||||
.init(TestConfiguration.INSTANCE).render(OsFamily.UNIX),
|
||||
Resources.toString(Resources.getResource("test_adminaccess_params.sh"), Charsets.UTF_8));
|
||||
|
||||
|
@ -66,8 +64,7 @@ public class AdminAccessTest {
|
|||
assertEquals(
|
||||
AdminAccess.builder().adminPassword("bar").adminPrivateKey("fooPrivateKey")
|
||||
.adminPublicKey("fooPublicKey").adminUsername("foo").adminFullName("JClouds Foo")
|
||||
.adminHome("/over/ridden/foo").build()
|
||||
.init(TestConfiguration.INSTANCE).render(OsFamily.UNIX),
|
||||
.adminHome("/over/ridden/foo").build().init(TestConfiguration.INSTANCE).render(OsFamily.UNIX),
|
||||
Resources.toString(Resources.getResource("test_adminaccess_params_and_fullname.sh"), Charsets.UTF_8));
|
||||
|
||||
} finally {
|
||||
|
@ -75,7 +72,6 @@ public class AdminAccessTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public void testOnlyInstallUserUNIX() throws IOException {
|
||||
TestConfiguration.INSTANCE.reset();
|
||||
try {
|
||||
|
@ -89,14 +85,13 @@ public class AdminAccessTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = UnsupportedOperationException.class)
|
||||
@Test(expectedExceptions = UnsupportedOperationException.class, expectedExceptionsMessageRegExp = "windows not yet implemented")
|
||||
public void testCreateWheelWindowsNotSupported() {
|
||||
AdminAccess.standard().init(TestConfiguration.INSTANCE).render(OsFamily.WINDOWS);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
// for issue 682
|
||||
public void testRootNotAllowed() throws IOException {
|
||||
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "cannot create admin user 'root'; ensure jclouds is not running as root, or specify an explicit non-root username in AdminAccess")
|
||||
public void testRootNotAllowed() {
|
||||
TestConfiguration.INSTANCE.reset();
|
||||
try {
|
||||
AdminAccess.builder().adminUsername("root").build().init(TestConfiguration.INSTANCE).render(OsFamily.UNIX);
|
||||
|
@ -105,12 +100,13 @@ public class AdminAccessTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = NullPointerException.class)
|
||||
public void testFamilyRequiredAllowed() throws IOException {
|
||||
@Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "family")
|
||||
public void testFamilyRequiredAllowed() {
|
||||
AdminAccess.standard().render(null);
|
||||
}
|
||||
|
||||
public void testWhenUninitializedLazyInitWithDefaultConfiguration() throws IOException {
|
||||
@Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "please call init\\(\\) first")
|
||||
public void testIllegalStateExceptionUnlessCalledInit() {
|
||||
AdminAccess access = AdminAccess.standard();
|
||||
// before rendered, holder is empty
|
||||
assertEquals(access.config.getAdminUsername(), null);
|
||||
|
@ -119,20 +115,6 @@ public class AdminAccessTest {
|
|||
assertEquals(access.config.getAdminPrivateKey(), null);
|
||||
assertEquals(access.config.getLoginPassword(), null);
|
||||
access.render(OsFamily.UNIX);
|
||||
// DefaultConfiguration
|
||||
try {
|
||||
assertEquals(access.config.getAdminUsername(), System.getProperty("user.name"));
|
||||
assertNotNull(access.config.getAdminPassword());
|
||||
assertNotNull(access.config.getAdminPublicKey());
|
||||
assertNotNull(access.config.getAdminPrivateKey());
|
||||
assertNotNull(access.config.getLoginPassword());
|
||||
} catch (AssertionError e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// we are catching throwables here, in case the test runner doesn't
|
||||
// have ssh keys setup
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,24 +18,34 @@
|
|||
*/
|
||||
package org.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Test(groups = "unit")
|
||||
public class ReplaceShadowPasswordEntryOfLoginUserTest {
|
||||
Function<String, String> crypt = new Function<String, String>() {
|
||||
public String apply(String in) {
|
||||
assertEquals(in, "password");
|
||||
return "CRYPT";
|
||||
}
|
||||
};
|
||||
|
||||
public void testWithPasswordUNIX() {
|
||||
String cmd = new ReplaceShadowPasswordEntryOfLoginUser("bar").render(OsFamily.UNIX);
|
||||
assert cmd.startsWith("awk -v user=^${SUDO_USER:=${USER}}: -v password='$6$") : cmd;
|
||||
String cmd = new ReplaceShadowPasswordEntryOfLoginUser(crypt, "password").render(OsFamily.UNIX);
|
||||
assert cmd.startsWith("awk -v user=^${SUDO_USER:=${USER}}: -v password='CRYPT") : cmd;
|
||||
assert cmd
|
||||
.endsWith("' 'BEGIN { FS=OFS=\":\" } $0 ~ user { $2 = password } 1' /etc/shadow >/etc/shadow.${SUDO_USER:=${USER}}\ntest -f /etc/shadow.${SUDO_USER:=${USER}} && mv /etc/shadow.${SUDO_USER:=${USER}} /etc/shadow\n") : cmd;
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = UnsupportedOperationException.class)
|
||||
public void testAddUserWindowsNotSupported() {
|
||||
new ReplaceShadowPasswordEntryOfLoginUser("password").render(OsFamily.WINDOWS);
|
||||
new ReplaceShadowPasswordEntryOfLoginUser(crypt, "password").render(OsFamily.WINDOWS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,24 +18,34 @@
|
|||
*/
|
||||
package org.jclouds.scriptbuilder.statements.login;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Test(groups = "unit")
|
||||
public class ReplaceShadowPasswordEntryTest {
|
||||
Function<String, String> crypt = new Function<String, String>() {
|
||||
public String apply(String in) {
|
||||
assertEquals(in, "password");
|
||||
return "CRYPT";
|
||||
}
|
||||
};
|
||||
|
||||
public void testWithPasswordUNIX() {
|
||||
String userAdd = new ReplaceShadowPasswordEntry("foo", "bar").render(OsFamily.UNIX);
|
||||
assert userAdd.startsWith("awk -v user=^foo: -v password='$6$") : userAdd;
|
||||
String userAdd = new ReplaceShadowPasswordEntry(crypt, "foo", "password").render(OsFamily.UNIX);
|
||||
assert userAdd.startsWith("awk -v user=^foo: -v password='CRYPT") : userAdd;
|
||||
assert userAdd
|
||||
.endsWith("' 'BEGIN { FS=OFS=\":\" } $0 ~ user { $2 = password } 1' /etc/shadow >/etc/shadow.foo\ntest -f /etc/shadow.foo && mv /etc/shadow.foo /etc/shadow\n") : userAdd;
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = UnsupportedOperationException.class)
|
||||
public void testAddUserWindowsNotSupported() {
|
||||
new ReplaceShadowPasswordEntry("user", "password").render(OsFamily.WINDOWS);
|
||||
new ReplaceShadowPasswordEntry(crypt, "user", "password").render(OsFamily.WINDOWS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.testng.Assert.assertEquals;
|
|||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
|
@ -58,9 +59,16 @@ public class UserAddTest {
|
|||
"mkdir -p /home/users\ngroupadd -f wheel\ngroupadd -f candy\nuseradd -c me -s /bin/bash -g wheel -G candy -m -d /home/users/me me\nchown -R me /home/users/me\n");
|
||||
}
|
||||
|
||||
Function<String, String> crypt = new Function<String, String>() {
|
||||
public String apply(String in) {
|
||||
assertEquals(in, "password");
|
||||
return "CRYPT";
|
||||
}
|
||||
};
|
||||
|
||||
public void testWithPasswordUNIX() {
|
||||
String userAdd = UserAdd.builder().login("me").password("foo").group("wheel").build().render(OsFamily.UNIX);
|
||||
assert userAdd.startsWith("mkdir -p /home/users\ngroupadd -f wheel\nuseradd -c me -s /bin/bash -g wheel -m -d /home/users/me -p '$6$") : userAdd;
|
||||
String userAdd = UserAdd.builder().cryptFunction(crypt).login("me").password("password").group("wheel").build().render(OsFamily.UNIX);
|
||||
assert userAdd.startsWith("mkdir -p /home/users\ngroupadd -f wheel\nuseradd -c me -s /bin/bash -g wheel -m -d /home/users/me -p 'CRYPT'") : userAdd;
|
||||
assert userAdd.endsWith("' me\nchown -R me /home/users/me\n") : userAdd;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue