Skip to content

Commit

Permalink
[#3569] Use TLS endpoint when connecting to Sandbox
Browse files Browse the repository at this point in the history
The command line client was still trying to connect to the insecure
ports of the Sandbox.

This has been changed so that the client now uses the TLS endpoints and
requires the user to specify a trust store for validating the server
certificate.

Also updated the Python Getting Started example script to use Kafka
instead of AMQP 1.0 based messaging infrastructure.
  • Loading branch information
sophokles73 committed Nov 8, 2023
1 parent 82430b2 commit e32e564
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,23 @@ static class TelemetrySendingOptions {
}

private void validateConnectionOptions() {
if (!connectionOptions.useSandbox && (connectionOptions.hostname.isEmpty() || connectionOptions.portNumber.isEmpty())) {
if (connectionOptions.useSandbox) {
if (!connectionOptions.trustStorePath.isPresent()) {
throw new ParameterException(
spec.commandLine(),
"""
Missing required option: '--ca-file=<path>' needs to be specified \
when using '--sandbox'.
""");
}
} else if (connectionOptions.hostname.isEmpty() || connectionOptions.portNumber.isEmpty()) {
throw new ParameterException(
spec.commandLine(),
"""
Missing required option: both '--host=<hostname>' and '--port=<portNumber> need to \
be specified if not using '--sandbox'.
be specified when not using '--sandbox'.
""");
}
}
}

/**
Expand All @@ -207,12 +216,17 @@ private Future<AmqpAdapterClient> getClient() {
return Future.succeededFuture(client);
}

validateConnectionOptions();
final var clientConfig = new ClientConfigProperties();
clientConfig.setReconnectAttempts(5);
clientConfig.setServerRole("Hono AMQP Adapter");
connectionOptions.trustStorePath.ifPresent(path -> {
clientConfig.setTrustStorePath(path);
connectionOptions.trustStorePassword.ifPresent(clientConfig::setTrustStorePassword);
});
if (connectionOptions.useSandbox) {
clientConfig.setHost(ConnectionOptions.SANDBOX_HOST_NAME);
clientConfig.setPort(5672);
clientConfig.setPort(5671);
Optional.ofNullable(connectionOptions.credentials).ifPresentOrElse(
creds -> {
clientConfig.setUsername(creds.username);
Expand All @@ -222,24 +236,18 @@ private Future<AmqpAdapterClient> getClient() {
clientConfig.setUsername(SANDBOX_DEFAULT_DEVICE_AUTH_ID);
clientConfig.setPassword(SANDBOX_DEFAULT_DEVICE_PWD);
});
connectionOptions.trustStorePath.ifPresent(path -> {
clientConfig.setPort(5671);
clientConfig.setTrustStorePath(path);
});
} else {
validateConnectionOptions();
connectionOptions.hostname.ifPresent(clientConfig::setHost);
connectionOptions.portNumber.ifPresent(clientConfig::setPort);
clientConfig.setHostnameVerificationRequired(!connectionOptions.disableHostnameVerification);
if (clientCertInfo != null) {
clientConfig.setCertPath(clientCertInfo.certPath);
clientConfig.setKeyPath(clientCertInfo.keyPath);
} else if (connectionOptions.credentials != null) {
clientConfig.setUsername(connectionOptions.credentials.username);
clientConfig.setPassword(connectionOptions.credentials.password);
}
connectionOptions.trustStorePath.ifPresent(clientConfig::setTrustStorePath);
}

final var clientFactory = AmqpAdapterClient.create(HonoConnection.newConnection(vertx, clientConfig));
return clientFactory.connect()
.onSuccess(con -> {
Expand Down
55 changes: 32 additions & 23 deletions cli/src/main/java/org/eclipse/hono/cli/app/NorthBoundApis.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,23 @@ public class NorthBoundApis {
ApplicationClient<? extends MessageContext> client;

private void validateConnectionOptions() {
if (!connectionOptions.useSandbox && (connectionOptions.hostname.isEmpty() || connectionOptions.portNumber.isEmpty())) {
if (connectionOptions.useSandbox) {
if (!connectionOptions.trustStorePath.isPresent()) {
throw new ParameterException(
spec.commandLine(),
"""
Missing required option: '--ca-file=<path>' needs to be specified \
when using '--sandbox'.
""");
}
} else if (connectionOptions.hostname.isEmpty() || connectionOptions.portNumber.isEmpty()) {
throw new ParameterException(
spec.commandLine(),
"""
Missing required option: both '--host=<hostname>' and '--port=<portNumber> need to \
be specified if not using '--sandbox'.
be specified when not using '--sandbox'.
""");
}
}
}

private String scramJaasConfig(final String username, final String password) {
Expand All @@ -117,13 +126,25 @@ private String scramJaasConfig(final String username, final String password) {
}

Future<KafkaApplicationClientImpl> createKafkaClient() {

validateConnectionOptions();
final var commonProps = new HashMap<String, String>();
final String bootstrapServers;

connectionOptions.trustStorePath
.ifPresent(path -> {
commonProps.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, path);
connectionOptions.trustStorePassword
.ifPresent(pwd -> commonProps.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, pwd));

Optional.ofNullable(FileFormat.detect(path))
.ifPresent(fileFormat -> commonProps.put(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, fileFormat.name()));
});

if (connectionOptions.useSandbox) {
bootstrapServers = "%1$s:9092,%1$s:9094".formatted(ConnectionOptions.SANDBOX_HOST_NAME);
bootstrapServers = "%s:9094".formatted(ConnectionOptions.SANDBOX_HOST_NAME);
commonProps.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
commonProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name);
commonProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_SSL.name);
commonProps.put(SaslConfigs.SASL_MECHANISM, ScramMechanism.SCRAM_SHA_512.mechanismName());
Optional.ofNullable(connectionOptions.credentials)
.ifPresentOrElse(
Expand All @@ -134,22 +155,11 @@ Future<KafkaApplicationClientImpl> createKafkaClient() {
SaslConfigs.SASL_JAAS_CONFIG,
scramJaasConfig(SANDBOX_KAFKA_USER, SANDBOX_KAFKA_PWD)));
} else {
validateConnectionOptions();
bootstrapServers = "%s:%d".formatted(connectionOptions.hostname.get(), connectionOptions.portNumber.get());
commonProps.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);

connectionOptions.trustStorePath
.ifPresent(path -> {
commonProps.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, path);
connectionOptions.trustStorePassword
.ifPresent(pwd -> commonProps.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, pwd));

Optional.ofNullable(FileFormat.detect(path))
.ifPresent(s -> commonProps.put(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, s.name()));
if (connectionOptions.disableHostnameVerification) {
commonProps.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
}
});
if (connectionOptions.disableHostnameVerification) {
commonProps.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
}

if (connectionOptions.credentials == null) {
if (connectionOptions.trustStorePath.isPresent()) {
Expand Down Expand Up @@ -191,23 +201,22 @@ Future<KafkaApplicationClientImpl> createKafkaClient() {

Future<ProtonBasedApplicationClient> createAmqpClient() {

validateConnectionOptions();
final var clientConfig = new ClientConfigProperties();
clientConfig.setReconnectAttempts(5);
clientConfig.setServerRole("Hono Messaging Infrastructure");
connectionOptions.trustStorePath.ifPresent(path -> {
clientConfig.setTrustStorePath(path);
connectionOptions.trustStorePassword.ifPresent(clientConfig::setTrustStorePassword);
});
if (connectionOptions.useSandbox) {
clientConfig.setHost(ConnectionOptions.SANDBOX_HOST_NAME);
clientConfig.setPort(connectionOptions.trustStorePath.map(s -> 15671).orElse(15672));
clientConfig.setPort(15671);
clientConfig.setUsername(SANDBOX_AMQP_USER);
clientConfig.setPassword(SANDBOX_AMQP_PWD);
} else {
validateConnectionOptions();
connectionOptions.hostname.ifPresent(clientConfig::setHost);
connectionOptions.portNumber.ifPresent(clientConfig::setPort);
connectionOptions.trustStorePath.ifPresent(clientConfig::setTrustStorePath);
connectionOptions.trustStorePassword.ifPresent(clientConfig::setTrustStorePassword);
clientConfig.setHostnameVerificationRequired(!connectionOptions.disableHostnameVerification);
Optional.ofNullable(connectionOptions.credentials)
.ifPresent(creds -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public class ConnectionOptions {
names = { "--ca-file" },
description = {
"Absolute path to a file containing trusted CA certificates to enable encrypted communication.",
"If not set explicitly, the platform's default trust store will be used."
"Needs to be set if connecting to an endpoint using TLS.",
"In particular, this needs to be set when connecting to the Hono Sandbox."
},
order = 4)
public Optional<String> trustStorePath;
Expand Down
24 changes: 9 additions & 15 deletions examples/quickstart-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,27 @@ Then run the `quickstart.py` script.

## What does the script do?

* Setup a Tenant and configure it to use AMQP 1.0 based messaging infrastructure
* Setup a Tenant and configure it to use Kafka based messaging infrastructure
* Setup a Device in the tenant
* Add credentials to the device
* Start a north bound AMQP 1.0 receiver for the messages sent by the tenant's devices
* Start a north bound Kafka consumer for the messages sent by the tenant's devices
* Send a Telemetry message via HTTP API
* Send a Telemetry message via MQTT API

The output should roughly look like this

```bash
```
Registered tenant f184ce49-d906-4a8c-af27-21df4c4acbf1
Registered device 6f948c41-c2da-47f5-a52e-bf013f09e670
Password is set!
You could now start the Hono Command Line Client in another terminal to consume messages from devices:
java -jar hono-cli-2.*-exec.jar app --sandbox consume --tenant=f184ce49-d906-4a8c-af27-21df4c4acbf1
Using source: amqp://hono.eclipseprojects.io:15672/telemetry
Using address: telemetry/f184ce49-d906-4a8c-af27-21df4c4acbf1
Starting (north bound) AMQP Connection...
Started
Send Telemetry Message via HTTP
HTTP sent successful
Send Telemetry Message via MQTT
Got a message:
b'{"temp": 5, "transport": "http"}
Got a message:
b'{"temp": 17, "transport": "mqtt"}
Stopping (north bound) AMQP Connection...
created Kafka consumer ...
waiting for Kafka messages
Sending Telemetry message via HTTP adapter
Sending Telemetry message via MQTT adapter
Got a message: {"temp": 5, "transport": "http"}
Got a message: {"temp": 17, "transport": "mqtt"}
```
Loading

0 comments on commit e32e564

Please sign in to comment.