Skip to content

Commit 5df60bf

Browse files
authored
[ISSUE-144] No longer require uploading a TrustStore when setting up a SSL enabled cluster (#217)
* [ISSUE-144] No longer require uploading a TrustStore when setting up a SSL enabled cluster * cleanup and test coverage * improve UI
1 parent 4870467 commit 5df60bf

File tree

9 files changed

+400
-57
lines changed

9 files changed

+400
-57
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
The format is based on [Keep a Changelog](http://keepachangelog.com/)
33
and this project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## 2.6.0 (UNRELEASED)
6+
- [ISSUE-144](https://github.com/SourceLabOrg/kafka-webview/issues/144) Make providing a TrustStore file when setting up a SSL enabled cluster optional. You might not want/need this option if your JVM is already configured to accept the SSL certificate served by the cluster, or if the cluster's certificate can be validated by a publically accessible CA.
7+
- [PR-215](https://github.com/SourceLabOrg/kafka-webview/pull/215) Improve errors displayed when using the `test cluster` functionality.
8+
59
## 2.5.1 (05/19/2020)
610
- [ISSUE-209](https://github.com/SourceLabOrg/kafka-webview/issues/209) Expose HealthCheck and App Info endpoints without requiring authentication.
711
- Docker image now exposes port 9090 for Actuator end points.

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/cluster/ClusterConfigController.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package org.sourcelab.kafka.webview.ui.controller.configuration.cluster;
2626

27+
import org.apache.kafka.common.errors.TimeoutException;
2728
import org.slf4j.Logger;
2829
import org.slf4j.LoggerFactory;
2930
import org.sourcelab.kafka.webview.ui.controller.BaseController;
@@ -136,6 +137,7 @@ public String editClusterForm(
136137
clusterForm.setSsl(cluster.isSslEnabled());
137138
clusterForm.setKeyStoreFilename(cluster.getKeyStoreFile());
138139
clusterForm.setTrustStoreFilename(cluster.getTrustStoreFile());
140+
clusterForm.setUseTrustStore(clusterForm.hasTrustStoreFilename());
139141

140142
// Set SASL options
141143
final SaslProperties saslProperties = saslUtility.decodeProperties(cluster);
@@ -181,11 +183,14 @@ public String clusterUpdate(
181183
if (clusterForm.getSsl()) {
182184
// If we're creating a new cluster
183185
if (!clusterForm.exists()) {
184-
// Ensure that we have files uploaded
185-
if (clusterForm.getTrustStoreFile() == null || clusterForm.getTrustStoreFile().isEmpty()) {
186-
bindingResult.addError(new FieldError(
187-
"clusterForm", "trustStoreFile", null, true, null, null, "Select a TrustStore JKS to upload")
188-
);
186+
// Ensure that we have files uploaded for the truststore,
187+
// but only if they elected to upload a truststore at all.
188+
if (clusterForm.getUseTrustStore()) {
189+
if (clusterForm.getTrustStoreFile() == null || clusterForm.getTrustStoreFile().isEmpty()) {
190+
bindingResult.addError(new FieldError(
191+
"clusterForm", "trustStoreFile", null, true, null, null, "Select a TrustStore JKS to upload")
192+
);
193+
}
189194
}
190195

191196
// Only require KeyStore if NOT using SASL
@@ -233,8 +238,23 @@ public String clusterUpdate(
233238
// Flip flag to true
234239
cluster.setSslEnabled(true);
235240

236-
// Determine if we should update keystores
237-
if (!clusterForm.exists() || (clusterForm.getTrustStoreFile() != null && !clusterForm.getTrustStoreFile().isEmpty())) {
241+
// If they've selected to NOT use a trust store.
242+
if (!clusterForm.getUseTrustStore()) {
243+
// Delete previous trust store if exists
244+
if (cluster.getTrustStoreFile() != null) {
245+
uploadManager.deleteKeyStore(cluster.getTrustStoreFile());
246+
}
247+
// Clear out properties
248+
cluster.setTrustStoreFile(null);
249+
cluster.setTrustStorePassword(null);
250+
}
251+
252+
/*
253+
* Determine if we should update truststore. We Update it in the following scenarios:
254+
* - If the cluster is being newly created.
255+
* - If they uploaded a trust store, and there previously was a truststore.
256+
*/
257+
else if (!clusterForm.exists() || (clusterForm.getTrustStoreFile() != null && !clusterForm.getTrustStoreFile().isEmpty())) {
238258
// Delete previous trust store if updating
239259
if (cluster.getTrustStoreFile() != null) {
240260
uploadManager.deleteKeyStore(cluster.getTrustStoreFile());
@@ -262,6 +282,7 @@ public String clusterUpdate(
262282
}
263283
}
264284

285+
// Determine if we should update keystores
265286
if (!clusterForm.exists() || (clusterForm.getKeyStoreFile() != null && !clusterForm.getKeyStoreFile().isEmpty())) {
266287
// Delete previous key store if updating, or if SASL is enabled.
267288
if (clusterForm.getSasl() || cluster.getKeyStoreFile() != null) {
@@ -421,7 +442,10 @@ public String testCluster(@PathVariable final Long id, final RedirectAttributes
421442
}
422443
} catch (final Exception e) {
423444
// Collect all reasons.
424-
final String reason = e.getMessage();
445+
String reason = e.getMessage();
446+
if (e instanceof TimeoutException) {
447+
reason = reason + " (This may indicate an authentication or connection problem)";
448+
}
425449

426450
// Set error msg
427451
redirectAttributes.addFlashAttribute(
@@ -450,5 +474,12 @@ private void setupBreadCrumbs(final Model model, final String name, final String
450474
} else {
451475
manager.addCrumb("Clusters", null);
452476
}
477+
478+
// Add default trust store property.
479+
String defaultTrustStore = System.getProperty("javax.net.ssl.trustStore", "<JRE_HOME>/lib/security/cacerts");
480+
if (defaultTrustStore != null && defaultTrustStore.trim().isEmpty()) {
481+
defaultTrustStore = "<JRE_HOME>/lib/security/cacerts";
482+
}
483+
model.addAttribute("defaultTrustStore", defaultTrustStore);
453484
}
454485
}

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/cluster/forms/ClusterForm.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class ClusterForm {
4545

4646
// SSL Options
4747
private Boolean ssl = false;
48+
private Boolean useTrustStore = false;
4849

4950
private MultipartFile trustStoreFile;
5051
private String trustStoreFilename;
@@ -141,6 +142,22 @@ public String getTrustStoreFilename() {
141142
}
142143
}
143144

145+
public Boolean getUseTrustStore() {
146+
return useTrustStore;
147+
}
148+
149+
public void setUseTrustStore(final Boolean useTrustStore) {
150+
this.useTrustStore = useTrustStore;
151+
}
152+
153+
/**
154+
* Is there a configured TrustStore file?
155+
* @return true if so, false if not.
156+
*/
157+
public boolean hasTrustStoreFilename() {
158+
return trustStoreFilename != null && !trustStoreFilename.isEmpty();
159+
}
160+
144161
public void setTrustStoreFilename(final String trustStoreFilename) {
145162
this.trustStoreFilename = trustStoreFilename;
146163
}

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/KafkaClientConfigUtil.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,11 @@ private void applySslSettings(final ClusterConfig clusterConfig, final Map<Strin
131131
config.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, keyStoreRootPath + "/" + clusterConfig.getKeyStoreFile());
132132
config.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, clusterConfig.getKeyStorePassword());
133133
}
134-
config.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, keyStoreRootPath + "/" + clusterConfig.getTrustStoreFile());
135-
config.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, clusterConfig.getTrustStorePassword());
134+
// Only put Trust properties if one is defined
135+
if (clusterConfig.getTrustStoreFile() != null) {
136+
config.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, keyStoreRootPath + "/" + clusterConfig.getTrustStoreFile());
137+
config.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, clusterConfig.getTrustStorePassword());
138+
}
136139
}
137140

138141
/**

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/KafkaOperations.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,16 @@ private List<ConfigItem> describeResource(final ConfigResource configResource) {
600600
}
601601
}
602602

603-
private RuntimeException handleExecutionException(final ExecutionException e) {
604-
if (e.getCause() != null && e.getCause() instanceof RuntimeException) {
605-
return (RuntimeException) e.getCause();
603+
/**
604+
* Handle ExecutionException errors when raised.
605+
* @param executionException The exception raised.
606+
* @return Appropriate exception instance to throw.
607+
*/
608+
private RuntimeException handleExecutionException(final ExecutionException executionException) {
609+
if (executionException.getCause() != null && executionException.getCause() instanceof RuntimeException) {
610+
return (RuntimeException) executionException.getCause();
606611
}
607-
return new RuntimeException(e.getMessage(), e);
612+
return new RuntimeException(executionException.getMessage(), executionException);
608613
}
609614

610615
/**

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/dto/ApiErrorResponse.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424

2525
package org.sourcelab.kafka.webview.ui.manager.kafka.dto;
2626

27-
import net.bytebuddy.implementation.bytecode.Throw;
28-
2927
import java.util.ArrayList;
3028
import java.util.Arrays;
3129
import java.util.List;

kafka-webview-ui/src/main/resources/templates/configuration/cluster/create.html

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
var isSaslChecked = jQuery('#sasl').is(':checked');
2323
jQuery('#ssl-keystore-options').toggle(!isSaslChecked);
2424
});
25+
jQuery('#useTrustStore').click(function() {
26+
var isChecked = jQuery('#useTrustStore').is(':checked');
27+
jQuery('#ssl-truststore-options').toggle(isChecked);
28+
});
2529
jQuery('#sasl').click(function() {
2630
var isChecked = jQuery('#sasl').is(':checked');
2731

@@ -114,37 +118,64 @@ <h6>SSL Settings</h6>
114118

115119
<!-- SSL Options -->
116120
<div id="ssl-options" th:styleappend="${clusterForm.getSsl()} ? 'display: block;' : 'display: none;'">
117-
<!-- Trust Store file -->
121+
<!-- Do you require upload of additional truststore? -->
118122
<div class="form-group row">
119-
<label class="col-md-3 form-control-label" for="trustStoreFile">
120-
Trust Store
121-
<div
122-
th:if="${clusterForm.getTrustStoreFilename()} != null"
123-
th:text="'Current: ' + ${clusterForm.getTrustStoreFilename()}">
124-
</div>
123+
<label class="col-md-3 form-control-label" for="useTrustStore">
124+
Upload truststore?
125125
</label>
126126
<div class="col-md-9">
127127
<input
128-
id="trustStoreFile" name="trustStoreFile" class="form-control" type="file"
129-
placeholder="Select TrustStore JKS"
128+
id="useTrustStore" name="useTrustStore" class="" type="checkbox"
130129
th:errorclass="is-invalid"
131-
th:value="*{trustStoreFile}">
132-
<div class="invalid-feedback" th:if="${#fields.hasErrors('trustStoreFile')}" th:errors="*{trustStoreFile}"/>
130+
th:field="*{useTrustStore}">
131+
132+
<small class="form-text text-muted">
133+
Only required if the certificate served by your cluster is not registered with an accessible certificate authority (CA), or
134+
if the cluster's certificate has not already been registered in your
135+
JVM's default truststore<span th:if="${defaultTrustStore} == null">.</span>
136+
<span th:if="${defaultTrustStore} != null"> at <i>[[${defaultTrustStore}]]</i></span>
137+
</small>
133138
</div>
134139
</div>
135140

136-
<!-- Trust Store Password -->
137-
<div class="form-group row">
138-
<label class="col-md-3 form-control-label" for="trustStorePassword">
139-
Trust Store Password
140-
</label>
141-
<div class="col-md-9">
142-
<input
143-
id="trustStorePassword" name="trustStorePassword" class="form-control" type="password"
144-
placeholder="TrustStore password"
145-
th:errorclass="is-invalid"
146-
th:value="*{trustStorePassword}">
147-
<div class="invalid-feedback" th:if="${#fields.hasErrors('trustStorePassword')}" th:errors="*{trustStorePassword}"/>
141+
<!-- Only display if trust store is configured -->
142+
<div id="ssl-truststore-options" th:styleappend="${clusterForm.hasTrustStoreFilename()} ? 'display: block;' : 'display: none;'">
143+
144+
<!-- Trust Store file -->
145+
<div class="form-group row">
146+
<label class="col-md-3 form-control-label" for="trustStoreFile">
147+
Trust Store
148+
<div
149+
th:if="${clusterForm.getTrustStoreFilename()} != null"
150+
th:text="'Current: ' + ${clusterForm.getTrustStoreFilename()}">
151+
</div>
152+
</label>
153+
<div class="col-md-9">
154+
<input
155+
id="trustStoreFile" name="trustStoreFile" class="form-control" type="file"
156+
placeholder="Select TrustStore JKS"
157+
th:errorclass="is-invalid"
158+
th:value="*{trustStoreFile}">
159+
<div class="invalid-feedback" th:if="${#fields.hasErrors('trustStoreFile')}" th:errors="*{trustStoreFile}"/>
160+
<small class="form-text text-muted" th:if="${clusterForm.getTrustStoreFilename()} != null">
161+
Leave empty to continue using previously uploaded TrustStore.
162+
</small>
163+
</div>
164+
</div>
165+
166+
<!-- Trust Store Password -->
167+
<div class="form-group row">
168+
<label class="col-md-3 form-control-label" for="trustStorePassword">
169+
Trust Store Password
170+
</label>
171+
<div class="col-md-9">
172+
<input
173+
id="trustStorePassword" name="trustStorePassword" class="form-control" type="password"
174+
placeholder="TrustStore password"
175+
th:errorclass="is-invalid"
176+
th:value="*{trustStorePassword}">
177+
<div class="invalid-feedback" th:if="${#fields.hasErrors('trustStorePassword')}" th:errors="*{trustStorePassword}"/>
178+
</div>
148179
</div>
149180
</div>
150181

0 commit comments

Comments
 (0)