Skip to content

Commit

Permalink
[grid] Securing 0mq when user provides a certificate
Browse files Browse the repository at this point in the history
Fixes #7856

The initial idea was to use `Curve` security and reuse
the certificate(s) provided by the user when they want
HTTPS in the Grid. However, the docs for 0mq and Curve
with XPUB/XSUB and security are almost non-existent.
Therefore, the approach is to create a small but hard
to guess registration secret based on the provided
certificate, given that the user provides did not
provide a registration secret.
  • Loading branch information
diemol committed Oct 28, 2020
1 parent 3bd0bc0 commit 0c6c62b
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.openqa.selenium.events.EventName;
import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.JsonInput;
import org.zeromq.ZContext;
Expand Down Expand Up @@ -59,9 +60,9 @@ public static EventBus create(Config config) {

boolean bind = config.getBool(EVENTS_SECTION, "bind").orElse(false);

Secret secret = config.get("server", "registration-secret").map(Secret::new).orElse(new Secret(""));
SecretOptions secretOptions = new SecretOptions(config);

return create(new ZContext(), publish, subscribe, bind, secret);
return create(new ZContext(), publish, subscribe, bind, secretOptions.getRegistrationSecret());
}

public static EventListener<RejectedEvent> onRejectedEvent(Consumer<RejectedEvent> handler) {
Expand Down
4 changes: 3 additions & 1 deletion java/server/src/org/openqa/selenium/grid/commands/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.openqa.selenium.grid.log.LoggingOptions;
import org.openqa.selenium.grid.router.ProxyCdpIntoGrid;
import org.openqa.selenium.grid.router.Router;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.grid.server.EventBusOptions;
import org.openqa.selenium.grid.server.NetworkOptions;
Expand Down Expand Up @@ -116,6 +117,7 @@ protected Handlers createHandlers(Config config) {
handler.addHandler(sessions);

BaseServerOptions serverOptions = new BaseServerOptions(config);
SecretOptions secretOptions = new SecretOptions(config);

URL externalUrl;
try {
Expand All @@ -135,7 +137,7 @@ protected Handlers createHandlers(Config config) {
bus,
clientFactory,
sessions,
serverOptions.getRegistrationSecret());
secretOptions.getRegistrationSecret());
handler.addHandler(distributor);

Router router = new Router(tracer, clientFactory, sessions, distributor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.openqa.selenium.grid.node.local.LocalNodeFactory;
import org.openqa.selenium.grid.router.Router;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.grid.server.EventBusOptions;
import org.openqa.selenium.grid.server.NetworkOptions;
Expand Down Expand Up @@ -115,7 +116,8 @@ protected Handlers createHandlers(Config config) {
EventBus bus = events.getEventBus();

BaseServerOptions serverOptions = new BaseServerOptions(config);
Secret registrationSecret = serverOptions.getRegistrationSecret();
SecretOptions secretOptions = new SecretOptions(config);
Secret registrationSecret = secretOptions.getRegistrationSecret();

URI localhost = serverOptions.getExternalUri();
URL localhostUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import org.openqa.selenium.grid.node.Node;
import org.openqa.selenium.grid.node.remote.RemoteNode;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.EventBusOptions;
import org.openqa.selenium.grid.server.NetworkOptions;
import org.openqa.selenium.grid.sessionmap.SessionMap;
Expand Down Expand Up @@ -113,9 +113,9 @@ public static Distributor create(Config config) {
EventBus bus = new EventBusOptions(config).getEventBus();
HttpClient.Factory clientFactory = new NetworkOptions(config).getHttpClientFactory(tracer);
SessionMap sessions = new SessionMapOptions(config).getSessionMap();
BaseServerOptions serverOptions = new BaseServerOptions(config);
SecretOptions secretOptions = new SecretOptions(config);

return new LocalDistributor(tracer, bus, clientFactory, sessions, serverOptions.getRegistrationSecret());
return new LocalDistributor(tracer, bus, clientFactory, sessions, secretOptions.getRegistrationSecret());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.openqa.selenium.grid.node.Node;
import org.openqa.selenium.grid.node.config.NodeOptions;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.grid.server.EventBusOptions;
import org.openqa.selenium.internal.Require;
Expand Down Expand Up @@ -117,6 +118,7 @@ public static Node create(Config config) {
LoggingOptions loggingOptions = new LoggingOptions(config);
EventBusOptions eventOptions = new EventBusOptions(config);
BaseServerOptions serverOptions = new BaseServerOptions(config);
SecretOptions secretOptions = new SecretOptions(config);
NodeOptions nodeOptions = new NodeOptions(config);

Map<String, Object> raw = new Json().toType(
Expand All @@ -142,7 +144,7 @@ public static Node create(Config config) {
return new OneShotNode(
loggingOptions.getTracer(),
eventOptions.getEventBus(),
serverOptions.getRegistrationSecret(),
secretOptions.getRegistrationSecret(),
new NodeId(UUID.randomUUID()),
serverOptions.getExternalUri(),
nodeOptions.getPublicGridUri().orElseThrow(() -> new ConfigException("Unable to determine public grid address")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.openqa.selenium.grid.node.SessionFactory;
import org.openqa.selenium.grid.node.config.DriverServiceSessionFactory;
import org.openqa.selenium.grid.node.config.NodeOptions;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.grid.server.EventBusOptions;
import org.openqa.selenium.grid.server.NetworkOptions;
Expand All @@ -47,6 +48,7 @@ public static Node create(Config config) {
BaseServerOptions serverOptions = new BaseServerOptions(config);
NodeOptions nodeOptions = new NodeOptions(config);
NetworkOptions networkOptions = new NetworkOptions(config);
SecretOptions secretOptions = new SecretOptions(config);

Tracer tracer = loggingOptions.getTracer();
HttpClient.Factory clientFactory = networkOptions.getHttpClientFactory(tracer);
Expand All @@ -56,7 +58,7 @@ public static Node create(Config config) {
eventOptions.getEventBus(),
serverOptions.getExternalUri(),
nodeOptions.getPublicGridUri().orElseGet(serverOptions::getExternalUri),
serverOptions.getRegistrationSecret());
secretOptions.getRegistrationSecret());

List<DriverService.Builder<?, ?>> builders = new ArrayList<>();
ServiceLoader.load(DriverService.Builder.class).forEach(builders::add);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ java_library(
"//java/server/src/org/openqa/selenium/grid/graphql",
"//java/server/src/org/openqa/selenium/grid/log",
"//java/server/src/org/openqa/selenium/grid/router",
"//java/server/src/org/openqa/selenium/grid/security",
"//java/server/src/org/openqa/selenium/grid/server",
"//java/server/src/org/openqa/selenium/grid/sessionmap",
"//java/server/src/org/openqa/selenium/grid/sessionmap/config",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.openqa.selenium.grid.log.LoggingOptions;
import org.openqa.selenium.grid.router.ProxyCdpIntoGrid;
import org.openqa.selenium.grid.router.Router;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.grid.server.NetworkOptions;
import org.openqa.selenium.grid.server.Server;
Expand Down Expand Up @@ -104,14 +105,15 @@ protected Handlers createHandlers(Config config) {
SessionMap sessions = sessionsOptions.getSessionMap();

BaseServerOptions serverOptions = new BaseServerOptions(config);
SecretOptions secretOptions = new SecretOptions(config);

DistributorOptions distributorOptions = new DistributorOptions(config);
URL distributorUrl = fromUri(distributorOptions.getDistributorUri());
Distributor distributor = new RemoteDistributor(
tracer,
clientFactory,
distributorUrl,
serverOptions.getRegistrationSecret());
secretOptions.getRegistrationSecret());

GraphqlHandler graphqlHandler = new GraphqlHandler(distributor, serverOptions.getExternalUri());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ java_library(
deps = [
"//java/client/src/org/openqa/selenium:core",
"//java/client/src/org/openqa/selenium/json",
"//java/server/src/org/openqa/selenium/grid/config",
"//java/client/src/org/openqa/selenium/remote/http",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.security;

import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.config.ConfigException;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.logging.Logger;

import static java.util.Base64.getEncoder;

public class SecretOptions {

private static final String SERVER_SECTION = "server";

private static final Logger LOG = Logger.getLogger(SecretOptions.class.getName());
private final Config config;

public SecretOptions(Config config) {
this.config = config;
}

public Secret getRegistrationSecret() {
String secret = "";
if ((isSecure() || isSelfSigned()) && config.get(SERVER_SECTION, "registration-secret").isEmpty()) {
try {
secret = getEncoder().encodeToString(Arrays.copyOfRange(Files.readAllBytes(getCertificate().toPath()), 0, 32));
return new Secret(secret);
} catch (IOException e) {
throw new ConfigException("Cannot read the certificate file: " + e.getMessage());
}
}
return config.get(SERVER_SECTION, "registration-secret").map(Secret::new).orElse(new Secret(secret));
}

private boolean isSecure() {
return config.get(SERVER_SECTION, "https-private-key").isPresent() && config.get(SERVER_SECTION, "https-certificate").isPresent();
}

private boolean isSelfSigned() {
return config.getBool(SERVER_SECTION, "https-self-signed").orElse(false);
}

private File getCertificate() {
String certificatePath = config.get(SERVER_SECTION, "https-certificate").orElse(null);
if (certificatePath != null) {
return new File(certificatePath);
}
throw new ConfigException("you must provide a certificate via --https-certificate when using --https");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ java_library(
"//java/server/src/org/openqa/selenium/events",
"//java/server/src/org/openqa/selenium/grid/component",
"//java/server/src/org/openqa/selenium/grid/config",
"//java/server/src/org/openqa/selenium/grid/security",
"//java/server/src/org/openqa/selenium/grid/web",
artifact("com.beust:jcommander"),
artifact("com.google.guava:guava"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.config.ConfigException;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.net.HostIdentifier;
import org.openqa.selenium.net.NetworkUtils;
import org.openqa.selenium.net.PortProber;
Expand Down Expand Up @@ -119,10 +118,6 @@ public File getCertificate() {
throw new ConfigException("you must provide a certificate via --https-certificate when using --https");
}

public Secret getRegistrationSecret() {
return config.get(SERVER_SECTION, "registration-secret").map(Secret::new).orElse(new Secret(""));
}

public boolean isSelfSigned() {
return config.getBool(SERVER_SECTION, "https-self-signed").orElse(false);
}
Expand Down

0 comments on commit 0c6c62b

Please sign in to comment.