Skip to content

Commit 1af7fcd

Browse files
committed
IGNITE-27216 Added capturing of cluster node certificates during join process
1 parent 94ec940 commit 1af7fcd

File tree

15 files changed

+336
-48
lines changed

15 files changed

+336
-48
lines changed

modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.ignite.internal.managers.discovery;
1919

2020
import java.io.Serializable;
21+
import java.security.cert.Certificate;
2122
import java.util.ArrayDeque;
2223
import java.util.ArrayList;
2324
import java.util.Collection;
@@ -121,6 +122,7 @@
121122
import org.apache.ignite.lang.IgniteProductVersion;
122123
import org.apache.ignite.lang.IgniteUuid;
123124
import org.apache.ignite.marshaller.Marshaller;
125+
import org.apache.ignite.plugin.security.AuthenticationContext;
124126
import org.apache.ignite.plugin.security.SecurityCredentials;
125127
import org.apache.ignite.plugin.segmentation.SegmentationPolicy;
126128
import org.apache.ignite.spi.IgniteSpiException;
@@ -518,9 +520,9 @@ private void updateClientNodes(UUID leftNodeId) {
518520
ctx.addNodeAttribute(ATTR_SECURITY_COMPATIBILITY_MODE, true);
519521

520522
spi.setAuthenticator(new DiscoverySpiNodeAuthenticator() {
521-
@Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) {
523+
@Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred, Certificate[] certs) {
522524
try {
523-
return ctx.security().authenticateNode(node, cred);
525+
return ctx.security().authenticate(new AuthenticationContext(node, cred, certs));
524526
}
525527
catch (IgniteCheckedException e) {
526528
throw U.convertException(e);

modules/core/src/main/java/org/apache/ignite/internal/processors/security/GridSecurityProcessor.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.apache.ignite.plugin.security.SecurityPermission;
3030
import org.apache.ignite.plugin.security.SecuritySubject;
3131

32+
import static org.apache.ignite.plugin.security.SecuritySubjectType.REMOTE_NODE;
33+
3234
/**
3335
* This interface is responsible for:
3436
* <ul>
@@ -46,30 +48,52 @@
4648
*/
4749
public interface GridSecurityProcessor extends GridProcessor {
4850
/**
49-
* Authenticates grid node with it's attributes via underlying Authenticator.
51+
* Authenticates grid node.
5052
*
51-
* @param node Node id to authenticate.
52-
* @param cred Security credentials.
53-
* @return {@code True} if succeeded, {@code false} otherwise.
53+
* @param node Cluster node.
54+
* @param cred Credentials of the node user.
55+
* @return {@code null} if authentication failed or {@link SecurityContext} instance that represents successfully authenticated user.
5456
* @throws IgniteCheckedException If error occurred.
57+
* @deprecated Use {@link #performAuthentication(AuthenticationContext)} instead.
5558
*/
56-
public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) throws IgniteCheckedException;
59+
@Deprecated
60+
public default SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) throws IgniteCheckedException {
61+
throw new UnsupportedOperationException();
62+
}
5763

5864
/**
59-
* Gets flag indicating whether all nodes or coordinator only should run the authentication for joining node.
65+
* Authenticates subject.
6066
*
61-
* @return {@code True} if all nodes should run authentication process, {@code false} otherwise.
67+
* @param ctx Authentication context.
68+
* @return {@code null} if authentication failed or {@link SecurityContext} instance that represents successfully authenticated user.
69+
* @throws IgniteCheckedException If error occurred.
70+
* @deprecated Use {@link #performAuthentication(AuthenticationContext)} instead.
6271
*/
63-
public boolean isGlobalNodeAuthentication();
72+
@Deprecated
73+
public default SecurityContext authenticate(AuthenticationContext ctx) throws IgniteCheckedException {
74+
throw new UnsupportedOperationException();
75+
}
6476

6577
/**
66-
* Authenticates subject via underlying Authenticator.
78+
* Authenticates subject.
6779
*
6880
* @param ctx Authentication context.
69-
* @return {@code True} if succeeded, {@code false} otherwise.
81+
* @return {@code null} if authentication failed or {@link SecurityContext} instance that represents successfully authenticated user.
7082
* @throws IgniteCheckedException If error occurred.
7183
*/
72-
public SecurityContext authenticate(AuthenticationContext ctx) throws IgniteCheckedException;
84+
public default SecurityContext performAuthentication(AuthenticationContext ctx) throws IgniteCheckedException {
85+
if (ctx.subjectType() == REMOTE_NODE)
86+
return authenticateNode(ctx.node(), ctx.credentials());
87+
88+
return authenticate(ctx);
89+
}
90+
91+
/**
92+
* Gets flag indicating whether all nodes or coordinator only should run the authentication for joining node.
93+
*
94+
* @return {@code True} if all nodes should run authentication process, {@code false} otherwise.
95+
*/
96+
public boolean isGlobalNodeAuthentication();
7397

7498
/**
7599
* Gets collection of authenticated nodes.

modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurity.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@
2020
import java.util.Collection;
2121
import java.util.UUID;
2222
import org.apache.ignite.IgniteCheckedException;
23-
import org.apache.ignite.cluster.ClusterNode;
2423
import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
2524
import org.apache.ignite.plugin.security.AuthenticationContext;
26-
import org.apache.ignite.plugin.security.SecurityCredentials;
2725
import org.apache.ignite.plugin.security.SecurityException;
2826
import org.apache.ignite.plugin.security.SecurityPermission;
2927
import org.apache.ignite.plugin.security.SecuritySubject;
@@ -69,19 +67,13 @@ public interface IgniteSecurity {
6967
*/
7068
public SecurityContext securityContext();
7169

72-
/**
73-
* Delegates call to {@link GridSecurityProcessor#authenticateNode(org.apache.ignite.cluster.ClusterNode,
74-
* org.apache.ignite.plugin.security.SecurityCredentials)}
75-
*/
76-
public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) throws IgniteCheckedException;
77-
7870
/**
7971
* Delegates call to {@link GridSecurityProcessor#isGlobalNodeAuthentication()}
8072
*/
8173
public boolean isGlobalNodeAuthentication();
8274

8375
/**
84-
* Delegates call to {@link GridSecurityProcessor#authenticate(AuthenticationContext)}
76+
* Delegates call to {@link GridSecurityProcessor#performAuthentication(AuthenticationContext)}
8577
*/
8678
public SecurityContext authenticate(AuthenticationContext ctx) throws IgniteCheckedException;
8779

modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.apache.ignite.lang.IgniteFuture;
3838
import org.apache.ignite.marshaller.jdk.JdkMarshaller;
3939
import org.apache.ignite.plugin.security.AuthenticationContext;
40-
import org.apache.ignite.plugin.security.SecurityCredentials;
4140
import org.apache.ignite.plugin.security.SecurityException;
4241
import org.apache.ignite.plugin.security.SecurityPermission;
4342
import org.apache.ignite.plugin.security.SecuritySubject;
@@ -204,20 +203,14 @@ void restoreDefaultContext() {
204203
return res == null ? dfltSecCtx : res;
205204
}
206205

207-
/** {@inheritDoc} */
208-
@Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred)
209-
throws IgniteCheckedException {
210-
return secPrc.authenticateNode(node, cred);
211-
}
212-
213206
/** {@inheritDoc} */
214207
@Override public boolean isGlobalNodeAuthentication() {
215208
return secPrc.isGlobalNodeAuthentication();
216209
}
217210

218211
/** {@inheritDoc} */
219212
@Override public SecurityContext authenticate(AuthenticationContext ctx) throws IgniteCheckedException {
220-
return secPrc.authenticate(ctx);
213+
return secPrc.performAuthentication(ctx);
221214
}
222215

223216
/** {@inheritDoc} */

modules/core/src/main/java/org/apache/ignite/internal/processors/security/NoOpIgniteSecurityProcessor.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.apache.ignite.internal.processors.security.sandbox.IgniteSandbox;
2727
import org.apache.ignite.internal.processors.security.sandbox.NoOpSandbox;
2828
import org.apache.ignite.plugin.security.AuthenticationContext;
29-
import org.apache.ignite.plugin.security.SecurityCredentials;
3029
import org.apache.ignite.plugin.security.SecurityException;
3130
import org.apache.ignite.plugin.security.SecurityPermission;
3231
import org.apache.ignite.plugin.security.SecuritySubject;
@@ -81,11 +80,6 @@ public NoOpIgniteSecurityProcessor(GridKernalContext ctx) {
8180
return null;
8281
}
8382

84-
/** {@inheritDoc} */
85-
@Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) {
86-
return null;
87-
}
88-
8983
/** {@inheritDoc} */
9084
@Override public boolean isGlobalNodeAuthentication() {
9185
return false;

modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ public static SecurityContext authenticateLocalNode(
374374
assert nodeAuth != null;
375375
assert cred != null || node.attribute(IgniteNodeAttributes.ATTR_AUTHENTICATION_ENABLED) != null;
376376

377-
SecurityContext secCtx = nodeAuth.authenticateNode(node, cred);
377+
SecurityContext secCtx = nodeAuth.authenticateNode(node, cred, null);
378378

379379
if (secCtx == null)
380380
throw new IgniteSpiException("Authentication failed for local node: " + node.id());

modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
import java.util.HashMap;
2424
import java.util.Map;
2525
import java.util.UUID;
26+
import org.apache.ignite.cluster.ClusterNode;
2627
import org.apache.ignite.internal.util.typedef.F;
28+
import org.jetbrains.annotations.Nullable;
29+
30+
import static org.apache.ignite.plugin.security.SecuritySubjectType.REMOTE_NODE;
2731

2832
/**
2933
* Authentication context.
@@ -32,7 +36,7 @@ public class AuthenticationContext {
3236
/** Subject type. */
3337
private SecuritySubjectType subjType;
3438

35-
/** Subject ID.w */
39+
/** Subject ID. */
3640
private UUID subjId;
3741

3842
/** Credentials. */
@@ -47,8 +51,52 @@ public class AuthenticationContext {
4751
/** True if this is a client node context. */
4852
private boolean client;
4953

50-
/** Client SSL certificates. */
51-
private Certificate[] certs;
54+
/** User SSL certificates. */
55+
@Nullable private Certificate[] certs;
56+
57+
/** Instance of the authenticated node. {@code null} if authenticated subject type is not {@link SecuritySubjectType#REMOTE_NODE}. */
58+
@Nullable private ClusterNode node;
59+
60+
/** */
61+
public AuthenticationContext() {
62+
// No-op.
63+
}
64+
65+
/**
66+
* @param node Authenticating cluster node.
67+
* @param creds Credentials of the node's user.
68+
* @param certs Certificates that the node used to establish a connection to the cluster. {@code null} if certificates
69+
* could not be obtained.
70+
*/
71+
public AuthenticationContext(ClusterNode node, SecurityCredentials creds, @Nullable Certificate[] certs) {
72+
this.node = node;
73+
this.creds = creds;
74+
this.certs = certs;
75+
76+
subjType = REMOTE_NODE;
77+
subjId = node.id();
78+
client = node.isClient();
79+
nodeAttrs = node.attributes();
80+
addr = new InetSocketAddress(F.first(node.addresses()), 0);
81+
}
82+
83+
/**
84+
* @return Cluster node being authenticated or {@code null} if authenticated subject type is not
85+
* {@link SecuritySubjectType#REMOTE_NODE}.
86+
*/
87+
@Nullable public ClusterNode node() {
88+
return node;
89+
}
90+
91+
/**
92+
* @param node Cluster node being authenticated.
93+
* @return {@code this} for chaining.
94+
*/
95+
public AuthenticationContext node(ClusterNode node) {
96+
this.node = node;
97+
98+
return this;
99+
}
52100

53101
/**
54102
* Gets subject type.

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,50 @@
1717

1818
package org.apache.ignite.spi.discovery;
1919

20+
import java.security.cert.Certificate;
2021
import org.apache.ignite.IgniteException;
2122
import org.apache.ignite.cluster.ClusterNode;
2223
import org.apache.ignite.internal.processors.security.SecurityContext;
2324
import org.apache.ignite.plugin.security.SecurityCredentials;
25+
import org.jetbrains.annotations.Nullable;
2426

2527
/**
2628
* Node authenticator.
2729
*/
2830
public interface DiscoverySpiNodeAuthenticator {
2931
/**
30-
* Security credentials.
32+
* Authenticates new cluster node before it joins the cluster.
3133
*
3234
* @param node Node to authenticate.
3335
* @param cred Security credentials.
3436
* @return Security context if authentication succeeded or {@code null} if authentication failed.
3537
* @throws IgniteException If authentication process failed
3638
* (invalid credentials should not lead to this exception).
39+
* @deprecated Use {@link #authenticateNode(ClusterNode, SecurityCredentials, Certificate[])} instead.
3740
*/
38-
public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) throws IgniteException;
41+
@Deprecated
42+
public default SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) throws IgniteException {
43+
throw new UnsupportedOperationException();
44+
}
45+
46+
/**
47+
* Authenticates new cluster node before it joins the cluster.
48+
*
49+
* @param node Node to authenticate.
50+
* @param cred Security credentials.
51+
* @param certs Certificates that the node used to establish a connection to the cluster. {@code null} if SSL is
52+
* disabled, it is local node authentication or Ignite Discovery implementation does not support
53+
* capturing of joining node certificates.
54+
* @return Security context if authentication succeeded or {@code null} if authentication failed.
55+
* @throws IgniteException If authentication process failed (invalid credentials should not lead to this exception).
56+
*/
57+
public default SecurityContext authenticateNode(
58+
ClusterNode node,
59+
SecurityCredentials cred,
60+
@Nullable Certificate[] certs
61+
) throws IgniteException {
62+
return authenticateNode(node, cred);
63+
}
3964

4065
/**
4166
* Gets global node authentication flag.

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4324,7 +4324,7 @@ else if (log.isDebugEnabled())
43244324
try {
43254325
SecurityCredentials cred = unmarshalCredentials(node);
43264326

4327-
SecurityContext subj = spi.nodeAuth.authenticateNode(node, cred);
4327+
SecurityContext subj = spi.nodeAuth.authenticateNode(node, cred, msg.nodeCertificates());
43284328

43294329
if (subj == null) {
43304330
// Node has not pass authentication.
@@ -4774,8 +4774,13 @@ else if (log.isDebugEnabled())
47744774

47754775
DiscoveryDataPacket data = msg.gridDiscoveryData();
47764776

4777-
TcpDiscoveryNodeAddedMessage nodeAddedMsg = new TcpDiscoveryNodeAddedMessage(locNodeId,
4778-
node, data, spi.gridStartTime);
4777+
TcpDiscoveryNodeAddedMessage nodeAddedMsg = new TcpDiscoveryNodeAddedMessage(
4778+
locNodeId,
4779+
node,
4780+
data,
4781+
spi.gridStartTime,
4782+
msg.nodeCertificates()
4783+
);
47794784

47804785
nodeAddedMsg = tracing.messages().branch(nodeAddedMsg, msg);
47814786

@@ -5084,7 +5089,7 @@ else if (!locNodeId.equals(node.id()) && ring.node(node.id()) != null) {
50845089
authFailed = false;
50855090
}
50865091
else {
5087-
SecurityContext subj = spi.nodeAuth.authenticateNode(node, cred);
5092+
SecurityContext subj = spi.nodeAuth.authenticateNode(node, cred, msg.nodeCertificates());
50885093

50895094
if (subj == null) {
50905095
// Node has not pass authentication.
@@ -7072,6 +7077,11 @@ else if (e.hasCause(ObjectStreamException.class) ||
70727077
else if (msg instanceof TcpDiscoveryJoinRequestMessage) {
70737078
TcpDiscoveryJoinRequestMessage req = (TcpDiscoveryJoinRequestMessage)msg;
70747079

7080+
// We are holding connection with the node that is joining the cluster. Therefore, we can save
7081+
// certificates with which the connection was established.
7082+
if (nodeId.equals(req.node().id()))
7083+
req.nodeCertificates(ses.extractCertificates());
7084+
70757085
if (!req.responded()) {
70767086
boolean ok = processJoinRequestMessage(req, clientMsgWrk);
70777087

0 commit comments

Comments
 (0)