-
Notifications
You must be signed in to change notification settings - Fork 28.7k
[SPARK-10004] [shuffle] Perform auth checks when clients read shuffle data. #8218
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
17eb187
c68deab
292a299
fadff27
3cc9321
4d19ed5
8153497
d25c6cf
b491ac7
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 |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
import java.util.UUID; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.TimeUnit; | ||
import javax.annotation.Nullable; | ||
|
||
import com.google.common.base.Objects; | ||
import com.google.common.base.Preconditions; | ||
|
@@ -70,6 +71,7 @@ public class TransportClient implements Closeable { | |
|
||
private final Channel channel; | ||
private final TransportResponseHandler handler; | ||
@Nullable private String clientId; | ||
|
||
public TransportClient(Channel channel, TransportResponseHandler handler) { | ||
this.channel = Preconditions.checkNotNull(channel); | ||
|
@@ -84,6 +86,25 @@ public SocketAddress getSocketAddress() { | |
return channel.remoteAddress(); | ||
} | ||
|
||
/** | ||
* Returns the ID used by the client to authenticate itself when authentication is enabled. | ||
* | ||
* @return The client ID, or null if authentication is disabled. | ||
*/ | ||
public String getClientId() { | ||
return clientId; | ||
} | ||
|
||
/** | ||
* Sets the authenticated client ID. This is meant to be used by the authentication layer. | ||
* | ||
* Trying to set a different client ID after it's been set will result in an exception. | ||
*/ | ||
public void setClientId(String id) { | ||
Preconditions.checkState(clientId == null, "Client ID has already been set."); | ||
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. If I understand correctly, this will never get called when you aren't using authentication, right? Maybe drop in a comment here explaining that, and on |
||
this.clientId = id; | ||
} | ||
|
||
/** | ||
* Requests a single chunk from the remote side, from the pre-negotiated streamId. | ||
* | ||
|
@@ -207,6 +228,7 @@ public void close() { | |
public String toString() { | ||
return Objects.toStringHelper(this) | ||
.add("remoteAdress", channel.remoteAddress()) | ||
.add("clientId", clientId) | ||
.add("isActive", isActive()) | ||
.toString(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,13 +24,13 @@ | |
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
import com.google.common.base.Preconditions; | ||
import io.netty.channel.Channel; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import org.apache.spark.network.buffer.ManagedBuffer; | ||
|
||
import com.google.common.base.Preconditions; | ||
import org.apache.spark.network.client.TransportClient; | ||
|
||
/** | ||
* StreamManager which allows registration of an Iterator<ManagedBuffer>, which are individually | ||
|
@@ -44,6 +44,7 @@ public class OneForOneStreamManager extends StreamManager { | |
|
||
/** State of a single stream. */ | ||
private static class StreamState { | ||
final String appId; | ||
final Iterator<ManagedBuffer> buffers; | ||
|
||
// The channel associated to the stream | ||
|
@@ -53,7 +54,8 @@ private static class StreamState { | |
// that the caller only requests each chunk one at a time, in order. | ||
int curChunk = 0; | ||
|
||
StreamState(Iterator<ManagedBuffer> buffers) { | ||
StreamState(String appId, Iterator<ManagedBuffer> buffers) { | ||
this.appId = appId; | ||
this.buffers = Preconditions.checkNotNull(buffers); | ||
} | ||
} | ||
|
@@ -109,15 +111,34 @@ public void connectionTerminated(Channel channel) { | |
} | ||
} | ||
|
||
@Override | ||
public void checkAuthorization(TransportClient client, long streamId) { | ||
if (client.getClientId() != null) { | ||
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'm confused about this: since 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. That would be true, but I don't believe you can actually set things up like that. Authentication is either enabled on the server or its not, for all clients. 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 see, do you mean if the server enabled authentication, then any client that did not also enable it will fail the handshake in the first place? 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. Correct. |
||
StreamState state = streams.get(streamId); | ||
Preconditions.checkArgument(state != null, "Unknown stream ID."); | ||
if (!client.getClientId().equals(state.appId)) { | ||
throw new SecurityException(String.format( | ||
"Client %s not authorized to read stream %d (app %s).", | ||
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. Should we not disclose the actual appId in the exception message ? 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. App IDs are not secret. |
||
client.getClientId(), | ||
streamId, | ||
state.appId)); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Registers a stream of ManagedBuffers which are served as individual chunks one at a time to | ||
* callers. Each ManagedBuffer will be release()'d after it is transferred on the wire. If a | ||
* client connection is closed before the iterator is fully drained, then the remaining buffers | ||
* will all be release()'d. | ||
* | ||
* If an app ID is provided, only callers who've authenticated with the given app ID will be | ||
* allowed to fetch from this stream. | ||
*/ | ||
public long registerStream(Iterator<ManagedBuffer> buffers) { | ||
public long registerStream(String appId, Iterator<ManagedBuffer> buffers) { | ||
long myStreamId = nextStreamId.getAndIncrement(); | ||
streams.put(myStreamId, new StreamState(buffers)); | ||
streams.put(myStreamId, new StreamState(appId, buffers)); | ||
return myStreamId; | ||
} | ||
|
||
} |
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.
this can also be null if clientId hasn't been set yet right?
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.
Technically yes, but I'm pretty sure code cannot get a
TransportClient
handle before the auth bootstraps have run.