/*
* Copyright 2008-2012 Amazon Technologies, Inc.
*
* Licensed 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://aws.amazon.com/apache2.0
*
* This file 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 com.amazonaws.eclipse.ec2.keypairs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.statushandlers.StatusManager;
import com.amazonaws.AmazonClientException;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.regions.Region;
import com.amazonaws.eclipse.core.regions.ServiceAbbreviations;
import com.amazonaws.eclipse.ec2.Ec2Plugin;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.model.CreateKeyPairRequest;
import com.amazonaws.services.ec2.model.CreateKeyPairResult;
import com.amazonaws.services.ec2.model.KeyPair;
/**
* Manages the EC2 key pairs that the plugin knows about, including creating new
* key pairs, registering private key files with a named key, etc.
*/
public class KeyPairManager {
/** The suffix for private key files */
private static final String PRIVATE_KEY_SUFFIX = ".pem";
/**
* Returns the private key file associated with the named key, or null if no
* key file can be found.
*
* @param accountId
* The account id that owns the key name
* @param keyPairName
* The name of the key being looked up.
* @return The file path to the associated private key file for the named
* key.
*/
public String lookupKeyPairPrivateKeyFile(String accountId, String keyPairName) {
try {
Properties registeredKeyPairs = loadRegisteredKeyPairs(accountId);
String privateKeyPath = registeredKeyPairs.getProperty(keyPairName);
if ( privateKeyPath != null )
return privateKeyPath;
} catch ( IOException e ) {
Status status = new Status(Status.WARNING, Ec2Plugin.PLUGIN_ID,
"Unable to load registered key pairs file: " + e.getMessage(), e);
StatusManager.getManager().handle(status, StatusManager.LOG);
}
return null;
}
/**
* Returns true if and only if the specified key pair is valid, meaning it
* has a valid registered private key file.
*
* @param accountId
* The account id that owns the key name
* @param keyPairName
* The name of the key pair to check.
* @return True if and only if the specified key pair is valid, meaning it
* has a valid registered private key file.
*/
public boolean isKeyPairValid(String accountId, String keyPairName) {
try {
String keyFile = lookupKeyPairPrivateKeyFile(accountId, keyPairName);
if ( keyFile == null )
return false;
File f = new File(keyFile);
return f.isFile();
} catch ( Exception e ) {
// If we catch an exception, we know this must not be valid
}
return false;
}
/**
* Requests a new key pair from EC2 with the specified name, and saves the
* private key portion in the specified directory.
*
* @param accountId
* The account id that owns the key name
* @param keyPairName
* The name of the requested key pair.
* @param keyPairDirectory
* The directory in which to save the private key file.
* @param ec2RegionOverride
* The region where the EC2 key pair is created.
* @throws IOException
* If any problems were encountered storing the private key to
* disk.
* @throws AmazonClientException
* If any problems were encountered requesting a new key pair
* from EC2.
*/
public void createNewKeyPair(String accountId, String keyPairName, String keyPairDirectory, Region ec2RegionOverride) throws IOException,
AmazonClientException {
File keyPairDirectoryFile = new File(keyPairDirectory);
if ( !keyPairDirectoryFile.exists() ) {
if ( !keyPairDirectoryFile.mkdirs() ) {
throw new IOException("Unable to create directory: " + keyPairDirectory);
}
}
/**
* It's possible that customers could have two keys with the same name,
* so silently rename to avoid such a conflict. This isn't the most
* straightforward user interface, but probably better than enforced
* directory segregation by account, or else disallowing identical names
* across accounts.
*/
File privateKeyFile = new File(keyPairDirectoryFile, keyPairName + PRIVATE_KEY_SUFFIX);
int i = 1;
while ( privateKeyFile.exists() ) {
privateKeyFile = new File(keyPairDirectoryFile, keyPairName + "-" + i + PRIVATE_KEY_SUFFIX);
}
CreateKeyPairRequest request = new CreateKeyPairRequest();
request.setKeyName(keyPairName);
AmazonEC2 ec2 = null;
if (ec2RegionOverride == null) {
ec2 = Ec2Plugin.getDefault().getDefaultEC2Client();
} else {
ec2 = AwsToolkitCore.getClientFactory().getEC2ClientByEndpoint(ec2RegionOverride.getServiceEndpoint(ServiceAbbreviations.EC2));
}
CreateKeyPairResult response = ec2.createKeyPair(request);
KeyPair keyPair = response.getKeyPair();
String privateKey = keyPair.getKeyMaterial();
FileWriter writer = new FileWriter(privateKeyFile);
try {
writer.write(privateKey);
} finally {
writer.close();
}
registerKeyPair(accountId, keyPairName, privateKeyFile.getAbsolutePath());
/*
* SSH requires our private key be locked down.
*/
try {
/*
* TODO: We should model these platform differences better (and
* support windows).
*/
Runtime.getRuntime().exec("chmod 600 " + privateKeyFile.getAbsolutePath());
} catch ( IOException e ) {
Status status = new Status(Status.WARNING, Ec2Plugin.PLUGIN_ID,
"Unable to restrict permissions on private key file: " + e.getMessage(), e);
StatusManager.getManager().handle(status, StatusManager.LOG);
}
}
/**
* Requests a new key pair from EC2 with the specified name, and saves the
* private key portion in the specified directory.
*
* @param accountId
* The account id that owns the key name
* @param keyPairName
* The name of the requested key pair.
* @param keyPairDirectory
* The directory in which to save the private key file.
* @throws IOException
* If any problems were encountered storing the private key to
* disk.
* @throws AmazonClientException
* If any problems were encountered requesting a new key pair
* from EC2.
*/
public void createNewKeyPair(String accountId, String keyPairName, String keyPairDirectory) throws IOException, AmazonClientException {
createNewKeyPair(accountId, keyPairName, keyPairDirectory, null);
}
/**
* Returns the default directory where the plugin assumes private keys are
* stored.
*
* @return The default directory where the plugin assumes private keys are
* stored.
*/
public static File getDefaultPrivateKeyDirectory() {
String userHomeDir = System.getProperty("user.home");
if ( userHomeDir == null || userHomeDir.length() == 0 )
return null;
return new File(userHomeDir + File.separator + ".ec2");
}
/**
* Registers an existing key pair and private key file with this key pair
* manager. This method is only for *existing* key pairs. If you need a new
* key pair created, you should be using createNewKeyPair.
*
* @param accountId
* The account id that owns the key name
* @param keyName
* The name of the key being registered.
* @param privateKeyFile
* The path to the private key file for the specified key pair.
* @throws IOException
* If any problems are encountered adding the specified key pair
* to the mapping of registered key pairs.
*/
public void registerKeyPair(String accountId, String keyName, String privateKeyFile) throws IOException {
Properties registeredKeyPairs = loadRegisteredKeyPairs(accountId);
registeredKeyPairs.put(keyName, privateKeyFile);
storeRegisteredKeyPairs(accountId, registeredKeyPairs);
}
/**
* Attempts to convert any legacy private key files by renaming them.
*/
public static void convertLegacyPrivateKeyFiles() throws IOException {
String accountId = AwsToolkitCore.getDefault().getCurrentAccountId();
File pluginStateLocation = Ec2Plugin.getDefault().getStateLocation().toFile();
File keyPairsFile = new File(pluginStateLocation, getKeyPropertiesFileName(accountId));
if ( !keyPairsFile.exists() ) {
File legacyKeyPairsFile = new File(pluginStateLocation, "registeredKeyPairs.properties");
if ( legacyKeyPairsFile.exists() ) {
FileUtils.copyFile(legacyKeyPairsFile, keyPairsFile);
}
}
}
/*
* Private Interface
*/
private Properties loadRegisteredKeyPairs(String accountId) throws IOException {
/*
* If the plugin isn't running (such as during tests), just return an
* empty property list.
*/
Ec2Plugin plugin = Ec2Plugin.getDefault();
if ( plugin == null )
return new Properties();
/*
* TODO: we could optimize this and only load the registered key pairs
* on startup and after changes.
*/
File pluginStateLocation = plugin.getStateLocation().toFile();
File registeredKeyPairsFile = new File(pluginStateLocation, getKeyPropertiesFileName(accountId));
registeredKeyPairsFile.createNewFile();
FileInputStream fileInputStream = new FileInputStream(registeredKeyPairsFile);
try {
Properties registeredKeyPairs = new Properties();
registeredKeyPairs.load(fileInputStream);
return registeredKeyPairs;
} finally {
fileInputStream.close();
}
}
private void storeRegisteredKeyPairs(String accountId, Properties registeredKeyPairs) throws IOException {
File pluginStateLocation = Ec2Plugin.getDefault().getStateLocation().toFile();
File registeredKeyPairsFile = new File(pluginStateLocation, getKeyPropertiesFileName(accountId));
registeredKeyPairsFile.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(registeredKeyPairsFile);
try {
registeredKeyPairs.store(fileOutputStream, null);
} finally {
fileOutputStream.close();
}
}
private static String getKeyPropertiesFileName(String accountId) {
return "registeredKeyPairs." + accountId + ".properties";
}
}