Skip to content

Commit

Permalink
feat(fips): enable fips support deployment flow (#1638)
Browse files Browse the repository at this point in the history
  • Loading branch information
yitingb authored Jul 24, 2024
1 parent f72ad92 commit 9a0de5c
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.aws.greengrass.util.LockFactory;
import com.aws.greengrass.util.LockScope;
import com.aws.greengrass.util.NucleusPaths;
import com.aws.greengrass.util.RootCAUtils;
import com.aws.greengrass.util.Utils;
import com.aws.greengrass.util.exceptions.TLSAuthException;
import com.aws.greengrass.util.platforms.Platform;
Expand All @@ -49,6 +50,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import javax.inject.Inject;
Expand Down Expand Up @@ -130,6 +132,7 @@ public class DeviceConfiguration {

private final Validator deTildeValidator;
private final Validator regionValidator;
private final AtomicBoolean rootCA3Downloaded = new AtomicBoolean(false);
private final AtomicReference<Boolean> deviceConfigValidateCachedResult = new AtomicReference<>();

private Topics loggingTopics;
Expand All @@ -156,10 +159,7 @@ public DeviceConfiguration(Configuration config, KernelCommandLine kernelCommand
handleLoggingConfig();
getComponentStoreMaxSizeBytes().dflt(COMPONENT_STORE_MAX_SIZE_DEFAULT_BYTES);
getDeploymentPollingFrequencySeconds().dflt(DEPLOYMENT_POLLING_FREQUENCY_DEFAULT_SECONDS);
if (System.getProperty(S3_ENDPOINT_PROP_NAME) != null
&& System.getProperty(S3_ENDPOINT_PROP_NAME).equalsIgnoreCase(S3EndpointType.REGIONAL.name())) {
gets3EndpointType().withValue(S3EndpointType.REGIONAL.name());
}
handleExistingSystemProperty();
// reset the cache when device configuration changes
onAnyChange((what, node) -> deviceConfigValidateCachedResult.set(null));
}
Expand Down Expand Up @@ -193,11 +193,6 @@ public DeviceConfiguration(Configuration config, KernelCommandLine kernelCommand
getRootCAFilePath().withValue(rootCaFilePath);
getAWSRegion().withValue(awsRegion);
getIotRoleAlias().withValue(tesRoleAliasName);

if (System.getProperty(S3_ENDPOINT_PROP_NAME) != null
&& System.getProperty(S3_ENDPOINT_PROP_NAME).equalsIgnoreCase(S3EndpointType.REGIONAL.name())) {
gets3EndpointType().withValue(S3EndpointType.REGIONAL.name());
}
validate();
}

Expand Down Expand Up @@ -370,6 +365,11 @@ private Validator getRegionValidator() {

// Get the current FIPS mode for the AWS SDK. Default will be false (no FIPS).
String useFipsMode = Boolean.toString(Coerce.toBoolean(getFipsMode()));
//Download CA3 to support iotDataEndpoint
if (Coerce.toBoolean(getFipsMode()) && !rootCA3Downloaded.get()) {
rootCA3Downloaded.set(RootCAUtils.downloadRootCAsWithPath(Coerce.toString(getRootCAFilePath()),
RootCAUtils.AMAZON_ROOT_CA_3_URL));
}
// Set the FIPS property so our SDK clients will use this FIPS mode by default.
// This won't change any client that exists already.
System.setProperty(SdkSystemSetting.AWS_USE_FIPS_ENDPOINT.property(), useFipsMode);
Expand All @@ -378,7 +378,6 @@ private Validator getRegionValidator() {
.withValue(useFipsMode);
// Read by stream manager
config.lookup(SETENV_CONFIG_NAMESPACE, "AWS_GG_FIPS_MODE").withValue(useFipsMode);

return region;
};
}
Expand Down Expand Up @@ -653,6 +652,7 @@ public String getNucleusVersion() {

/**
* Get the Nucleus version from the ZIP file.
* Get the Nucleus version from the ZIP file
*
* @return version from the zip file, or a default if the version can't be determined
*/
Expand Down Expand Up @@ -791,4 +791,20 @@ public KeyManager[] getDeviceIdentityKeyManagers() throws TLSAuthException {
public Topics getHttpClientOptions() {
return getTopics("httpClient");
}

/**
* Set device config based on existing System property.
*/
private void handleExistingSystemProperty() {
//handle s3 endpoint type
if (System.getProperty(S3_ENDPOINT_PROP_NAME) != null
&& System.getProperty(S3_ENDPOINT_PROP_NAME).equalsIgnoreCase(S3EndpointType.REGIONAL.name())) {
gets3EndpointType().withValue(S3EndpointType.REGIONAL.name());
}
//handle fips mode
String useFipsMode = System.getProperty(SdkSystemSetting.AWS_USE_FIPS_ENDPOINT.property());
if (Coerce.toBoolean(useFipsMode)) {
getFipsMode().withValue(useFipsMode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

import static com.aws.greengrass.deployment.DeviceConfiguration.DEVICE_MQTT_NAMESPACE;
import static com.aws.greengrass.deployment.DeviceConfiguration.DEVICE_NETWORK_PROXY_NAMESPACE;
import static com.aws.greengrass.deployment.DeviceConfiguration.DEVICE_PARAM_FIPS_MODE;
import static com.aws.greengrass.deployment.DeviceConfiguration.DEVICE_PARAM_NO_PROXY_ADDRESSES;
import static com.aws.greengrass.deployment.DeviceConfiguration.DEVICE_PARAM_PROXY_PASSWORD;
import static com.aws.greengrass.deployment.DeviceConfiguration.DEVICE_PARAM_PROXY_URL;
Expand Down Expand Up @@ -216,6 +217,17 @@ private boolean willRemovePlugins(Map<String, Object> serviceConfig) {
return !pluginsToRemove.isEmpty();
}

private boolean fipsModeHasChanged(Map<String, Object> newNucleusParameters,
DeviceConfiguration currentDeviceConfiguration) {
boolean currentFipsMode = Coerce.toBoolean(currentDeviceConfiguration.getFipsMode());
boolean newFipsMode = Coerce.toBoolean(newNucleusParameters.get(DEVICE_PARAM_FIPS_MODE));
if (currentFipsMode != newFipsMode) {
logger.atInfo().kv(DEVICE_PARAM_FIPS_MODE, newFipsMode).log(RESTART_REQUIRED_MESSAGE);
return true;
}
return false;
}

private boolean mqttVersionHasChanged(Map<String, Object> newNucleusParameters,
DeviceConfiguration currentDeviceConfiguration) {
String currentMqttVersion = Coerce.toString(
Expand Down Expand Up @@ -346,8 +358,9 @@ private boolean nucleusConfigChangeRequiresRestart(Map<String, Object> newNucleu
boolean mqttVersionChanged = mqttVersionHasChanged(newNucleusParameters, currentDeviceConfiguration);
boolean spoolerStorageTypeChanged = spoolerStorageTypeHasChanged(newNucleusParameters,
currentDeviceConfiguration);
boolean fipsModeChanged = fipsModeHasChanged(newNucleusParameters, currentDeviceConfiguration);

return proxyChanged || runWithChanged || mqttVersionChanged || spoolerStorageTypeChanged;
return proxyChanged || runWithChanged || mqttVersionChanged || spoolerStorageTypeChanged || fipsModeChanged;
}

private boolean nucleusConfigValidAndNeedsRestart(Map<String, Object> deploymentConfig)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryMode;
import software.amazon.awssdk.endpoints.Endpoint;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.greengrassv2data.GreengrassV2DataClient;
import software.amazon.awssdk.services.greengrassv2data.GreengrassV2DataClientBuilder;
import software.amazon.awssdk.services.greengrassv2data.endpoints.GreengrassV2DataEndpointParams;
import software.amazon.awssdk.services.greengrassv2data.endpoints.GreengrassV2DataEndpointProvider;

import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import javax.inject.Inject;
Expand All @@ -50,6 +54,7 @@ public class GreengrassServiceClientFactory {
private volatile String configValidationError;
private final AtomicBoolean deviceConfigChanged = new AtomicBoolean(true);
private final Lock lock = LockFactory.newReentrantLock(this);
private GreengrassV2DataClientBuilder clientBuilder;

/**
* Constructor with custom endpoint/region configuration.
Expand Down Expand Up @@ -183,19 +188,28 @@ private void configureClient(DeviceConfiguration deviceConfiguration) {
configureHttpClient(deviceConfiguration);
}
logger.atDebug().log(CONFIGURING_GGV2_INFO_MESSAGE);
GreengrassV2DataClientBuilder clientBuilder = GreengrassV2DataClient.builder()
// Use an empty credential provider because our requests don't need SigV4
// signing, as they are going through IoT Core instead
.credentialsProvider(AnonymousCredentialsProvider.create())
.httpClient(cachedHttpClient)
.overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RetryMode.STANDARD).build());
String greengrassServiceEndpoint = ClientConfigurationUtils
.getGreengrassServiceEndpoint(deviceConfiguration);
GreengrassV2DataEndpointProvider endpointProvider = new GreengrassV2DataEndpointProvider() {
@Override
public CompletableFuture<Endpoint> resolveEndpoint(GreengrassV2DataEndpointParams endpointParams) {
return CompletableFuture.supplyAsync(() -> Endpoint.builder()
.url(URI.create(greengrassServiceEndpoint))
.build());
}
};
clientBuilder = GreengrassV2DataClient.builder()
// Use an empty credential provider because our requests don't need SigV4
// signing, as they are going through IoT Core instead
.credentialsProvider(AnonymousCredentialsProvider.create())
.endpointProvider(endpointProvider)
.httpClient(cachedHttpClient)
.overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RetryMode.STANDARD).build());


String region = Coerce.toString(deviceConfiguration.getAWSRegion());

if (!Utils.isEmpty(region)) {
String greengrassServiceEndpoint = ClientConfigurationUtils
.getGreengrassServiceEndpoint(deviceConfiguration);

if (!Utils.isEmpty(greengrassServiceEndpoint)) {
// Region and endpoint are both required when updating endpoint config
logger.atDebug("initialize-greengrass-client")
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/aws/greengrass/util/RootCAUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private static void removeDuplicateCertificates(File f) {
}
}
} catch (IOException e) {
logger.atDebug().log("Failed to remove duplicate certificates - %s%n", e);
logger.atDebug().log("Failed to remove duplicate certificates ", e);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/aws/greengrass/util/S3SdkClientFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,32 +89,32 @@ public S3Client getS3Client() throws DeviceConfigurationException {
* @return s3client
*/
public S3Client getClientForRegion(Region r) {
setS3EndpointType(Coerce.toString(deviceConfiguration.gets3EndpointType()));
handleS3EndpointType(Coerce.toString(deviceConfiguration.gets3EndpointType()));
return clientCache.computeIfAbsent(r, (region) -> S3Client.builder()
.httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder())
.serviceConfiguration(S3Configuration.builder().useArnRegionEnabled(true).build())
.credentialsProvider(credentialsProvider).region(r).build());
}

/**
* Set s3 endpoint type.
* Handle s3 endpoint type. Refresh client if system property updated
*
* @param type s3EndpointType
*/
private void setS3EndpointType(String type) {
private void handleS3EndpointType(String type) {
//Check if system property and device config are consistent
//If not consistent, set system property according to device config value
String s3EndpointSystemProp = System.getProperty(S3_ENDPOINT_PROP_NAME);
boolean isGlobal = S3EndpointType.GLOBAL.name().equals(type);

if (isGlobal && S3_REGIONAL_ENDPOINT_VALUE.equals(s3EndpointSystemProp)) {
System.clearProperty(S3_ENDPOINT_PROP_NAME);
refreshClientCache();
logger.atDebug().log("s3 endpoint set to global");
refreshClientCache();
} else if (!isGlobal && !S3_REGIONAL_ENDPOINT_VALUE.equals(s3EndpointSystemProp)) {
System.setProperty(S3_ENDPOINT_PROP_NAME, S3_REGIONAL_ENDPOINT_VALUE);
refreshClientCache();
logger.atDebug().log("s3 endpoint set to regional");
refreshClientCache();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.amazon.aws.iot.greengrass.component.common.ComponentType;
import com.aws.greengrass.componentmanager.ComponentStore;
import com.aws.greengrass.config.Topic;
import com.aws.greengrass.config.Topics;
import com.aws.greengrass.dependency.Context;
import com.aws.greengrass.deployment.DeviceConfiguration;
Expand Down Expand Up @@ -738,4 +739,60 @@ void GIVEN_spooler_storage_type_same_WHEN_isBootstrapRequired_THEN_return_false(
}};
assertThat("restart required", bootstrapManager.isBootstrapRequired(config), is(false));
}
@Test
void GIVEN_fips_Mode_changes_WHEN_isBootstrapRequired_THEN_return_true()
throws ServiceUpdateException, ComponentConfigurationValidationException, ServiceLoadException {
when(context.get(DeviceConfiguration.class)).thenReturn(deviceConfiguration);
when(kernel.getContext()).thenReturn(context);

GenericExternalService service = mock(GenericExternalService.class);
doReturn(false).when(service).isBootstrapRequired(anyMap());
when(kernel.locate(DEFAULT_NUCLEUS_COMPONENT_NAME)).thenReturn(service);
when(deviceConfiguration.getSpoolerNamespace().findOrDefault(any(), any())).thenReturn(SpoolerStorageType.Memory);
Topics mockMqttTopics = mock(Topics.class);
when(mockMqttTopics.findOrDefault(any(), any())).thenAnswer((c) -> c.getArgument(0));
when(deviceConfiguration.getMQTTNamespace()).thenReturn(mockMqttTopics);
Map<String, Object> runWith = mockRunWith;
when(deviceConfiguration.getRunWithTopic().toPOJO()).thenReturn(runWith);
Topic fipsMode = Topic.of(context, DeviceConfiguration.DEVICE_PARAM_FIPS_MODE,
"false");
when(deviceConfiguration.getFipsMode()).thenReturn(fipsMode);

// Change fipsMode from "false" to "true"
BootstrapManager bootstrapManager = new BootstrapManager(kernel, platform);
Map<String, Object> config =
new HashMap<String, Object>() {{
put(SERVICES_NAMESPACE_TOPIC, new HashMap<String, Object>() {{
put(DEFAULT_NUCLEUS_COMPONENT_NAME, new HashMap<String, Object>() {{
put(SERVICE_TYPE_TOPIC_KEY, ComponentType.NUCLEUS.toString());
put(CONFIGURATION_CONFIG_KEY, new HashMap<String, Object>() {{
put(DeviceConfiguration.DEVICE_PARAM_FIPS_MODE, "true");
put(DeviceConfiguration.RUN_WITH_TOPIC, runWith);
}});
}});
}});
}};
boolean actual = bootstrapManager.isBootstrapRequired(config);
assertThat("restart required", actual, is(true));

// Change FipsMode from "true" to "false"
fipsMode = Topic.of(context, DeviceConfiguration.DEVICE_PARAM_FIPS_MODE,
"true");
when(deviceConfiguration.getFipsMode()).thenReturn(fipsMode);
bootstrapManager = new BootstrapManager(kernel, platform);
config =
new HashMap<String, Object>() {{
put(SERVICES_NAMESPACE_TOPIC, new HashMap<String, Object>() {{
put(DEFAULT_NUCLEUS_COMPONENT_NAME, new HashMap<String, Object>() {{
put(SERVICE_TYPE_TOPIC_KEY, ComponentType.NUCLEUS.toString());
put(CONFIGURATION_CONFIG_KEY, new HashMap<String, Object>() {{
put(DeviceConfiguration.DEVICE_PARAM_FIPS_MODE, "false");
put(DeviceConfiguration.RUN_WITH_TOPIC, runWith);
}});
}});
}});
}};
actual = bootstrapManager.isBootstrapRequired(config);
assertThat("restart required", actual, is(true));
}
}

0 comments on commit 9a0de5c

Please sign in to comment.