package com.sequenceiq.cloudbreak.cloud.aws;
import static com.sequenceiq.cloudbreak.cloud.model.CloudCredential.SMART_SENSE_ID;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNoneEmpty;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DeleteKeyPairRequest;
import com.amazonaws.services.ec2.model.DescribeKeyPairsRequest;
import com.amazonaws.services.ec2.model.ImportKeyPairRequest;
import com.sequenceiq.cloudbreak.cloud.CredentialConnector;
import com.sequenceiq.cloudbreak.cloud.aws.view.AwsCredentialView;
import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext;
import com.sequenceiq.cloudbreak.cloud.context.CloudContext;
import com.sequenceiq.cloudbreak.cloud.credential.CredentialNotifier;
import com.sequenceiq.cloudbreak.cloud.model.CloudCredential;
import com.sequenceiq.cloudbreak.cloud.model.CloudCredentialStatus;
import com.sequenceiq.cloudbreak.cloud.model.CredentialStatus;
import com.sequenceiq.cloudbreak.cloud.model.ExtendedCloudCredential;
@Service
public class AwsCredentialConnector implements CredentialConnector {
private static final Logger LOGGER = LoggerFactory.getLogger(AwsCredentialConnector.class);
@Inject
private AwsSessionCredentialClient credentialClient;
@Inject
private AwsClient awsClient;
@Inject
private AwsSmartSenseIdGenerator smartSenseIdGenerator;
@Override
public CloudCredentialStatus verify(AuthenticatedContext authenticatedContext) {
CloudCredential credential = authenticatedContext.getCloudCredential();
LOGGER.info("Create credential: {}", credential);
AwsCredentialView awsCredential = new AwsCredentialView(credential);
String roleArn = awsCredential.getRoleArn();
String accessKey = awsCredential.getAccessKey();
String secretKey = awsCredential.getSecretKey();
String smartSenseId = smartSenseIdGenerator.getSmartSenseId(awsCredential);
if (StringUtils.isNoneEmpty(smartSenseId)) {
credential.putParameter(SMART_SENSE_ID, smartSenseId);
}
if (isNoneEmpty(roleArn) && isNoneEmpty(accessKey) && isNoneEmpty(secretKey)) {
String message = "Please only provide the 'role arn' or the 'access' and 'secret key'";
return new CloudCredentialStatus(credential, CredentialStatus.FAILED, new Exception(message), message);
}
if (isNoneEmpty(roleArn)) {
return verifyIamRoleIsAssumable(credential);
}
if (isEmpty(accessKey) || isEmpty(secretKey)) {
String message = "Please provide both the 'access' and 'secret key'";
return new CloudCredentialStatus(credential, CredentialStatus.FAILED, new Exception(message), message);
}
return new CloudCredentialStatus(credential, CredentialStatus.VERIFIED);
}
@Override
public CloudCredentialStatus create(AuthenticatedContext auth) {
if (!awsClient.existingKeyPairNameSpecified(auth)) {
AwsCredentialView awsCredential = new AwsCredentialView(auth.getCloudCredential());
try {
String region = auth.getCloudContext().getLocation().getRegion().value();
LOGGER.info(String.format("Importing public key to %s region on AWS", region));
AmazonEC2Client client = awsClient.createAccess(awsCredential, region);
String keyPairName = awsClient.getKeyPairName(auth);
ImportKeyPairRequest importKeyPairRequest = new ImportKeyPairRequest(keyPairName, awsCredential.getPublicKey());
try {
client.describeKeyPairs(new DescribeKeyPairsRequest().withKeyNames(keyPairName));
LOGGER.info("Key-pair already exists: {}", keyPairName);
} catch (AmazonServiceException e) {
client.importKeyPair(importKeyPairRequest);
}
} catch (Exception e) {
String errorMessage = String.format("Failed to import public key [roleArn:'%s'], detailed message: %s", awsCredential.getRoleArn(),
e.getMessage());
LOGGER.error(errorMessage, e);
return new CloudCredentialStatus(auth.getCloudCredential(), CredentialStatus.FAILED, e, errorMessage);
}
}
return new CloudCredentialStatus(auth.getCloudCredential(), CredentialStatus.CREATED);
}
@Override
public Map<String, String> interactiveLogin(CloudContext cloudContext, ExtendedCloudCredential extendedCloudCredential,
CredentialNotifier credentialNotifier) {
throw new UnsupportedOperationException("Interactive login not supported on AWS");
}
@Override
public CloudCredentialStatus delete(AuthenticatedContext auth) {
AwsCredentialView awsCredential = new AwsCredentialView(auth.getCloudCredential());
String region = auth.getCloudContext().getLocation().getRegion().value();
if (!awsClient.existingKeyPairNameSpecified(auth)) {
try {
AmazonEC2Client client = awsClient.createAccess(awsCredential, region);
DeleteKeyPairRequest deleteKeyPairRequest = new DeleteKeyPairRequest(awsClient.getKeyPairName(auth));
client.deleteKeyPair(deleteKeyPairRequest);
} catch (Exception e) {
String errorMessage = String.format("Failed to delete public key [roleArn:'%s', region: '%s'], detailed message: %s",
awsCredential.getRoleArn(), region, e.getMessage());
LOGGER.error(errorMessage, e);
}
}
return new CloudCredentialStatus(auth.getCloudCredential(), CredentialStatus.DELETED);
}
private CloudCredentialStatus verifyIamRoleIsAssumable(CloudCredential cloudCredential) {
AwsCredentialView awsCredential = new AwsCredentialView(cloudCredential);
try {
credentialClient.retrieveSessionCredentials(awsCredential);
} catch (AmazonClientException ae) {
if (ae.getMessage().contains("Unable to load AWS credentials")) {
String errorMessage =
"Unable to load AWS credentials: please make sure the deployer defined AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY";
LOGGER.error(errorMessage, ae);
return new CloudCredentialStatus(cloudCredential, CredentialStatus.FAILED, ae, errorMessage);
}
} catch (Exception e) {
String errorMessage = String.format("Could not assume role '%s': check if the role exists and if it's created with the correct external ID",
awsCredential.getRoleArn());
LOGGER.error(errorMessage, e);
return new CloudCredentialStatus(cloudCredential, CredentialStatus.FAILED, e, errorMessage);
}
return new CloudCredentialStatus(cloudCredential, CredentialStatus.CREATED);
}
}