/*
* 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 gobblin.aws;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.AbstractIdleService;
import com.typesafe.config.Config;
import gobblin.annotation.Alpha;
import gobblin.util.ExecutorsUtils;
/**
* Class for managing AWS login and credentials renewal.
*
* <p>
* This class makes use of {@link BasicAWSCredentials} and {@link BasicSessionCredentials} to
* manage renewal of Amazon AWS authentication.
* This class runs a scheduled login executor
* task to refresh the credentials upon specified renewal interval which is configurable.
* </p>
*
* @author Abhishek Tiwari
*/
@Alpha
public class AWSClusterSecurityManager extends AbstractIdleService {
private static final Logger LOGGER = LoggerFactory.getLogger(AWSClusterSecurityManager.class);
private final Config config;
private volatile String serviceAccessKey;
private volatile String serviceSecretKey;
private volatile boolean clientAssumeRole;
private volatile String clientRoleArn;
private volatile String clientExternalId;
private volatile String clientSessionId;
private volatile long lastRefreshTimeInMillis;
private volatile BasicAWSCredentials basicAWSCredentials;
private volatile BasicSessionCredentials basicSessionCredentials;
private final long refreshIntervalInMinutes;
private final ScheduledExecutorService loginExecutor;
public AWSClusterSecurityManager(Config config) {
this.config = config;
this.refreshIntervalInMinutes = config.getLong(GobblinAWSConfigurationKeys.CREDENTIALS_REFRESH_INTERVAL);
this.loginExecutor = Executors.newSingleThreadScheduledExecutor(
ExecutorsUtils.newThreadFactory(Optional.of(LOGGER), Optional.of("LoginExecutor")));
}
private void fetchLoginConfiguration() {
this.serviceAccessKey = config.getString(GobblinAWSConfigurationKeys.SERVICE_ACCESS_KEY);
this.serviceSecretKey = config.getString(GobblinAWSConfigurationKeys.SERVICE_SECRET_KEY);
this.clientAssumeRole = config.getBoolean(GobblinAWSConfigurationKeys.CLIENT_ASSUME_ROLE_KEY);
// If we are running on behalf of another AWS user, we need to fetch temporary credentials for a
// .. configured role
if (this.clientAssumeRole) {
this.clientRoleArn = config.getString(GobblinAWSConfigurationKeys.CLIENT_ROLE_ARN_KEY);
this.clientExternalId = config.getString(GobblinAWSConfigurationKeys.CLIENT_EXTERNAL_ID_KEY);
this.clientSessionId = config.getString(GobblinAWSConfigurationKeys.CLIENT_SESSION_ID_KEY);
}
}
@Override
protected void startUp()
throws Exception {
LOGGER.info("Starting the " + AWSClusterSecurityManager.class.getSimpleName());
LOGGER.info(
String.format("Scheduling the credentials refresh task with an interval of %d minute(s)",
this.refreshIntervalInMinutes));
// Schedule the login task
this.loginExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
login();
} catch (IOException ioe) {
LOGGER.error("Failed to login", ioe);
throw Throwables.propagate(ioe);
}
}
}, 0, this.refreshIntervalInMinutes, TimeUnit.MINUTES);
}
@Override
protected void shutDown()
throws Exception {
GobblinAWSUtils.shutdownExecutorService(this.getClass(), this.loginExecutor, LOGGER);
}
private void login() throws IOException {
// Refresh login configuration details from config
fetchLoginConfiguration();
// Primary AWS user login
this.basicAWSCredentials = new BasicAWSCredentials(this.serviceAccessKey, this.serviceSecretKey);
// If running on behalf of another AWS user,
// .. assume role as configured
if (this.clientAssumeRole) {
AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest()
.withRoleSessionName(this.clientSessionId)
.withExternalId(this.clientExternalId)
.withRoleArn(this.clientRoleArn);
final AWSSecurityTokenServiceClient stsClient = new AWSSecurityTokenServiceClient(this.basicAWSCredentials);
final AssumeRoleResult assumeRoleResult = stsClient.assumeRole(assumeRoleRequest);
this.basicSessionCredentials = new BasicSessionCredentials(
assumeRoleResult.getCredentials().getAccessKeyId(),
assumeRoleResult.getCredentials().getSecretAccessKey(),
assumeRoleResult.getCredentials().getSessionToken()
);
}
this.lastRefreshTimeInMillis = System.currentTimeMillis();
}
public long getLastRefreshTimeInMillis() {
return lastRefreshTimeInMillis;
}
public boolean isAssumeRoleEnabled() {
return this.clientAssumeRole;
}
public BasicAWSCredentials getBasicAWSCredentials() {
return this.basicAWSCredentials;
}
public BasicSessionCredentials getBasicSessionCredentials() {
if (!this.clientAssumeRole) {
throw new IllegalStateException("AWS Security manager is not configured to run on behalf of another AWS user. "
+ "Use getBasicAWSCredentials() instead");
}
return this.basicSessionCredentials;
}
}