Skip to content

Commit eefd46f

Browse files
committed
1 parent bd27205 commit eefd46f

File tree

7 files changed

+247
-0
lines changed

7 files changed

+247
-0
lines changed

modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public final class IgniteNodeAttributes {
150150
/** V2 security subject for authenticated node. */
151151
public static final String ATTR_SECURITY_SUBJECT_V2 = ATTR_PREFIX + ".security.subject.v2";
152152

153+
/** Node certificates with which the connection was established. */
154+
public static final String ATTR_NODE_CERTIFICATES = ATTR_PREFIX + ".security.certificates";
155+
153156
/** Client mode flag. */
154157
public static final String ATTR_CLIENT_MODE = ATTR_PREFIX + ".cache.client";
155158

modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,7 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms
23012301
}
23022302

23032303
locNode.setAttributes(msg.clientNodeAttributes());
2304+
locNode.clearCertificates();
23042305
locNode.visible(true);
23052306

23062307
long topVer = msg.topologyVersion();
@@ -2357,6 +2358,7 @@ else if (log.isDebugEnabled())
23572358

23582359
if (!node.visible()) {
23592360
node.order(topVer);
2361+
node.clearCertificates();
23602362
node.visible(true);
23612363

23622364
if (spi.locNodeVer.equals(node.version()))

modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5298,6 +5298,8 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms
52985298
if (msg.verified()) {
52995299
assert topVer > 0 : "Invalid topology version: " + msg;
53005300

5301+
node.clearCertificates();
5302+
53015303
if (node.order() == 0)
53025304
node.order(topVer);
53035305

@@ -7072,6 +7074,11 @@ else if (e.hasCause(ObjectStreamException.class) ||
70727074
else if (msg instanceof TcpDiscoveryJoinRequestMessage) {
70737075
TcpDiscoveryJoinRequestMessage req = (TcpDiscoveryJoinRequestMessage)msg;
70747076

7077+
// Current node holds connection with the node that is joining the cluster. Therefore, it can
7078+
// save certificates with which the connection was established to joining node attributes.
7079+
if (spi.nodeAuth != null && nodeId.equals(req.node().id()))
7080+
req.node().setCertificates(ses.extractCertificates());
7081+
70757082
if (!req.responded()) {
70767083
boolean ok = processJoinRequestMessage(req, clientMsgWrk);
70777084

modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import java.io.StreamCorruptedException;
2828
import java.net.Socket;
2929
import java.nio.ByteBuffer;
30+
import java.security.cert.Certificate;
31+
import javax.net.ssl.SSLPeerUnverifiedException;
32+
import javax.net.ssl.SSLSocket;
3033
import org.apache.ignite.IgniteCheckedException;
3134
import org.apache.ignite.IgniteException;
3235
import org.apache.ignite.internal.direct.DirectMessageReader;
@@ -36,6 +39,7 @@
3639
import org.apache.ignite.plugin.extensions.communication.Message;
3740
import org.apache.ignite.plugin.extensions.communication.MessageSerializer;
3841
import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
42+
import org.jetbrains.annotations.Nullable;
3943

4044
import static org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi.makeMessageType;
4145

@@ -205,6 +209,21 @@ <T> T readMessage() throws IgniteCheckedException, IOException {
205209
}
206210
}
207211

212+
/** @return SSL certificate this session is established with. {@code null} if SSL is disabled or certificate validation failed. */
213+
@Nullable Certificate[] extractCertificates() {
214+
if (!spi.isSslEnabled())
215+
return null;
216+
217+
try {
218+
return ((SSLSocket)sock).getSession().getPeerCertificates();
219+
}
220+
catch (SSLPeerUnverifiedException e) {
221+
U.error(spi.log, "Failed to extract discovery IO session certificates", e);
222+
223+
return null;
224+
}
225+
}
226+
208227
/**
209228
* Serializes a discovery message into a byte array.
210229
*

modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryNode.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.io.ObjectOutput;
2424
import java.io.Serializable;
2525
import java.net.InetSocketAddress;
26+
import java.security.cert.Certificate;
2627
import java.util.ArrayList;
2728
import java.util.Collection;
2829
import java.util.Collections;
@@ -48,6 +49,7 @@
4849
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
4950
import org.jetbrains.annotations.Nullable;
5051

52+
import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_NODE_CERTIFICATES;
5153
import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_NODE_CONSISTENT_ID;
5254
import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.eqNodes;
5355

@@ -251,6 +253,31 @@ public void lastSuccessfulAddress(InetSocketAddress lastSuccessfulAddr) {
251253
});
252254
}
253255

256+
/**
257+
* Sets nodes SSL certificates with which connection was established.
258+
*
259+
* @param certificates SSL certificates.
260+
*/
261+
public void setCertificates(@Nullable Certificate[] certificates) {
262+
Map<String, Object> attrs = new HashMap<>(this.attrs);
263+
264+
attrs.put(IgniteNodeAttributes.ATTR_NODE_CERTIFICATES, certificates);
265+
266+
setAttributes(attrs);
267+
}
268+
269+
/** Clears nodes SSL certificates with which connection was established. */
270+
public void clearCertificates() {
271+
if (!attrs.containsKey(ATTR_NODE_CERTIFICATES))
272+
return;
273+
274+
Map<String, Object> attrs = new HashMap<>(this.attrs);
275+
276+
attrs.remove(IgniteNodeAttributes.ATTR_NODE_CERTIFICATES);
277+
278+
setAttributes(attrs);
279+
}
280+
254281
/**
255282
* Sets node attributes.
256283
*
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal.processors.security;
19+
20+
import java.security.Permissions;
21+
import java.security.cert.Certificate;
22+
import java.security.cert.X509Certificate;
23+
import java.util.Arrays;
24+
import java.util.Collection;
25+
import java.util.UUID;
26+
import java.util.concurrent.ConcurrentLinkedQueue;
27+
import java.util.concurrent.CountDownLatch;
28+
import org.apache.ignite.Ignite;
29+
import org.apache.ignite.IgniteCheckedException;
30+
import org.apache.ignite.cluster.ClusterNode;
31+
import org.apache.ignite.configuration.IgniteConfiguration;
32+
import org.apache.ignite.internal.GridKernalContext;
33+
import org.apache.ignite.internal.IgniteEx;
34+
import org.apache.ignite.internal.processors.security.impl.TestSecurityData;
35+
import org.apache.ignite.internal.processors.security.impl.TestSecurityPluginProvider;
36+
import org.apache.ignite.internal.processors.security.impl.TestSecurityProcessor;
37+
import org.apache.ignite.internal.util.typedef.G;
38+
import org.apache.ignite.plugin.security.SecurityCredentials;
39+
import org.apache.ignite.plugin.security.SecurityPermissionSet;
40+
import org.apache.ignite.testframework.GridTestUtils;
41+
import org.junit.Test;
42+
43+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
44+
import static org.apache.ignite.events.EventType.EVT_CLIENT_NODE_RECONNECTED;
45+
import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_NODE_CERTIFICATES;
46+
import static org.apache.ignite.plugin.security.SecurityPermission.JOIN_AS_SERVER;
47+
import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.NO_PERMISSIONS;
48+
import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.systemPermissions;
49+
50+
/** */
51+
public class NodeConnectionCertificateCapturingTest extends AbstractSecurityTest {
52+
/** */
53+
private static final Collection<AuthenticationEvent> NODE_AUTHENTICATION_EVENTS = new ConcurrentLinkedQueue<>();
54+
55+
/** */
56+
@Test
57+
public void testNodeConnectionCertificateCapturing() throws Exception{
58+
checkNewNodesAuthenticationByClusterNode(0, false, 1);
59+
checkNewNodesAuthenticationByClusterNode(1, false, 2);
60+
checkNewNodesAuthenticationByClusterNode(2, false, 3);
61+
checkNewNodesAuthenticationByClusterNode(3, true, 3);
62+
63+
// Checks nodes restart.
64+
stopGrid(2);
65+
stopGrid(3);
66+
67+
checkNewNodesAuthenticationByClusterNode(3, true, 2);
68+
checkNewNodesAuthenticationByClusterNode(2, false, 3);
69+
70+
71+
// Checks client node reconnect.
72+
NODE_AUTHENTICATION_EVENTS.clear();
73+
74+
CountDownLatch cliNodeReconnectedLatch = new CountDownLatch(1);
75+
76+
grid(3).events().localListen(evt -> {
77+
cliNodeReconnectedLatch.countDown();
78+
79+
return true;
80+
}, EVT_CLIENT_NODE_RECONNECTED);
81+
82+
grid(0).context().discovery().failNode(grid(3).localNode().id(), "test");
83+
84+
assertTrue(cliNodeReconnectedLatch.await(getTestTimeout(), MILLISECONDS));
85+
86+
checkNodeAuthenticationByClusterNode(3, grid(3).localNode().id(), true, 3);
87+
}
88+
89+
/** */
90+
private void checkNewNodesAuthenticationByClusterNode(int authNodeIdx, boolean isClient, int expAuthCnt) throws Exception {
91+
NODE_AUTHENTICATION_EVENTS.clear();
92+
93+
UUID authNodeId = startGrid(authNodeIdx, isClient).cluster().localNode().id();
94+
95+
checkNodeAuthenticationByClusterNode(authNodeIdx, authNodeId, isClient, expAuthCnt);
96+
}
97+
98+
/** */
99+
private void checkNodeAuthenticationByClusterNode(int authNodeIdx, UUID authNodeId, boolean isClient, int expAuthCnt) {
100+
assertEquals(expAuthCnt, NODE_AUTHENTICATION_EVENTS.size());
101+
102+
for (AuthenticationEvent auth : NODE_AUTHENTICATION_EVENTS) {
103+
if (auth.clusterNodeId.equals(authNodeId))
104+
assertNull(auth.certs);
105+
else {
106+
assertEquals(2, auth.certs.length);
107+
108+
X509Certificate cert = (X509Certificate)auth.certs[0];
109+
110+
assertEquals(isClient ? "CN=client" : "CN=node0" + (authNodeIdx + 1), cert.getSubjectDN().getName());
111+
}
112+
}
113+
114+
for (Ignite ignite : G.allGrids()) {
115+
for (ClusterNode node : ignite.cluster().nodes())
116+
assertNull(node.attribute(ATTR_NODE_CERTIFICATES));
117+
}
118+
}
119+
120+
/** */
121+
private IgniteEx startGrid(int idx, boolean isClient) throws Exception {
122+
String login = getTestIgniteInstanceName(idx);
123+
124+
IgniteConfiguration cfg = getConfiguration(
125+
login,
126+
new SecurityPluginProvider(
127+
login,
128+
"",
129+
isClient ? NO_PERMISSIONS : systemPermissions(JOIN_AS_SERVER),
130+
null,
131+
true
132+
)).setClientMode(isClient);
133+
134+
cfg.setSslContextFactory(GridTestUtils.sslTrustedFactory(isClient ? "client" : "node0" + (idx + 1), "trustboth"));
135+
136+
return startGrid(cfg);
137+
}
138+
139+
/** */
140+
private static class SecurityPluginProvider extends TestSecurityPluginProvider {
141+
/** */
142+
SecurityPluginProvider(
143+
String login,
144+
String pwd,
145+
SecurityPermissionSet perms,
146+
Permissions sandboxPerms,
147+
boolean globalAuth,
148+
TestSecurityData... clientData
149+
) {
150+
super(login, pwd, perms, sandboxPerms, globalAuth, clientData);
151+
}
152+
153+
/** {@inheritDoc} */
154+
@Override protected GridSecurityProcessor securityProcessor(GridKernalContext ctx) {
155+
return new TestSecurityProcessor(
156+
ctx,
157+
new TestSecurityData(login, pwd, perms, sandboxPerms),
158+
Arrays.asList(clientData),
159+
globalAuth
160+
) {
161+
@Override public SecurityContext authenticateNode(
162+
ClusterNode node,
163+
SecurityCredentials cred
164+
) throws IgniteCheckedException {
165+
NODE_AUTHENTICATION_EVENTS.add(new AuthenticationEvent(ctx.localNodeId(), node.attribute(ATTR_NODE_CERTIFICATES)));
166+
167+
return super.authenticateNode(node, cred);
168+
}
169+
};
170+
}
171+
}
172+
173+
/** */
174+
private static class AuthenticationEvent {
175+
/** */
176+
UUID clusterNodeId;
177+
178+
/** */
179+
Certificate[] certs;
180+
181+
/** */
182+
AuthenticationEvent(UUID clusterNodeId, Certificate[] certs) {
183+
this.clusterNodeId = clusterNodeId;
184+
this.certs = certs;
185+
}
186+
}
187+
}

modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.apache.ignite.internal.processors.security.IgniteSecurityProcessorTest;
2121
import org.apache.ignite.internal.processors.security.InvalidServerTest;
22+
import org.apache.ignite.internal.processors.security.NodeConnectionCertificateCapturingTest;
2223
import org.apache.ignite.internal.processors.security.NodeSecurityContextPropagationTest;
2324
import org.apache.ignite.internal.processors.security.SecurityContextInternalFuturePropagationTest;
2425
import org.apache.ignite.internal.processors.security.cache.CacheOperationPermissionCheckTest;
@@ -143,6 +144,7 @@
143144
NodeJoinPermissionsTest.class,
144145
ActivationOnJoinWithoutPermissionsWithPersistenceTest.class,
145146
SecurityContextInternalFuturePropagationTest.class,
147+
NodeConnectionCertificateCapturingTest.class
146148
})
147149
public class SecurityTestSuite {
148150
/** */

0 commit comments

Comments
 (0)