Skip to content

Commit a6981bf

Browse files
committed
oncrpc: add support for AUTH_TLS
Motivation: To secure NFS over public networks there is an IETF activity to define rpc-over-tls. To avoid extra TCP port number allocation and compatibility with non TLS clients a start TLS mechanism in introduced by AUTH_TLS rpc security flavor. See https://datatracker.ietf.org/doc/draft-cel-nfsv4-rpc-tls/ Modification: To ensure, that TLS is enabled only after reply is sent to the client add a new filter that will update connections filter chain after write operation is complete. Added RpcTransport#startTLS method to enable TLS handshake. As IETF work still in a draft phase, the API modifications marked as `BETA`. Result: The current IETF draft of rpc-over-tls can be supported. Acked-by: Paul Millar Target: master
1 parent d7f934b commit a6981bf

File tree

10 files changed

+457
-48
lines changed

10 files changed

+457
-48
lines changed

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/grizzly/GrizzlyRpcTransport.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton,
2+
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
33
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
44
*
55
* This library is free software; you can redistribute it and/or modify
@@ -31,7 +31,12 @@
3131
import org.glassfish.grizzly.EmptyCompletionHandler;
3232
import org.glassfish.grizzly.WriteResult;
3333
import org.glassfish.grizzly.asyncqueue.WritableMessage;
34+
import org.glassfish.grizzly.filterchain.FilterChain;
35+
import org.glassfish.grizzly.ssl.SSLFilter;
3436

37+
import org.dcache.oncrpc4j.rpc.RpcAuthError;
38+
import org.dcache.oncrpc4j.rpc.RpcAuthException;
39+
import org.dcache.oncrpc4j.rpc.RpcAuthStat;
3540
import org.dcache.oncrpc4j.rpc.RpcTransport;
3641

3742
import static java.util.Objects.requireNonNull;
@@ -108,4 +113,21 @@ public RpcTransport getPeerTransport() {
108113
public String toString() {
109114
return getRemoteSocketAddress() + " <=> " + getLocalSocketAddress();
110115
}
116+
117+
@Override
118+
public void startTLS() throws RpcAuthException {
119+
final FilterChain currentChain = (FilterChain) _connection.getProcessor();
120+
if (currentChain.indexOfType(SSLFilter.class) >= 0) {
121+
// already enabled
122+
throw new IllegalStateException("TLS is already enabled.");
123+
}
124+
125+
currentChain.stream()
126+
.filter(StartTlsFilter.class::isInstance)
127+
.findAny()
128+
.map(StartTlsFilter.class::cast)
129+
.orElseThrow(() -> new RpcAuthException("SSL is not configured",
130+
new RpcAuthError(RpcAuthStat.AUTH_FAILED)))
131+
.startTLS(_connection);
132+
}
111133
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) 2019 Deutsches Elektronen-Synchroton,
3+
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
4+
*
5+
* This library is free software; you can redistribute it and/or modify
6+
* it under the terms of the GNU Library General Public License as
7+
* published by the Free Software Foundation; either version 2 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Library General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Library General Public
16+
* License along with this program (see the file COPYING.LIB for more
17+
* details); if not, write to the Free Software Foundation, Inc.,
18+
* 675 Mass Ave, Cambridge, MA 02139, USA.
19+
*/
20+
package org.dcache.oncrpc4j.grizzly;
21+
22+
import com.google.common.annotations.Beta;
23+
import java.io.IOException;
24+
import org.dcache.oncrpc4j.rpc.RpcAuthError;
25+
import org.dcache.oncrpc4j.rpc.RpcAuthException;
26+
import org.dcache.oncrpc4j.rpc.RpcAuthStat;
27+
import org.glassfish.grizzly.Connection;
28+
import org.glassfish.grizzly.EmptyCompletionHandler;
29+
import org.glassfish.grizzly.filterchain.Filter;
30+
import org.glassfish.grizzly.filterchain.BaseFilter;
31+
import org.glassfish.grizzly.filterchain.FilterChain;
32+
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
33+
import org.glassfish.grizzly.filterchain.FilterChainContext;
34+
import org.glassfish.grizzly.filterchain.NextAction;
35+
import org.glassfish.grizzly.ssl.SSLFilter;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
38+
39+
/**
40+
* A pass-through {@link Filter} that will enable TLS on connection if required.
41+
*/
42+
@Beta
43+
public class StartTlsFilter extends BaseFilter {
44+
45+
private final static Logger LOGGER = LoggerFactory.getLogger(StartTlsFilter.class);
46+
47+
private final SSLFilter sslFilter;
48+
private final boolean isClient;
49+
private volatile boolean start;
50+
51+
public StartTlsFilter(SSLFilter sslFilter, boolean isClient) {
52+
this.sslFilter = sslFilter;
53+
this.isClient = isClient;
54+
}
55+
56+
@Override
57+
public NextAction handleWrite(FilterChainContext ctx) throws IOException {
58+
/** After service receives start-TLS it must reply to the client and then
59+
* enable TLS on the connection.
60+
*/
61+
62+
NextAction nextAction = super.handleWrite(ctx);
63+
if (start) {
64+
enableSSLFilter(ctx.getConnection());
65+
}
66+
67+
return nextAction;
68+
}
69+
70+
public void startTLS(Connection connection) throws RpcAuthException {
71+
start = true;
72+
if (isClient) {
73+
enableSSLFilter(connection);
74+
try {
75+
sslFilter.handshake(connection, new EmptyCompletionHandler<>());
76+
} catch (IOException e) {
77+
LOGGER.error("Failed to perform TLS handshake: {}", e.getMessage());
78+
throw new RpcAuthException("Failed to perform TLS handshake",
79+
new RpcAuthError(RpcAuthStat.AUTH_FAILED));
80+
}
81+
}
82+
}
83+
84+
private void enableSSLFilter(Connection connection) {
85+
final FilterChain currentChain = (FilterChain) connection.getProcessor();
86+
FilterChainBuilder chainBuilder = FilterChainBuilder
87+
.stateless()
88+
.addAll(currentChain)
89+
.remove(this)
90+
.add(1, sslFilter);
91+
connection.setProcessor(chainBuilder.build());
92+
}
93+
}

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvc.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.dcache.oncrpc4j.rpc;
2121

22+
import org.dcache.oncrpc4j.grizzly.StartTlsFilter;
2223
import org.dcache.oncrpc4j.rpc.net.IpProtocolType;
2324
import org.dcache.oncrpc4j.rpc.net.InetSocketAddresses;
2425
import org.dcache.oncrpc4j.rpc.gss.GssProtocolFilter;
@@ -104,6 +105,11 @@ public class OncRpcSvc {
104105
*/
105106
private final SSLContext _sslContext;
106107

108+
/**
109+
* Start TLS only when requested.
110+
*/
111+
private final boolean _startTLS;
112+
107113
/**
108114
* mapping of registered programs.
109115
*/
@@ -173,6 +179,7 @@ public class OncRpcSvc {
173179
_withSubjectPropagation = builder.getSubjectPropagation();
174180
_svcName = builder.getServiceName();
175181
_sslContext = builder.getSSLContext();
182+
_startTLS = builder.isStartTLS();
176183
}
177184

178185
/**
@@ -312,8 +319,9 @@ public void start() throws IOException {
312319
SSLEngineConfigurator clientSSLEngineConfigurator =
313320
new SSLEngineConfigurator(_sslContext, true, false, false);
314321

315-
filterChain.add(new SSLFilter(serverSSLEngineConfigurator,
316-
clientSSLEngineConfigurator));
322+
SSLFilter sslFilter = new SSLFilter(serverSSLEngineConfigurator,
323+
clientSSLEngineConfigurator);
324+
filterChain.add(_startTLS ? new StartTlsFilter(sslFilter, _isClient) : sslFilter);
317325
}
318326

319327
filterChain.add(rpcMessageReceiverFor(t));

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.dcache.oncrpc4j.rpc;
2121

22+
import com.google.common.annotations.Beta;
2223
import com.google.common.util.concurrent.MoreExecutors;
2324
import com.google.common.util.concurrent.ThreadFactoryBuilder;
2425
import org.dcache.oncrpc4j.rpc.gss.GssSessionManager;
@@ -78,12 +79,19 @@ public class OncRpcSvcBuilder {
7879
private int _workerThreadPoolSize = 0;
7980
private boolean _subjectPropagation = false;
8081
private SSLContext _sslContext = null;
82+
private boolean _startTLS = false;
8183

8284
public OncRpcSvcBuilder withAutoPublish() {
8385
_autoPublish = true;
8486
return this;
8587
}
8688

89+
@Beta
90+
public OncRpcSvcBuilder withStartTLS() {
91+
_startTLS = true;
92+
return this;
93+
}
94+
8795
public OncRpcSvcBuilder withoutAutoPublish() {
8896
_autoPublish = false;
8997
return this;
@@ -226,6 +234,11 @@ public boolean isAutoPublish() {
226234
return _autoPublish;
227235
}
228236

237+
@Beta
238+
public boolean isStartTLS() {
239+
return _startTLS;
240+
}
241+
229242
public IoStrategy getIoStrategy() {
230243
return _ioStrategy;
231244
}

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/RpcAuthType.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton,
2+
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
33
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
44
*
55
* This library is free software; you can redistribute it and/or modify
@@ -40,4 +40,8 @@ public interface RpcAuthType {
4040
*/
4141
static public final int RPCGSS_SEC = 6;
4242

43+
/**
44+
* Initiate a TLS handshake.
45+
*/
46+
static public final int TLS = 7;
4347
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2019 Deutsches Elektronen-Synchroton,
3+
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
4+
*
5+
* This library is free software; you can redistribute it and/or modify
6+
* it under the terms of the GNU Library General Public License as
7+
* published by the Free Software Foundation; either version 2 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Library General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Library General Public
16+
* License along with this program (see the file COPYING.LIB for more
17+
* details); if not, write to the Free Software Foundation, Inc.,
18+
* 675 Mass Ave, Cambridge, MA 02139, USA.
19+
*/
20+
package org.dcache.oncrpc4j.rpc;
21+
22+
import java.io.IOException;
23+
import javax.security.auth.Subject;
24+
import org.dcache.oncrpc4j.xdr.XdrAble;
25+
import org.dcache.oncrpc4j.xdr.XdrDecodingStream;
26+
import org.dcache.oncrpc4j.xdr.XdrEncodingStream;
27+
28+
import org.dcache.auth.Subjects;
29+
30+
/**
31+
*
32+
*/
33+
public class RpcAuthTypeTls implements RpcAuth, XdrAble {
34+
35+
private final RpcAuthVerifier verifier = new RpcAuthVerifier(RpcAuthType.NONE, new byte[0]);
36+
37+
@Override
38+
public int type() {
39+
return RpcAuthType.TLS;
40+
}
41+
42+
@Override
43+
public RpcAuthVerifier getVerifier() {
44+
return verifier;
45+
}
46+
47+
@Override
48+
public Subject getSubject() {
49+
return Subjects.NOBODY;
50+
}
51+
52+
@Override
53+
public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException {
54+
byte[] opaque = xdr.xdrDecodeDynamicOpaque();
55+
verifier.xdrDecode(xdr);
56+
}
57+
58+
@Override
59+
public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException {
60+
xdr.xdrEncodeInt(type());
61+
xdr.xdrEncodeInt(0); // spec: credential size must be zero
62+
verifier.xdrEncode(xdr);
63+
}
64+
65+
}

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/RpcCall.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton,
2+
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
33
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
44
*
55
* This library is free software; you can redistribute it and/or modify
@@ -205,7 +205,7 @@ public void accept() throws IOException, OncRpcException {
205205
_prog = _xdr.xdrDecodeInt();
206206
_version = _xdr.xdrDecodeInt();
207207
_proc = _xdr.xdrDecodeInt();
208-
_cred = RpcCredential.decode(_xdr);
208+
_cred = RpcCredential.decode(_xdr, _transport);
209209
}
210210

211211
/**

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/RpcCredential.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton,
2+
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
33
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
44
*
55
* This library is free software; you can redistribute it and/or modify
@@ -32,7 +32,7 @@ public class RpcCredential {
3232

3333
private RpcCredential() {}
3434

35-
public static RpcAuth decode(XdrDecodingStream xdr) throws OncRpcException, IOException {
35+
public static RpcAuth decode(XdrDecodingStream xdr, RpcTransport transport) throws OncRpcException, IOException {
3636

3737
int authType = xdr.xdrDecodeInt();
3838
RpcAuth credential;
@@ -46,6 +46,10 @@ public static RpcAuth decode(XdrDecodingStream xdr) throws OncRpcException, IOEx
4646
case RpcAuthType.RPCGSS_SEC:
4747
credential = new RpcAuthGss();
4848
break;
49+
case RpcAuthType.TLS:
50+
credential = new RpcAuthTypeTls();
51+
transport.startTLS();
52+
break;
4953
default:
5054
throw new RpcAuthException("Unsuported type: " + authType,
5155
new RpcAuthError(RpcAuthStat.AUTH_FAILED));

oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/RpcTransport.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton,
2+
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
33
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
44
*
55
* This library is free software; you can redistribute it and/or modify
@@ -19,9 +19,9 @@
1919
*/
2020
package org.dcache.oncrpc4j.rpc;
2121

22+
import com.google.common.annotations.Beta;
2223
import java.net.InetSocketAddress;
2324
import java.nio.channels.CompletionHandler;
24-
import org.dcache.oncrpc4j.rpc.ReplyQueue;
2525
import org.dcache.oncrpc4j.xdr.Xdr;
2626

2727
/**
@@ -75,4 +75,13 @@ public interface RpcTransport {
7575
* @return
7676
*/
7777
public RpcTransport getPeerTransport();
78+
79+
80+
/**
81+
* Enable TLS on this transport.
82+
* @throws RpcAuthException if handshake can't be initialized (due to missing configuration).
83+
* @throws IllegalStateException if TLS is already enabled.
84+
*/
85+
@Beta
86+
void startTLS() throws RpcAuthException, IllegalStateException;
7887
}

0 commit comments

Comments
 (0)