bug4811: working towards MFA enablement

This commit is contained in:
Axel Uhl
2021-02-15 18:23:49 +01:00
parent aac2904611
commit 5e6bed337c
16 changed files with 271 additions and 39 deletions
@@ -125,4 +125,8 @@ echo "Patching SDK version in target platform definition ${TARGET_DEFINITION}...
sed -i -e 's/<unit id="com.amazon.aws.aws-java-api.feature.group" version="[0-9.]*"\/>/<unit id="com.amazon.aws.aws-java-api.feature.group" version="'${VERSION}'"\/>/' ${TARGET_DEFINITION}
echo "You may test your target platform locally by creating race-analysis-p2-local.target by running the script createLocalTargetDef.sh."
echo "You can also try a Hudson build with the -v option, generating and using the local target platform during the build."
echo "When all this works, update the P2 repository at p2.sapsailing.com using the script uploadAwsApiRepositoryToServer.sh."
echo "In this case, start with an unpatched remote target platform (race-analysis-p2-remote.target) and an unpatched"
echo "java/com.sap.sse.feature.runtime/feature.xml so that running this script during the Hudson build can resolve"
echo "the target platform."
echo "When all this works, commit and push the patched to race-analysis-p2-remote.target and feature.xml"
echo "and update the P2 repository at p2.sapsailing.com using the script uploadAwsApiRepositoryToServer.sh."
@@ -22,7 +22,8 @@ Require-Bundle: com.sap.sse.gwt,
com.sap.sse.landscape.common,
com.sap.sse.landscape.aws.common,
com.jcraft.jsch;bundle-version="0.1.54",
elemental2;bundle-version="1.1.0"
elemental2;bundle-version="1.1.0",
org.json.simple
Export-Package: com.google.gwt.user.client.rpc.core.com.sap.sse.landscape.aws.common.shared,
com.google.gwt.user.client.rpc.core.com.sap.sse.landscape.common.shared,
com.sap.sailing.landscape.ui.client,
@@ -37,4 +37,19 @@ public interface LandscapeManagementWriteService extends RemoteService {
AmazonMachineImageDTO upgradeAmazonMachineImage(String awsAccessKey, String awsSecret, String region, String machineImageId) throws Exception;
void scaleMongo(String awsAccessKey, String awsSecret, String region, MongoScalingInstructionsDTO mongoScalingInstructions) throws Exception;
/**
* For a combination of an AWS access key ID, the corresponding secret plus an MFA token code produces new session
* credentials and stores them in the user's preference store from where they can be obtained again using
* {@link #getSessionCredentials()}. Any session credentials previously stored in the current user's preference store
* will be overwritten by this. The current user must have the {@code LANDSCAPE:MANAGE:AWS} permission.
*/
void createMfaSessionCredentials(String awsAccessKey, String awsSecret, String mfaTokenCode);
/**
* For the current user who has to have the {@code LANDSCAPE:MANAGE:AWS} permission, clears the preference in the
* user's preference store which holds any session credentials created previously using
* {@link #createMfaSessionCredentials(String, String, String)}.
*/
void clearSessionCredentials();
}
@@ -57,4 +57,20 @@ public interface LandscapeManagementWriteServiceAsync {
void scaleMongo(String awsAccessKey, String awsSecret, String region,
MongoScalingInstructionsDTO mongoScalingInstructions, AsyncCallback<Void> asyncCallback);
/**
* For a combination of an AWS access key ID, the corresponding secret plus an MFA token code produces new session
* credentials and stores them in the user's preference store from where they can be obtained again using
* {@link #getSessionCredentials()}. Any session credentials previously stored in the current user's preference store
* will be overwritten by this. The current user must have the {@code LANDSCAPE:MANAGE:AWS} permission.
*/
void createMfaSessionCredentials(String awsAccessKey, String awsSecret, String mfaTokenCode,
AsyncCallback<Void> callback);
/**
* For the current user who has to have the {@code LANDSCAPE:MANAGE:AWS} permission, clears the preference in the
* user's preference store which holds any session credentials created previously using
* {@link #createMfaSessionCredentials(String, String, String)}.
*/
void clearSessionCredentials(AsyncCallback<Void> callback);
}
@@ -3,12 +3,19 @@ package com.sap.sailing.landscape.ui.impl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sailing.landscape.ui.shared.AwsSessionCredentialsFromUserPreference;
import com.sap.sse.security.interfaces.PreferenceConverter;
import com.sap.sse.security.util.GenericJSONPreferenceConverter;
public class Activator implements BundleActivator {
private static BundleContext context;
@Override
public void start(BundleContext context) throws Exception {
Activator.context = context;
context.registerService(PreferenceConverter.class,
new GenericJSONPreferenceConverter<>(() -> new AwsSessionCredentialsFromUserPreference()),
/* properties */ null);
}
@Override
@@ -15,6 +15,9 @@ import com.sap.sailing.landscape.procedures.UpgradeAmi;
import com.sap.sailing.landscape.ui.client.LandscapeManagementWriteService;
import com.sap.sailing.landscape.ui.impl.Activator;
import com.sap.sailing.landscape.ui.shared.AmazonMachineImageDTO;
import com.sap.sailing.landscape.ui.shared.AwsSessionCredentialsFromUserPreference;
import com.sap.sailing.landscape.ui.shared.AwsSessionCredentialsWithExpiry;
import com.sap.sailing.landscape.ui.shared.AwsSessionCredentialsWithExpiryImpl;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.MongoScalingInstructionsDTO;
import com.sap.sailing.landscape.ui.shared.SSHKeyPairDTO;
@@ -43,6 +46,7 @@ import com.sap.sse.security.ui.server.SecurityDTOUtil;
import software.amazon.awssdk.services.ec2.model.InstanceType;
import software.amazon.awssdk.services.ec2.model.KeyPairInfo;
import software.amazon.awssdk.services.sts.model.Credentials;
public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRemoteServiceServlet
implements LandscapeManagementWriteService {
@@ -51,6 +55,8 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
private static final Optional<Duration> IMAGE_UPGRADE_TIMEOUT = Optional.of(Duration.ONE_MINUTE.times(10));
private final FullyInitializedReplicableTracker<SecurityService> securityServiceTracker;
private static final String USER_PREFERENCE_FOR_SESSION_TOKEN = "___aws.session.token___";
public <ShardingKey, MetricsT extends ApplicationProcessMetrics,
ProcessT extends ApplicationProcess<ShardingKey, MetricsT, ProcessT>> LandscapeManagementWriteServiceImpl() {
@@ -66,10 +72,67 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
}
}
/**
* For the logged-in user checks the LANDSCAPE:MANAGE:AWS permission, and if present, tries to obtain the user preference
* named like {@link #USER_PREFERENCE_FOR_SESSION_TOKEN}. If found and not yet expired, they are returned. Otherwise,
* {@code null} is returned, indicating to the caller that new session credentials shall be obtained which shall then be
* stored to the user preference again for future reference.
*/
private AwsSessionCredentialsWithExpiry getSessionCredentials() {
final AwsSessionCredentialsWithExpiry result;
checkLandscapeManageAwsPermission();
final AwsSessionCredentialsFromUserPreference credentialsPreferences = getSecurityService().getPreferenceObject(
getSecurityService().getCurrentUser().getName(), USER_PREFERENCE_FOR_SESSION_TOKEN);
if (credentialsPreferences != null) {
final AwsSessionCredentialsWithExpiry credentials = credentialsPreferences.getAwsSessionCredentialsWithExpiry();
if (credentials.getExpiration().after(TimePoint.now())) {
result = null;
} else {
result = credentials;
}
} else {
result = null;
}
return result;
}
/**
* For a combination of an AWS access key ID, the corresponding secret plus an MFA token code produces new session
* credentials and stores them in the user's preference store from where they can be obtained again using
* {@link #getSessionCredentials()}. Any session credentials previously stored in the current user's preference store
* will be overwritten by this. The current user must have the {@code LANDSCAPE:MANAGE:AWS} permission.
*/
@Override
public ArrayList<String> getRegions() {
public void createMfaSessionCredentials(String awsAccessKey, String awsSecret, String mfaTokenCode) {
checkLandscapeManageAwsPermission();
final Credentials credentials = getLandscape(awsAccessKey, awsSecret).getMfaSessionCredentials(mfaTokenCode);
final AwsSessionCredentialsWithExpiryImpl result = new AwsSessionCredentialsWithExpiryImpl(
credentials.accessKeyId(), credentials.secretAccessKey(), credentials.sessionToken(),
TimePoint.of(credentials.expiration().toEpochMilli()));
final AwsSessionCredentialsFromUserPreference credentialsPreferences = new AwsSessionCredentialsFromUserPreference(result);
getSecurityService().setPreferenceObject(
getSecurityService().getCurrentUser().getName(), USER_PREFERENCE_FOR_SESSION_TOKEN, credentialsPreferences);
}
/**
* For the current user who has to have the {@code LANDSCAPE:MANAGE:AWS} permission, clears the preference in the
* user's preference store which holds any session credentials created previously using
* {@link #createMfaSessionCredentials(String, String, String)}.
*/
@Override
public void clearSessionCredentials() {
checkLandscapeManageAwsPermission();
getSecurityService().unsetPreference(getSecurityService().getCurrentUser().getName(), USER_PREFERENCE_FOR_SESSION_TOKEN);
}
private void checkLandscapeManageAwsPermission() {
SecurityUtils.getSubject().checkPermission(SecuredLandscapeTypes.LANDSCAPE.getStringPermissionForTypeRelativeIdentifier(SecuredLandscapeTypes.LandscapeActions.MANAGE,
new TypeRelativeObjectIdentifier("AWS")));
}
@Override
public ArrayList<String> getRegions() {
checkLandscapeManageAwsPermission();
final ArrayList<String> result = new ArrayList<>();
Util.addAll(Util.map(AwsLandscape.obtain().getRegions(), r->r.getId()), result);
return result;
@@ -84,10 +147,9 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public ArrayList<MongoEndpointDTO> getMongoEndpoints(String awsAccessKey, String awsSecret, String region) {
SecurityUtils.getSubject().checkPermission(SecuredLandscapeTypes.LANDSCAPE.getStringPermissionForTypeRelativeIdentifier(SecuredLandscapeTypes.LandscapeActions.MANAGE,
new TypeRelativeObjectIdentifier("AWS")));
checkLandscapeManageAwsPermission();
final ArrayList<MongoEndpointDTO> result = new ArrayList<>();
for (final MongoEndpoint mongoEndpoint : AwsLandscape.obtain(awsAccessKey, awsSecret).getMongoEndpoints(new AwsRegion(region))) {
for (final MongoEndpoint mongoEndpoint : getLandscape(awsAccessKey, awsSecret).getMongoEndpoints(new AwsRegion(region))) {
final MongoEndpointDTO dto;
if (mongoEndpoint.isReplicaSet()) {
final MongoReplicaSet replicaSet = mongoEndpoint.asMongoReplicaSet();
@@ -105,6 +167,23 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
return result;
}
private AwsLandscape<String> getLandscape(String awsAccessKey, String awsSecret) {
final String keyId;
final String secret;
final Optional<String> sessionToken;
final AwsSessionCredentialsWithExpiry sessionCredentials = getSessionCredentials();
if (sessionCredentials != null) {
keyId = sessionCredentials.getAccessKeyId();
secret = sessionCredentials.getSecretAccessKey();
sessionToken = Optional.of(sessionCredentials.getSessionToken());
} else {
keyId = awsAccessKey;
secret = awsSecret;
sessionToken = Optional.empty();
}
return AwsLandscape.obtain(keyId, secret, sessionToken);
}
@Override
public MongoEndpointDTO getMongoEndpoint(String awsAccessKey, String awsSecret, String region, String replicaSetName) {
return getMongoEndpoints(awsAccessKey, awsSecret, region).stream().filter(mep->Util.equalsWithNull(mep.getReplicaSetName(), replicaSetName)).findAny().orElse(null);
@@ -118,7 +197,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
final SSHKeyPair keyPair = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(dummyKeyPairForSecurityCheck.getPermissionType(),
dummyKeyPairForSecurityCheck.getIdentifier().getTypeRelativeObjectIdentifier(), keyName,
()->{
return AwsLandscape.obtain(awsAccessKey, awsSecret)
return getLandscape(awsAccessKey, awsSecret)
.createKeyPair(new AwsRegion(regionId), keyName, privateKeyEncryptionPassphrase.getBytes());
});
return convertToSSHKeyPairDTO(keyPair);
@@ -133,7 +212,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
final SSHKeyPair keyPair = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(dummyKeyPairForSecurityCheck.getPermissionType(),
dummyKeyPairForSecurityCheck.getIdentifier().getTypeRelativeObjectIdentifier(), keyName,
()->{
return AwsLandscape.obtain(awsAccessKey, awsSecret)
return getLandscape(awsAccessKey, awsSecret)
.importKeyPair(new AwsRegion(regionId), publicKey.getBytes(), encryptedPrivateKey.getBytes(), keyName);
});
return convertToSSHKeyPairDTO(keyPair);
@@ -148,7 +227,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public ArrayList<SSHKeyPairDTO> getSshKeys(String awsAccessKey, String awsSecret, String regionId) {
final ArrayList<SSHKeyPairDTO> result = new ArrayList<>();
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret);
final AwsLandscape<String> landscape = getLandscape(awsAccessKey, awsSecret);
final AwsRegion region = new AwsRegion(regionId);
for (final KeyPairInfo keyPairInfo : landscape.getAllKeyPairInfos(region)) {
final SSHKeyPair key = landscape.getSSHKeyPair(region, keyPairInfo.keyName());
@@ -164,7 +243,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public void removeSshKey(String awsAccessKey, String awsSecret, SSHKeyPairDTO keyPair) {
getSecurityService().checkPermissionAndDeleteOwnershipForObjectRemoval(keyPair,
()->AwsLandscape.obtain(awsAccessKey, awsSecret).deleteKeyPair(new AwsRegion(keyPair.getRegionId()), keyPair.getName()));
()->getLandscape(awsAccessKey, awsSecret).deleteKeyPair(new AwsRegion(keyPair.getRegionId()), keyPair.getName()));
}
@Override
@@ -185,11 +264,10 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public ArrayList<AmazonMachineImageDTO> getAmazonMachineImages(String awsAccessKey, String awsSecret, String region) {
SecurityUtils.getSubject().checkPermission(SecuredLandscapeTypes.LANDSCAPE.getStringPermissionForTypeRelativeIdentifier(SecuredLandscapeTypes.LandscapeActions.MANAGE,
new TypeRelativeObjectIdentifier("AWS")));
checkLandscapeManageAwsPermission();
final ArrayList<AmazonMachineImageDTO> result = new ArrayList<>();
final AwsRegion awsRegion = new AwsRegion(region);
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret);
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret, /* sessionToken */ Optional.empty());
for (final String imageType : landscape.getMachineImageTypes(awsRegion)) {
for (final AmazonMachineImage<String> machineImage : landscape.getAllImagesWithType(awsRegion, imageType)) {
final AmazonMachineImageDTO dto = new AmazonMachineImageDTO(machineImage.getId(),
@@ -203,18 +281,16 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public void removeAmazonMachineImage(String awsAccessKey, String awsSecret, String region, String machineImageId) {
SecurityUtils.getSubject().checkPermission(SecuredLandscapeTypes.LANDSCAPE.getStringPermissionForTypeRelativeIdentifier(SecuredLandscapeTypes.LandscapeActions.MANAGE,
new TypeRelativeObjectIdentifier("AWS")));
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret);
checkLandscapeManageAwsPermission();
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret, /* sessionToken */ Optional.empty());
final AmazonMachineImage<String> ami = landscape.getImage(new AwsRegion(region), machineImageId);
ami.delete();
}
@Override
public AmazonMachineImageDTO upgradeAmazonMachineImage(String awsAccessKey, String awsSecret, String region, String machineImageId) throws Exception {
SecurityUtils.getSubject().checkPermission(SecuredLandscapeTypes.LANDSCAPE.getStringPermissionForTypeRelativeIdentifier(SecuredLandscapeTypes.LandscapeActions.MANAGE,
new TypeRelativeObjectIdentifier("AWS")));
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret);
checkLandscapeManageAwsPermission();
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret, /* sessionToken */ Optional.empty());
final AwsRegion awsRegion = new AwsRegion(region);
final AmazonMachineImage<String> ami = landscape.getImage(awsRegion, machineImageId);
final UpgradeAmi.Builder<?, String, SailingAnalyticsProcess<String>> upgradeAmiBuilder = UpgradeAmi.builder();
@@ -234,7 +310,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
if (mongoScalingInstructions.getReplicaSetName() == null) {
throw new IllegalArgumentException("Can only scale MongoDB Replica Sets, not standalone instances");
}
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret);
final AwsLandscape<String> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret, /* sessionToken */ Optional.empty());
final AwsRegion region = new AwsRegion(regionId);
for (int i=0; i<mongoScalingInstructions.getLaunchParameters().getNumberOfInstances(); i++) {
final StartMongoDBServer.Builder<?, String, MongoProcessInReplicaSet> startMongoProcessBuilder = StartMongoDBServer.builder();
@@ -0,0 +1,40 @@
package com.sap.sailing.landscape.ui.shared;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.settings.generic.AbstractGenericSerializableSettings;
import com.sap.sse.common.settings.generic.LongSetting;
import com.sap.sse.common.settings.generic.StringSetting;
public class AwsSessionCredentialsFromUserPreference extends AbstractGenericSerializableSettings {
private static final long serialVersionUID = -3250243915670349222L;
private StringSetting accessKeyId;
private StringSetting secretAccessKey;
private StringSetting sessionToken;
private LongSetting expiry;
@Override
protected void addChildSettings() {
accessKeyId = new StringSetting("accessKeyId", this);
secretAccessKey = new StringSetting("secretAccessKey", this);
sessionToken = new StringSetting("sessionToken", this);
expiry = new LongSetting("expiry", this);
}
/**
* The default settings
*/
public AwsSessionCredentialsFromUserPreference() {
}
public AwsSessionCredentialsFromUserPreference(AwsSessionCredentialsWithExpiry awsSessionCredentialsWithExpiry) {
this.accessKeyId.setValue(awsSessionCredentialsWithExpiry.getAccessKeyId());
this.secretAccessKey.setValue(awsSessionCredentialsWithExpiry.getSecretAccessKey());
this.sessionToken.setValue(awsSessionCredentialsWithExpiry.getSessionToken());
this.expiry.setValue(awsSessionCredentialsWithExpiry.getExpiration().asMillis());
}
public AwsSessionCredentialsWithExpiry getAwsSessionCredentialsWithExpiry() {
return new AwsSessionCredentialsWithExpiryImpl(accessKeyId.getValue(), secretAccessKey.getValue(), sessionToken.getValue(), TimePoint.of(expiry.getValue()));
}
}
@@ -0,0 +1,10 @@
package com.sap.sailing.landscape.ui.shared;
import com.sap.sse.common.TimePoint;
public interface AwsSessionCredentialsWithExpiry {
String getAccessKeyId();
String getSecretAccessKey();
String getSessionToken();
TimePoint getExpiration();
}
@@ -0,0 +1,39 @@
package com.sap.sailing.landscape.ui.shared;
import com.sap.sse.common.TimePoint;
public class AwsSessionCredentialsWithExpiryImpl implements AwsSessionCredentialsWithExpiry {
private final String accessKeyId;
private final String secretAccessKey;
private final String sessionToken;
private final TimePoint expiration;
public AwsSessionCredentialsWithExpiryImpl(String accessKeyId, String secretAccessKey, String sessionToken,
TimePoint expiration) {
super();
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.sessionToken = sessionToken;
this.expiration = expiration;
}
@Override
public String getAccessKeyId() {
return accessKeyId;
}
@Override
public String getSecretAccessKey() {
return secretAccessKey;
}
@Override
public String getSessionToken() {
return sessionToken;
}
@Override
public TimePoint getExpiration() {
return expiration;
}
}
@@ -71,6 +71,7 @@ import com.sap.sse.security.SecurityService;
import com.sap.sse.security.SecurityUrlPathProvider;
import com.sap.sse.security.interfaces.PreferenceConverter;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.util.GenericJSONPreferenceConverter;
import com.sap.sse.security.shared.HasPermissionsProvider;
import com.sap.sse.security.shared.RoleDefinition;
import com.sap.sse.util.ClearStateTestSupport;
@@ -47,6 +47,7 @@ import software.amazon.awssdk.services.elasticloadbalancingv2.model.TargetHealth
import software.amazon.awssdk.services.route53.Route53Client;
import software.amazon.awssdk.services.route53.model.ChangeInfo;
import software.amazon.awssdk.services.route53.model.RRType;
import software.amazon.awssdk.services.sts.model.Credentials;
/**
* A simplified, largely stateless view onto the AWS SDK API that is geared towards specific ways and patterns of
@@ -64,7 +65,7 @@ import software.amazon.awssdk.services.route53.model.RRType;
* <p>
*
* Clients may also create dedicated instances of this service wrapper, using their own credentials. See
* {@link #obtain(String, String)}.
* {@link #obtain(String, String, Optional)}.
* <p>
*
* This object interacts with an instance of {@link AwsLandscapeState} which keeps persistent and replicable state about
@@ -118,7 +119,9 @@ public interface AwsLandscape<ShardingKey> extends Landscape<ShardingKey> {
* Based on system properties for the AWS access key ID and the secret access key (see
* {@link #ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME} and {@link #SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME}), this method
* returns a landscape object which internally has access to the clients for the underlying AWS landscape, such as
* an EC2 client, a Route53 client, etc.
* an EC2 client, a Route53 client, etc. Note that this way no multi-factor authentication (MFA) is possible. If
* the system properties described above are not set or not valid, an unauthenticated landscape object will result;
* some rudimentary things may still work, such as querying the set of regions.
*/
static <ShardingKey, MetricsT extends ApplicationProcessMetrics,
ProcessT extends ApplicationProcess<ShardingKey, MetricsT, ProcessT>>
@@ -134,8 +137,8 @@ public interface AwsLandscape<ShardingKey> extends Landscape<ShardingKey> {
*/
static <ShardingKey, MetricsT extends ApplicationProcessMetrics,
ProcessT extends ApplicationProcess<ShardingKey, MetricsT, ProcessT>>
AwsLandscape<ShardingKey> obtain(String accessKey, String secret) {
final AwsLandscape<ShardingKey> result = new AwsLandscapeImpl<>(Activator.getInstance().getLandscapeState(), accessKey, secret);
AwsLandscape<ShardingKey> obtain(String accessKey, String secret, Optional<String> mfaTokenCode) {
final AwsLandscape<ShardingKey> result = new AwsLandscapeImpl<>(Activator.getInstance().getLandscapeState(), accessKey, secret, mfaTokenCode);
return result;
}
@@ -556,4 +559,10 @@ public interface AwsLandscape<ShardingKey> extends Landscape<ShardingKey> {
String tagName, BiFunction<Host, String, ProcessT> processFactoryFromHostAndServerDirectory,
Optional<Duration> optionalTimeout, Optional<String> optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
/**
* Obtains session credentials using an MFA token code valid for the user for which this landscape object was authenticated
* during its creation with an access key ID and a secret.
*/
Credentials getMfaSessionCredentials(String nonEmptyMfaTokenCode);
}
@@ -62,6 +62,7 @@ import com.sap.sse.security.SessionUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
@@ -135,6 +136,8 @@ import software.amazon.awssdk.services.route53.model.GetChangeRequest;
import software.amazon.awssdk.services.route53.model.RRType;
import software.amazon.awssdk.services.route53.model.ResourceRecord;
import software.amazon.awssdk.services.route53.model.ResourceRecordSet;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.Credentials;
public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey> {
private static final String DEFAULT_TARGET_GROUP_PREFIX = "D";
@@ -150,25 +153,26 @@ public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey>
private static final String DEFAULT_NON_DNS_MAPPED_ALB_NAME = "DefDyn";
private final String accessKeyId;
private final String secretAccessKey;
private final Optional<String> sessionToken;
private final AwsRegion globalRegion;
private final AwsLandscapeState landscapeState;
public AwsLandscapeImpl(AwsLandscapeState awsLandscapeState) {
this(awsLandscapeState,
System.getProperty(ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME), System.getProperty(SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME));
System.getProperty(ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME), System.getProperty(SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME), Optional.empty());
}
public AwsLandscapeImpl(AwsLandscapeState awsLandscapeState, String accessKeyId, String secretAccessKey) {
this(accessKeyId, secretAccessKey,
public AwsLandscapeImpl(AwsLandscapeState awsLandscapeState, String accessKeyId, String secretAccessKey, Optional<String> mfaTokenCode) {
this(accessKeyId, secretAccessKey, mfaTokenCode,
// by using MongoDBService.INSTANCE the default test configuration will be used if nothing else is configured
PersistenceFactory.INSTANCE.getDomainObjectFactory(MongoDBService.INSTANCE),
PersistenceFactory.INSTANCE.getMongoObjectFactory(MongoDBService.INSTANCE), awsLandscapeState);
PersistenceFactory.INSTANCE.getDomainObjectFactory(MongoDBService.INSTANCE), PersistenceFactory.INSTANCE.getMongoObjectFactory(MongoDBService.INSTANCE), awsLandscapeState);
}
public AwsLandscapeImpl(String accessKeyId, String secretAccessKey,
DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory, AwsLandscapeState landscapeState) {
Optional<String> sessionToken, DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory, AwsLandscapeState landscapeState) {
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.sessionToken = sessionToken;
this.globalRegion = new AwsRegion(Region.AWS_GLOBAL);
this.landscapeState = landscapeState;
}
@@ -397,7 +401,7 @@ public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey>
}
private Route53Client getRoute53Client() {
return Route53Client.builder().region(getRegion(globalRegion)).build();
return getClient(Route53Client.builder(), getRegion(globalRegion));
}
@Override
@@ -577,8 +581,20 @@ public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey>
};
}
/**
* If a {@link #sessionToken} was provided to this landscape, use it to create {@link AwsSessionCredentials}; otherwise
* an {@link AwsBasicCredentials} object will be produced from the {@link #accessKeyId} and the {@link #secretAccessKey}.
* @return
*/
private AwsCredentials getCredentials() {
return AwsBasicCredentials.create(accessKeyId, secretAccessKey);
return sessionToken.map(nonEmptySessionToken->(AwsCredentials) AwsSessionCredentials.create(accessKeyId, secretAccessKey, sessionToken.get()))
.orElse(AwsBasicCredentials.create(accessKeyId, secretAccessKey));
}
@Override
public Credentials getMfaSessionCredentials(String nonEmptyMfaTokenCode) {
return StsClient.builder().credentialsProvider(()->AwsBasicCredentials.create(accessKeyId, secretAccessKey)).build()
.getSessionToken(b->b.tokenCode(nonEmptyMfaTokenCode)).credentials();
}
@Override
@@ -52,7 +52,6 @@ public class PreferenceConverterRegistrationManager implements Stoppable {
}
private class Cutomizer implements ServiceTrackerCustomizer<PreferenceConverter<?>, PreferenceConverter<?>> {
@Override
public PreferenceConverter<?> addingService(ServiceReference<PreferenceConverter<?>> reference) {
final String preferenceKey = (String) reference.getProperty(PreferenceConverter.KEY_PARAMETER_NAME);
@@ -30,8 +30,8 @@ public interface UserStore extends BasicUserStore {
/**
* <p>
* In an OSGi environment, this shouldn't be called manually, but instead automatically managed by setting a
* {@link PreferenceConverterRegistrationManager} up. {@link PreferenceConverter}s should be registered in the OSGi
* In an OSGi environment, this shouldn't be called manually, but instead automatically managed by setting up a
* {@link PreferenceConverterRegistrationManager}. {@link PreferenceConverter}s should be registered in the OSGi
* service registry with {@link PreferenceConverter#KEY_PARAMETER_NAME} containing the associated preference key
* added as property of the service registration.
* </p>
@@ -307,13 +307,13 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica
/**
* Gets a preference object. Always returns null if there is no converter associated with the given key -> see
* {@link #registerPreferenceConverter(String, PreferenceConverter)}.
* {@link UserStore#registerPreferenceConverter(String, PreferenceConverter)}.
*/
<T> T getPreferenceObject(String username, String key);
/**
* Gets all preference objects resolving to a certain key. Always returns a valid map. May be empty.
* {@link #registerPreferenceConverter(String, PreferenceConverter)}.
* {@link UserStore#registerPreferenceConverter(String, PreferenceConverter)}.
*/
<T> Map<String, T> getPreferenceObjectsByKey(String key);
@@ -1,4 +1,4 @@
package com.sap.sailing.server.impl;
package com.sap.sse.security.util;
import java.util.function.Supplier;
@@ -24,5 +24,4 @@ public class GenericJSONPreferenceConverter<PREF extends GenericSerializableSett
public PREF toPreferenceObject(String stringPreference) {
return serializer.deserialize(emptyInstanceFactory.get(), stringPreference);
}
}