Skip to content

Commit 39b35f8

Browse files
committed
Make it possible to secure the Router
This requires clients to connect using basic authentication.
1 parent afa9187 commit 39b35f8

File tree

5 files changed

+72
-10
lines changed

5 files changed

+72
-10
lines changed

java/server/src/org/openqa/selenium/grid/commands/Hub.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.auto.service.AutoService;
2121
import com.google.common.collect.ImmutableSet;
2222
import org.openqa.selenium.BuildInfo;
23+
import org.openqa.selenium.UsernameAndPassword;
2324
import org.openqa.selenium.cli.CliCommand;
2425
import org.openqa.selenium.events.EventBus;
2526
import org.openqa.selenium.grid.TemplateGridServerCommand;
@@ -32,6 +33,7 @@
3233
import org.openqa.selenium.grid.log.LoggingOptions;
3334
import org.openqa.selenium.grid.router.ProxyCdpIntoGrid;
3435
import org.openqa.selenium.grid.router.Router;
36+
import org.openqa.selenium.grid.security.BasicAuthenticationFilter;
3537
import org.openqa.selenium.grid.security.Secret;
3638
import org.openqa.selenium.grid.security.SecretOptions;
3739
import org.openqa.selenium.grid.server.BaseServerOptions;
@@ -180,13 +182,20 @@ protected Handlers createHandlers(Config config) {
180182
Routable ui = new GridUiRoute();
181183
Routable routerWithSpecChecks = router.with(networkOptions.getSpecComplianceChecks());
182184

183-
HttpHandler httpHandler = combine(
185+
Routable httpHandler = combine(
184186
ui,
185187
routerWithSpecChecks,
186188
Route.prefix("/wd/hub").to(combine(routerWithSpecChecks)),
187189
Route.options("/graphql").to(() -> graphqlHandler),
188-
Route.post("/graphql").to(() -> graphqlHandler),
189-
Route.get("/readyz").to(() -> readinessCheck));
190+
Route.post("/graphql").to(() -> graphqlHandler));
191+
192+
UsernameAndPassword uap = secretOptions.getServerAuthentication();
193+
if (uap != null) {
194+
httpHandler = httpHandler.with(new BasicAuthenticationFilter(uap.username(), uap.password()));
195+
}
196+
197+
// Allow the liveness endpoint to be reached, since k8s doesn't make it easy to authenticate these checks
198+
httpHandler = combine(httpHandler, Route.get("/readyz").to(() -> readinessCheck));
190199

191200
return new Handlers(httpHandler, new ProxyCdpIntoGrid(clientFactory, sessions));
192201
}

java/server/src/org/openqa/selenium/grid/commands/Standalone.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.auto.service.AutoService;
2121
import com.google.common.collect.ImmutableSet;
2222
import org.openqa.selenium.BuildInfo;
23+
import org.openqa.selenium.UsernameAndPassword;
2324
import org.openqa.selenium.cli.CliCommand;
2425
import org.openqa.selenium.events.EventBus;
2526
import org.openqa.selenium.grid.TemplateGridServerCommand;
@@ -34,6 +35,7 @@
3435
import org.openqa.selenium.grid.node.ProxyNodeCdp;
3536
import org.openqa.selenium.grid.node.config.NodeOptions;
3637
import org.openqa.selenium.grid.router.Router;
38+
import org.openqa.selenium.grid.security.BasicAuthenticationFilter;
3739
import org.openqa.selenium.grid.security.Secret;
3840
import org.openqa.selenium.grid.security.SecretOptions;
3941
import org.openqa.selenium.grid.server.BaseServerOptions;
@@ -180,13 +182,20 @@ protected Handlers createHandlers(Config config) {
180182

181183
Routable ui = new GridUiRoute();
182184

183-
HttpHandler httpHandler = combine(
185+
Routable httpHandler = combine(
184186
ui,
185187
router,
186188
Route.prefix("/wd/hub").to(combine(router)),
187189
Route.options("/graphql").to(() -> graphqlHandler),
188-
Route.post("/graphql").to(() -> graphqlHandler),
189-
Route.get("/readyz").to(() -> readinessCheck));
190+
Route.post("/graphql").to(() -> graphqlHandler));
191+
192+
UsernameAndPassword uap = secretOptions.getServerAuthentication();
193+
if (uap != null) {
194+
httpHandler = httpHandler.with(new BasicAuthenticationFilter(uap.username(), uap.password()));
195+
}
196+
197+
// Allow the liveness endpoint to be reached, since k8s doesn't make it easy to authenticate these checks
198+
httpHandler = combine(httpHandler, Route.get("/readyz").to(() -> readinessCheck));
190199

191200
Node node = new NodeOptions(config).getNode();
192201
combinedHandler.addHandler(node);

java/server/src/org/openqa/selenium/grid/router/httpd/RouterFlags.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ public class RouterFlags implements HasRoles {
4242
@ConfigValue(section = "network", name = "relax-checks", example = "true")
4343
private Boolean relaxChecks = false;
4444

45+
@Parameter(
46+
names = "--username",
47+
description = "User name clients must use to connect to the server. " +
48+
"Both this and password need to be set in order to be used.")
49+
@ConfigValue(section = "router", name = "username", example = "admin")
50+
private String username;
51+
52+
@Parameter(
53+
names = "--password",
54+
description = "Password clients must use to connect to the server. " +
55+
"Both this and the username need to be set in order to be used.")
56+
@ConfigValue(section = "router", name = "password", example = "hunter2")
57+
private String password;
58+
4559
@Override
4660
public Set<Role> getRoles() {
4761
return Collections.singleton(ROUTER_ROLE);

java/server/src/org/openqa/selenium/grid/router/httpd/RouterServer.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
import com.google.auto.service.AutoService;
2121
import com.google.common.collect.ImmutableMap;
2222
import com.google.common.collect.ImmutableSet;
23-
2423
import org.openqa.selenium.BuildInfo;
24+
import org.openqa.selenium.UsernameAndPassword;
2525
import org.openqa.selenium.cli.CliCommand;
2626
import org.openqa.selenium.grid.TemplateGridServerCommand;
2727
import org.openqa.selenium.grid.config.Config;
@@ -34,6 +34,7 @@
3434
import org.openqa.selenium.grid.log.LoggingOptions;
3535
import org.openqa.selenium.grid.router.ProxyCdpIntoGrid;
3636
import org.openqa.selenium.grid.router.Router;
37+
import org.openqa.selenium.grid.security.BasicAuthenticationFilter;
3738
import org.openqa.selenium.grid.security.Secret;
3839
import org.openqa.selenium.grid.security.SecretOptions;
3940
import org.openqa.selenium.grid.server.BaseServerOptions;
@@ -47,6 +48,7 @@
4748
import org.openqa.selenium.grid.web.GridUiRoute;
4849
import org.openqa.selenium.internal.Require;
4950
import org.openqa.selenium.remote.http.HttpClient;
51+
import org.openqa.selenium.remote.http.HttpHandler;
5052
import org.openqa.selenium.remote.http.HttpResponse;
5153
import org.openqa.selenium.remote.http.Routable;
5254
import org.openqa.selenium.remote.http.Route;
@@ -148,15 +150,25 @@ protected Handlers createHandlers(Config config) {
148150
Routable routerWithSpecChecks = new Router(tracer, clientFactory, sessions, queue, distributor)
149151
.with(networkOptions.getSpecComplianceChecks());
150152

151-
Route handler = Route.combine(
153+
Routable route = Route.combine(
152154
ui,
153155
routerWithSpecChecks,
154156
Route.prefix("/wd/hub").to(combine(routerWithSpecChecks)),
155157
Route.options("/graphql").to(() -> graphqlHandler),
156-
Route.post("/graphql").to(() -> graphqlHandler),
158+
Route.post("/graphql").to(() -> graphqlHandler));
159+
160+
UsernameAndPassword uap = secretOptions.getServerAuthentication();
161+
if (uap != null) {
162+
LOG.info("Requiring authentication to connect");
163+
route = route.with(new BasicAuthenticationFilter(uap.username(), uap.password()));
164+
}
165+
166+
// Since k8s doesn't make it easy to do an authenticated liveness probe, allow unauthenticated access to it.
167+
Routable routeWithLiveness = Route.combine(
168+
route,
157169
get("/readyz").to(() -> req -> new HttpResponse().setStatus(HTTP_NO_CONTENT)));
158170

159-
return new Handlers(handler, new ProxyCdpIntoGrid(clientFactory, sessions));
171+
return new Handlers(routeWithLiveness, new ProxyCdpIntoGrid(clientFactory, sessions));
160172
}
161173

162174
@Override

java/server/src/org/openqa/selenium/grid/security/SecretOptions.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919

2020
import static java.util.Base64.getEncoder;
2121

22+
import org.openqa.selenium.Credentials;
23+
import org.openqa.selenium.UsernameAndPassword;
2224
import org.openqa.selenium.grid.config.Config;
2325
import org.openqa.selenium.grid.config.ConfigException;
2426

2527
import java.io.File;
2628
import java.io.IOException;
2729
import java.nio.file.Files;
2830
import java.util.Arrays;
31+
import java.util.Optional;
2932

3033
public class SecretOptions {
3134

35+
private static final String ROUTER_SECTION = "router";
3236
private static final String SERVER_SECTION = "server";
3337

3438
private final Config config;
@@ -54,6 +58,20 @@ public Secret getRegistrationSecret() {
5458
.map(Secret::new).orElse(new Secret(secret));
5559
}
5660

61+
public UsernameAndPassword getServerAuthentication() {
62+
Optional<String> username = config.get(ROUTER_SECTION, "username");
63+
Optional<String> password = config.get(ROUTER_SECTION, "password");
64+
65+
System.out.println(username);
66+
System.out.println(password);
67+
68+
if (!username.isPresent() || !password.isPresent()) {
69+
return null;
70+
}
71+
72+
return new UsernameAndPassword(username.get(), password.get());
73+
}
74+
5775
private boolean isSecure() {
5876
return config.get(SERVER_SECTION, "https-private-key").isPresent()
5977
&& config.get(SERVER_SECTION, "https-certificate").isPresent();

0 commit comments

Comments
 (0)