0
# Security Features
1
2
Selenium Grid provides authentication and authorization mechanisms to secure grid endpoints, manage access control, and protect sensitive operations through secret management and request filtering.
3
4
## Capabilities
5
6
### Secret Management
7
8
Core secret handling for authentication and authorization across grid components.
9
10
```java { .api }
11
/**
12
* Wrapper for sensitive values like passwords and API keys
13
*/
14
class Secret {
15
// Implementation intentionally opaque for security
16
17
/** Create a secret from a string value */
18
static Secret fromString(String value);
19
20
/** Create a secret from environment variable */
21
static Secret fromEnvironment(String envVar);
22
23
/** Create a secret from file contents */
24
static Secret fromFile(Path filePath) throws IOException;
25
26
/** Check if this secret matches another secret */
27
boolean matches(Secret other);
28
29
/** Check if this secret matches a string value */
30
boolean matches(String value);
31
32
// Note: No methods to retrieve the actual secret value
33
// This prevents accidental logging or exposure
34
}
35
```
36
37
**Usage Example:**
38
39
```java
40
// Create secrets from various sources
41
Secret registrationSecret = Secret.fromEnvironment("SE_REGISTRATION_SECRET");
42
Secret apiKey = Secret.fromFile(Paths.get("/etc/selenium/api-key"));
43
Secret password = Secret.fromString("my-secure-password");
44
45
// Use secrets for authentication
46
Node node = LocalNode.builder(tracer, eventBus, nodeUri, gridUri, registrationSecret)
47
.build();
48
49
// Secrets automatically used in HTTP headers for authentication
50
// Grid components validate secrets before processing requests
51
```
52
53
### Basic Authentication Filter
54
55
HTTP Basic Authentication support for protecting grid endpoints.
56
57
```java { .api }
58
/**
59
* HTTP Basic Authentication filter for grid endpoints
60
*/
61
class BasicAuthenticationFilter implements Filter {
62
/** Create filter with username and password secret */
63
BasicAuthenticationFilter(String username, Secret password);
64
65
/** Create filter from configuration */
66
static BasicAuthenticationFilter create(Config config);
67
68
@Override
69
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
70
throws IOException, ServletException;
71
}
72
```
73
74
**Configuration Example:**
75
76
```toml
77
[server]
78
enable-basic-auth = true
79
username = "admin"
80
password = "secret_password"
81
82
[server.auth]
83
realm = "Selenium Grid"
84
```
85
86
### Secret Validation Filter
87
88
Filter that validates request secrets for API access control.
89
90
```java { .api }
91
/**
92
* Filter that validates secret tokens in request headers
93
*/
94
class RequiresSecretFilter implements Filter {
95
/** Create filter with required secret */
96
RequiresSecretFilter(Secret requiredSecret);
97
98
/** Create filter from configuration */
99
static RequiresSecretFilter create(Config config);
100
101
@Override
102
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
103
throws IOException, ServletException;
104
105
/** Get the expected header name for the secret */
106
static String getSecretHeaderName();
107
}
108
```
109
110
**Usage Example:**
111
112
```java
113
// Configure secret validation
114
Secret apiSecret = Secret.fromEnvironment("SE_API_SECRET");
115
RequiresSecretFilter secretFilter = new RequiresSecretFilter(apiSecret);
116
117
// Apply to specific endpoints
118
server.addFilter(secretFilter, "/grid/api/*");
119
120
// Clients must include secret in headers
121
// X-Registration-Secret: <secret-value>
122
```
123
124
### Configuration Options
125
126
Security-specific configuration settings.
127
128
```java { .api }
129
/**
130
* Configuration options for security features
131
*/
132
class SecretOptions {
133
static final String SECRET_SECTION = "secret";
134
135
/** Get registration secret for node authentication */
136
Secret getRegistrationSecret(Config config);
137
138
/** Get API access secret */
139
Secret getApiSecret(Config config);
140
141
/** Get whether to require secrets for node registration */
142
boolean requireRegistrationSecret(Config config);
143
144
/** Get whether to require secrets for API access */
145
boolean requireApiSecret(Config config);
146
}
147
148
/**
149
* Security-related command-line flags
150
*/
151
class SecurityFlags {
152
@Parameter(names = {"--registration-secret"},
153
description = "Secret required for node registration")
154
String registrationSecret;
155
156
@Parameter(names = {"--api-secret"},
157
description = "Secret required for API access")
158
String apiSecret;
159
160
@Parameter(names = {"--require-registration-secret"},
161
description = "Require secret for node registration")
162
boolean requireRegistrationSecret = false;
163
164
@Parameter(names = {"--enable-basic-auth"},
165
description = "Enable HTTP Basic Authentication")
166
boolean enableBasicAuth = false;
167
168
@Parameter(names = {"--basic-auth-username"},
169
description = "Username for Basic Authentication")
170
String basicAuthUsername = "admin";
171
172
@Parameter(names = {"--basic-auth-password"},
173
description = "Password for Basic Authentication")
174
String basicAuthPassword;
175
}
176
```
177
178
## Authentication Patterns
179
180
### Node Registration Security
181
182
```java
183
// Secure node registration with shared secret
184
public class SecureNodeRegistration {
185
public static void registerNode() {
186
// Node includes registration secret
187
Secret registrationSecret = Secret.fromEnvironment("SE_REGISTRATION_SECRET");
188
189
Node node = LocalNode.builder(tracer, eventBus, nodeUri, gridUri, registrationSecret)
190
.add(chromeCapabilities, chromeFactory)
191
.build();
192
193
// Grid validates secret before accepting registration
194
// If secret doesn't match, registration is rejected
195
}
196
197
// Grid side validation
198
public boolean validateNodeRegistration(HttpRequest request, Secret expectedSecret) {
199
String secretHeader = request.getHeader("X-Registration-Secret");
200
if (secretHeader == null) {
201
return !requireRegistrationSecret; // Allow if not required
202
}
203
204
return expectedSecret.matches(secretHeader);
205
}
206
}
207
```
208
209
### API Access Control
210
211
```java
212
// Protect sensitive grid operations
213
@Path("/grid/admin")
214
public class AdminEndpoints {
215
216
@POST
217
@Path("/shutdown")
218
@RequiresSecret
219
public Response shutdownGrid() {
220
// Only accessible with valid API secret
221
gridManager.shutdown();
222
return Response.ok().build();
223
}
224
225
@DELETE
226
@Path("/sessions")
227
@RequiresSecret
228
public Response clearAllSessions() {
229
// Requires API secret to clear all sessions
230
sessionMap.clear();
231
return Response.ok().build();
232
}
233
}
234
235
// Custom annotation for secret requirement
236
@Target({METHOD, TYPE})
237
@Retention(RUNTIME)
238
public @interface RequiresSecret {
239
}
240
```
241
242
### TLS/SSL Configuration
243
244
```java
245
// Configure HTTPS for grid communications
246
public class TLSConfiguration {
247
public static Server createSecureServer(Config config) {
248
ServerOptions serverOptions = new ServerOptions(config);
249
250
if (serverOptions.isTlsEnabled()) {
251
return new NettyServer(
252
serverOptions,
253
HttpHandler.combine(
254
new TlsHandler(
255
serverOptions.getKeyStore(),
256
serverOptions.getKeyStorePassword(),
257
serverOptions.getTrustStore()
258
),
259
routeHandlers
260
)
261
);
262
}
263
264
return new NettyServer(serverOptions, routeHandlers);
265
}
266
}
267
```
268
269
**TLS Configuration Example:**
270
271
```toml
272
[server]
273
https = true
274
keystore = "/etc/selenium/keystore.jks"
275
keystore-password = "keystore_password"
276
truststore = "/etc/selenium/truststore.jks"
277
```
278
279
## Authorization Patterns
280
281
### Role-Based Access Control
282
283
```java
284
// Different access levels for different operations
285
public enum GridRole {
286
ADMIN("admin"), // Full access to all operations
287
OPERATOR("operator"), // Can manage sessions and nodes
288
VIEWER("viewer"); // Read-only access
289
290
private final String roleName;
291
292
GridRole(String roleName) {
293
this.roleName = roleName;
294
}
295
}
296
297
public class RoleBasedAuthFilter implements Filter {
298
private final Map<String, Set<GridRole>> endpointRoles;
299
300
@Override
301
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
302
String path = ((HttpServletRequest) request).getPathInfo();
303
Set<GridRole> requiredRoles = endpointRoles.get(path);
304
305
if (requiredRoles != null && !hasRequiredRole(request, requiredRoles)) {
306
((HttpServletResponse) response).setStatus(403);
307
return;
308
}
309
310
chain.doFilter(request, response);
311
}
312
313
private boolean hasRequiredRole(ServletRequest request, Set<GridRole> required) {
314
// Extract user roles from token/certificate/etc.
315
Set<GridRole> userRoles = extractUserRoles(request);
316
return userRoles.stream().anyMatch(required::contains);
317
}
318
}
319
```
320
321
### IP-Based Access Control
322
323
```java
324
// Restrict access by client IP address
325
public class IPWhitelistFilter implements Filter {
326
private final Set<String> allowedIPs;
327
private final Set<String> allowedSubnets;
328
329
public IPWhitelistFilter(Config config) {
330
this.allowedIPs = Set.of(config.get("security", "allowed-ips").orElse("").split(","));
331
this.allowedSubnets = Set.of(config.get("security", "allowed-subnets").orElse("").split(","));
332
}
333
334
@Override
335
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
336
String clientIP = request.getRemoteAddr();
337
338
if (!isAllowedIP(clientIP)) {
339
((HttpServletResponse) response).setStatus(403);
340
return;
341
}
342
343
chain.doFilter(request, response);
344
}
345
346
private boolean isAllowedIP(String clientIP) {
347
return allowedIPs.contains(clientIP) ||
348
allowedSubnets.stream().anyMatch(subnet -> isInSubnet(clientIP, subnet));
349
}
350
}
351
```
352
353
## Security Best Practices
354
355
### Secret Rotation
356
357
```java
358
// Support for rotating secrets without downtime
359
public class RotatingSecretManager {
360
private final List<Secret> validSecrets; // Current + previous secrets
361
private final ScheduledExecutorService scheduler;
362
363
public boolean validateSecret(String providedSecret) {
364
return validSecrets.stream()
365
.anyMatch(secret -> secret.matches(providedSecret));
366
}
367
368
public void rotateSecret(Secret newSecret) {
369
// Add new secret while keeping old one valid
370
validSecrets.add(0, newSecret); // New secret first
371
372
// Schedule removal of old secret after grace period
373
scheduler.schedule(() -> {
374
if (validSecrets.size() > 1) {
375
validSecrets.remove(validSecrets.size() - 1); // Remove oldest
376
}
377
}, 24, TimeUnit.HOURS);
378
}
379
}
380
```
381
382
### Audit Logging
383
384
```java
385
// Security event logging
386
public class SecurityAuditor {
387
private static final Logger auditLog = LoggerFactory.getLogger("SECURITY_AUDIT");
388
389
public void logAuthenticationAttempt(String clientIP, String username, boolean success) {
390
auditLog.info("AUTH_ATTEMPT: ip={}, user={}, success={}", clientIP, username, success);
391
}
392
393
public void logAPIAccess(String clientIP, String endpoint, String method, boolean authorized) {
394
auditLog.info("API_ACCESS: ip={}, endpoint={}, method={}, authorized={}",
395
clientIP, endpoint, method, authorized);
396
}
397
398
public void logNodeRegistration(String nodeIP, String nodeId, boolean accepted) {
399
auditLog.info("NODE_REGISTRATION: ip={}, nodeId={}, accepted={}", nodeIP, nodeId, accepted);
400
}
401
402
public void logSecurityViolation(String clientIP, String reason) {
403
auditLog.warn("SECURITY_VIOLATION: ip={}, reason={}", clientIP, reason);
404
}
405
}
406
```
407
408
### Secure Configuration
409
410
```toml
411
# Example secure configuration
412
[server]
413
https = true
414
keystore = "/etc/selenium/keystore.p12"
415
keystore-password = "${KEYSTORE_PASSWORD}"
416
enable-basic-auth = true
417
username = "admin"
418
password = "${ADMIN_PASSWORD}"
419
420
[security]
421
registration-secret = "${REGISTRATION_SECRET}"
422
api-secret = "${API_SECRET}"
423
require-registration-secret = true
424
allowed-ips = "127.0.0.1,10.0.0.0/8,192.168.0.0/16"
425
session-timeout = "300s"
426
max-login-attempts = 3
427
lockout-duration = "300s"
428
429
[audit]
430
enable-security-logging = true
431
log-file = "/var/log/selenium/security.log"
432
```
433
434
## Error Handling
435
436
```java
437
// Security-related error responses
438
public class SecurityErrorHandler {
439
440
public static Response handleAuthenticationFailure(String reason) {
441
// Don't reveal specific failure reasons
442
return Response.status(401)
443
.header("WWW-Authenticate", "Basic realm=\"Selenium Grid\"")
444
.entity(Map.of("error", "Authentication required"))
445
.build();
446
}
447
448
public static Response handleAuthorizationFailure() {
449
return Response.status(403)
450
.entity(Map.of("error", "Insufficient permissions"))
451
.build();
452
}
453
454
public static Response handleRateLimitExceeded() {
455
return Response.status(429)
456
.header("Retry-After", "300")
457
.entity(Map.of("error", "Rate limit exceeded"))
458
.build();
459
}
460
}