-
Notifications
You must be signed in to change notification settings - Fork 25.3k
Reload secure settings with password #43197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8956cfb
76a88de
dabb53b
c647b24
9dc75a4
ec8019a
c13558f
a515988
1abb1e9
2760e59
86bffbd
33cedf0
fccb76f
01c4787
7fa9648
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,16 +21,20 @@ | |
|
||
import org.apache.logging.log4j.message.ParameterizedMessage; | ||
import org.apache.logging.log4j.util.Supplier; | ||
import org.elasticsearch.ElasticsearchException; | ||
import org.elasticsearch.ExceptionsHelper; | ||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.FailedNodeException; | ||
import org.elasticsearch.action.support.ActionFilters; | ||
import org.elasticsearch.action.support.nodes.BaseNodeRequest; | ||
import org.elasticsearch.action.support.nodes.TransportNodesAction; | ||
import org.elasticsearch.cluster.node.DiscoveryNode; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.inject.Inject; | ||
import org.elasticsearch.common.io.stream.StreamInput; | ||
import org.elasticsearch.common.io.stream.StreamOutput; | ||
import org.elasticsearch.common.settings.KeyStoreWrapper; | ||
import org.elasticsearch.common.settings.SecureString; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.env.Environment; | ||
import org.elasticsearch.plugins.PluginsService; | ||
|
@@ -78,15 +82,39 @@ protected NodesReloadSecureSettingsResponse.NodeResponse newNodeResponse() { | |
return new NodesReloadSecureSettingsResponse.NodeResponse(); | ||
} | ||
|
||
@Override | ||
protected void doExecute(Task task, NodesReloadSecureSettingsRequest request, | ||
ActionListener<NodesReloadSecureSettingsResponse> listener) { | ||
if (request.hasPassword() && isNodeLocal(request) == false && isNodeTransportTLSEnabled() == false) { | ||
request.closePassword(); | ||
listener.onFailure( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this checking be done at the rest layer, rather than in the transport action? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had similar concerns when implementing this but I think that it makes more sense to check this here, especially because of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would feel more comfortable if we called |
||
new ElasticsearchException("Secure settings cannot be updated cluster wide when TLS for the transport layer" + | ||
" is not enabled. Enable TLS or use the API with a `_local` filter on each node.")); | ||
} else { | ||
super.doExecute(task, request, ActionListener.wrap(response -> { | ||
request.closePassword(); | ||
listener.onResponse(response); | ||
}, e -> { | ||
request.closePassword(); | ||
listener.onFailure(e); | ||
})); | ||
} | ||
} | ||
|
||
@Override | ||
protected NodesReloadSecureSettingsResponse.NodeResponse nodeOperation(NodeRequest nodeReloadRequest, Task task) { | ||
final NodesReloadSecureSettingsRequest request = nodeReloadRequest.request; | ||
// We default to using an empty string as the keystore password so that we mimic pre 7.3 API behavior | ||
final SecureString secureSettingsPassword = request.hasPassword() ? request.getSecureSettingsPassword() : | ||
jkakavas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
new SecureString(new char[0]); | ||
try (KeyStoreWrapper keystore = KeyStoreWrapper.load(environment.configFile())) { | ||
// reread keystore from config file | ||
if (keystore == null) { | ||
return new NodesReloadSecureSettingsResponse.NodeResponse(clusterService.localNode(), | ||
new IllegalStateException("Keystore is missing")); | ||
} | ||
keystore.decrypt(new char[0]); | ||
// decrypt the keystore using the password from the request | ||
keystore.decrypt(secureSettingsPassword.getChars()); | ||
jkakavas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// add the keystore to the original node settings object | ||
final Settings settingsWithKeystore = Settings.builder() | ||
.put(environment.settings(), false) | ||
|
@@ -107,6 +135,8 @@ protected NodesReloadSecureSettingsResponse.NodeResponse nodeOperation(NodeReque | |
return new NodesReloadSecureSettingsResponse.NodeResponse(clusterService.localNode(), null); | ||
} catch (final Exception e) { | ||
return new NodesReloadSecureSettingsResponse.NodeResponse(clusterService.localNode(), e); | ||
} finally { | ||
secureSettingsPassword.close(); | ||
} | ||
} | ||
|
||
|
@@ -134,4 +164,20 @@ public void writeTo(StreamOutput out) throws IOException { | |
request.writeTo(out); | ||
} | ||
} | ||
|
||
/** | ||
* Returns true if the node is configured for TLS on the transport layer | ||
*/ | ||
private boolean isNodeTransportTLSEnabled() { | ||
return transportService.isTransportSecure(); | ||
} | ||
|
||
private boolean isNodeLocal(NodesReloadSecureSettingsRequest request) { | ||
if (null == request.concreteNodes()) { | ||
resolveRequest(request, clusterService.state()); | ||
assert request.concreteNodes() != null; | ||
} | ||
final DiscoveryNode[] nodes = request.concreteNodes(); | ||
return nodes.length == 1 && nodes[0].getId().equals(clusterService.localNode().getId()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A somewhat orthogonal point, but it seems like most of the places we use CharArrays like this we have a BytesReference, and we end up copying to a byte array in order to convert to chars. I wonder if we could flip the CharArrays input around so it takes a BytesReference, so that we don't need to have the try/finally like this. In the few cases we actually have a real bytes array, we can wrap the array in a bytesreference. It's not necessary to do here, but I think it would make these uses much easier to deal with and not so messy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make an issue out of your comment and tackle it in all cases we have this in a follow up PR