Search code examples
sshamazon-ec2jclouds

Setting user credentials on aws instance using jclouds


I am trying to create an aws instance using jclouds 1.9.0 and then run a script on it (via ssh). I am following the example locate here but I am getting authentication failed errors when the client (java program) tries to connect at the instance. The AWS console show that instance is up and running.

The example tries to create a LoginCrendentials object

String user = System.getProperty("user.name");
String privateKey = Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa"), UTF_8);
return LoginCredentials.builder().user(user).privateKey(privateKey).build();

which is latter used from the ssh client

responses = compute.runScriptOnNodesMatching(
                inGroup(groupName),             // predicate used to select nodes
                exec(command),                  // what you actually intend to run
                overrideLoginCredentials(login) // use my local user & ssh key
                        .runAsRoot(false)       // don't attempt to run as root (sudo)
                        .wrapInInitScript(false));

Some Login information are injected to the instance with following commands

Statement bootInstructions = AdminAccess.standard();
templateBuilder.options(runScript(bootInstructions));

Since I am on Windows machine the creation of LoginCrendentials 'fails' and thus I alter its code to

String user = "ec2-user";
String privateKey = "-----BEGIN RSA PRIVATE KEY-----.....-----END RSA PRIVATE KEY-----";
return  LoginCredentials.builder().user(user).privateKey(privateKey).build();

I also to define the credentials while building the template as described in "EC2: In Depth" guide but with no luck.

An alternative is to build instance and inject the keypair as follows, but this implies that I need to have the ssh key stored in my AWS console, which is not currently the case and also breaks the functionality of running a script (via ssh) since I can not infer the NodeMetadata from a RunningInstance object.

RunInstancesOptions options = RunInstancesOptions.Builder.asType("t2.micro").withKeyName(keypair).withSecurityGroup(securityGroup).withUserData(script.getBytes());

Any suggestions??

Note: While I am currently testing this on aws, I want to keep the code as decoupled from the provider as possible.

Update 26/10/2015

Based on @Ignasi Barrera answer, I changed my implementation by adding .init(new MyAdminAccessConfiguration()) while creating the bootInstructions

Statement bootInstructions = AdminAccess.standard().init(new MyAdminAccessConfiguration());
templateBuilder.options(runScript(bootInstructions));

Where MyAdminAccessConfiguration is my own implementation of the AdminAccessConfiguration interface as @Ignasi Barrera described it.


Solution

  • I think the issue relies on the fact that the jclouds code runs on a Windows machine and jclouds makes some Unix assumptions by default.

    There are two different things here: first, the AdminAccess.standard() is used to configure a user in the deployed node once it boots, and later the LoginCredentials object passed to the run script method is used to authenticate against the user that has been created with the previous statement.

    The issue here is that the AdminAccess.standard() reads the "current user" information and assumes a Unix System. That user information is provided by this Default class, and in your case I'm pretty sure it will fallback to the catch block and return an auto-generated SSH key pair. That means, the AdminAccess.standard() is creating a user in the node with an auto-generated (random) SSH key, but the LoginCredentials you are building don't match those keys, thus the authentication failure.

    Since the AdminAccess entity is immutable, the better and cleaner approach to fix this is to create your own implementation of the AdminAccessConfiguration interface. You can just copy the entire Default class and change the Unix specific bits to accommodate the SSH setup in your Windows machine. Once you have the implementation class, you can inject it by creating a Guice module and passing it to the list of modules provided when creating the jclouds context. Something like:

    // Create the custom module to inject your implementation
    Module windowsAdminAccess = new AbstractModule() {
       @Override protected void configure() {
          bind(AdminAccessConfiguration.class).to(YourCustomWindowsImpl.class).in(Scopes.SINGLETON);
       }
    };
    
    // Provide the module in the module list when creating the context
    ComputeServiceContext context = ContextBuilder.newBuilder("aws-ec2")
       .credentials("api-key", "api-secret")
       .modules(ImmutableSet.<Module> of(windowsAdminAccess, new SshjSshClientModule()))
       .buildView(ComputeServiceContext.class);