Skip to content

Commit efa7c73

Browse files
committed
Improve security documentation - Fix inverted LOG.warn in AuthorizedUserFilter that warned when the impersonation authorizer was configured instead of when it was missing
- Disable directory listing on UI static content servlets - Deprecate BlowfishTupleSerializer (Sweet32 vulnerable, 64-bit block) - Document: reverse proxy recommendation, CSRF limitations, missing security headers, doAsUser impersonation risk with partial config, BlobStore ACL validation, ZooKeeper SSL hostname verification, and serialization security guidance
1 parent 5dc833c commit efa7c73

File tree

4 files changed

+68
-6
lines changed

4 files changed

+68
-6
lines changed

docs/SECURITY.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,35 @@ storm.messaging.netty.tls.client.truststore.password: password
306306
| `storm.messaging.netty.tls.client.truststore.password`| Password for the Netty client truststore |
307307

308308

309+
## UI Security Hardening
310+
311+
### Reverse Proxy Recommendation
312+
313+
For production deployments, the Storm UI, Logviewer, and DRPC HTTP endpoints should be placed behind a reverse proxy such as Apache httpd, nginx, or a similar gateway. While Storm provides built-in SSL/TLS configuration, a reverse proxy offers significant additional security benefits:
314+
315+
- Proper TLS termination with up-to-date cipher suites and certificate management
316+
- CSRF token validation or same-origin enforcement on state-mutating requests
317+
- Security response headers (see below)
318+
- Rate limiting and request filtering
319+
- Centralized access logging and monitoring
320+
321+
This is especially important because the Storm UI exposes state-mutating operations (topology kill, activate, deactivate, rebalance) via its REST API.
322+
323+
### CSRF Protection
324+
325+
The Storm UI REST API does not currently include built-in Cross-Site Request Forgery (CSRF) protection. State-mutating endpoints can potentially be triggered by a malicious website if a user has an active authenticated session in the same browser. This is particularly relevant when using browser-based authentication mechanisms such as Kerberos (SPNEGO), as the browser automatically attaches credentials to cross-origin requests.
326+
327+
To mitigate this risk, configure your reverse proxy to enforce CSRF token validation or same-origin checks on POST requests.
328+
329+
### Security Response Headers
330+
331+
The Storm UI does not set security-related HTTP response headers by default. Operators deploying a secured cluster should configure the following headers via their reverse proxy or a custom servlet filter configured through `ui.filter`:
332+
333+
- `X-Frame-Options: DENY` or `SAMEORIGIN` to prevent clickjacking attacks
334+
- `X-Content-Type-Options: nosniff` to prevent MIME type sniffing
335+
- `Content-Security-Policy` to restrict resource loading
336+
- `Strict-Transport-Security` when using HTTPS, to enforce secure connections
337+
309338
## Authentication (Kerberos)
310339

311340
Storm offers pluggable authentication support through thrift and SASL. This
@@ -509,6 +538,14 @@ The Log servers have their own authorization configurations. These are set thro
509538
510539
When a topology is submitted, the submitting user can specify users in this list as well. The users and groups specified-in addition to the users in the cluster-wide setting-will be granted access to the submitted topology's worker logs in the logviewers.
511540
541+
### BlobStore ACL Validation
542+
543+
Storm uses a BlobStore to distribute topology code and configuration across the cluster. Access control lists on blobs are not enforced by default (`storm.blobstore.acl.validation.enabled: false`). In a secured multi-tenant cluster this means any authenticated user can read or modify any topology's blobs, regardless of ACL settings. To enforce BlobStore ACLs, set the following:
544+
545+
```yaml
546+
storm.blobstore.acl.validation.enabled: true
547+
```
548+
512549
### Supervisors headless User and group Setup
513550

514551
To ensure isolation of users in multi-tenancy, there is need to run supervisors and headless user and group unique to execution on the supervisor nodes. To enable this follow below steps.
@@ -566,6 +603,8 @@ to get a nimbus client as some other user and perform any nimbus action(i.e. kil
566603
Impersonation authorization is disabled by default which means any user can perform impersonation. To ensure only authorized users can perform impersonation you should start nimbus with `nimbus.impersonation.authorizer` set to `org.apache.storm.security.auth.authorizer.ImpersonationAuthorizer`.
567604
The `ImpersonationAuthorizer` uses `nimbus.impersonation.acl` as the acl to authorize users. Following is a sample nimbus config for supporting impersonation:
568605
606+
**Important:** If you have enabled authentication (e.g. Kerberos) and authorization (e.g. `SimpleACLAuthorizer`) but have *not* configured `nimbus.impersonation.authorizer`, any authenticated user can impersonate any other user by setting the `doAsUser` HTTP header or query parameter. The `DefaultHttpCredentialsPlugin` always processes `doAsUser` requests, and without an impersonation authorizer these requests are silently allowed. When deploying a secured cluster you should always configure `nimbus.impersonation.authorizer` alongside `nimbus.authorizer`.
607+
569608
```yaml
570609
nimbus.impersonation.authorizer: org.apache.storm.security.auth.authorizer.ImpersonationAuthorizer
571610
nimbus.impersonation.acl:
@@ -618,6 +657,14 @@ By default storm allows any sized topology to be submitted. But ZK and others ha
618657
| nimbus.slots.perTopology | The maximum number of slots/workers a topology can use. |
619658
| nimbus.executors.perTopology | The maximum number of executors/threads a topology can use. |
620659

660+
### Serialization Security
661+
662+
Storm uses Kryo for serializing tuple data between spouts and bolts. By default, Kryo requires all classes to be explicitly registered (`topology.fall.back.on.java.serialization: false`). When this setting is changed to `true`, any unregistered class will be serialized using Java's native `ObjectInputStream`/`ObjectOutputStream`, which is known to be vulnerable to deserialization attacks if untrusted data reaches the serialization path.
663+
664+
**Do not set `topology.fall.back.on.java.serialization` to `true` in production.** While topology submitters already run arbitrary code via their spouts and bolts, enabling the Java serialization fallback broadens the attack surface and may allow malicious data from external sources (e.g. message queues) to trigger unintended code execution during deserialization.
665+
666+
For tuple encryption, use TLS-based transport encryption (`storm.messaging.netty.tls.enable`) instead of the deprecated `BlowfishTupleSerializer`, which uses a 64-bit block cipher vulnerable to birthday attacks.
667+
621668
### Log Cleanup
622669
The Logviewer daemon now is also responsible for cleaning up old log files for dead topologies.
623670

@@ -693,4 +740,13 @@ Also, there are several configurations for topology Zookeeper authentication:
693740

694741
Note: If storm.zookeeper.topology.auth.payload isn't set, Storm will generate a ZooKeeper secret payload for MD5-digest with generateZookeeperDigestSecretPayload() method.
695742

743+
### ZooKeeper SSL Hostname Verification
744+
745+
When enabling ZooKeeper SSL via `storm.zookeeper.ssl.enable`, be aware that hostname verification is disabled by default (`storm.zookeeper.ssl.hostnameVerification: false`). Without hostname verification, a man-in-the-middle attacker with any valid certificate signed by a trusted CA could intercept communication between Storm and ZooKeeper. For production deployments you should enable this:
746+
747+
```yaml
748+
storm.zookeeper.ssl.enable: true
749+
storm.zookeeper.ssl.hostnameVerification: true
750+
```
751+
696752

storm-client/src/jvm/org/apache/storm/security/serialization/BlowfishTupleSerializer.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030

3131
/**
3232
* Apply Blowfish encryption for tuple communication to bolts.
33+
*
34+
* @deprecated since 2.8.6. Blowfish uses a 64-bit block size which is vulnerable to birthday attacks (Sweet32).
35+
* Use TLS-based transport encryption instead (see storm.messaging.netty.tls.enable).
3336
*/
37+
@Deprecated(since = "2.8.6", forRemoval = true)
3438
public class BlowfishTupleSerializer extends Serializer<ListDelegate> {
3539
/**
3640
* The secret key (if any) for data encryption by blowfish payload serialization factory (BlowfishSerializationFactory). You should use
@@ -48,7 +52,9 @@ public BlowfishTupleSerializer(Kryo unused, Map<String, Object> topoConf) {
4852
String encryptionkey;
4953
try {
5054
encryptionkey = (String) topoConf.get(SECRET_KEY);
51-
LOG.debug("Blowfish serializer being constructed ...");
55+
LOG.warn("BlowfishTupleSerializer is deprecated and will be removed in a future release. "
56+
+ "Blowfish uses a 64-bit block size which is vulnerable to birthday attacks (Sweet32). "
57+
+ "Use TLS-based transport encryption instead (storm.messaging.netty.tls.enable).");
5258

5359
byte[] bytes;
5460
if (encryptionkey != null) {

storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIServer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,15 @@ protected void configure() {
164164
}
165165
}
166166

167-
holderHome.setInitParameter("dirAllowed", "true");
167+
holderHome.setInitParameter("dirAllowed", "false");
168168
holderHome.setInitParameter("pathInfoOnly", "true");
169169
context.addFilter(new FilterHolder(new HeaderResponseServletFilter(metricsRegistry)), "/*", EnumSet.allOf(DispatcherType.class));
170170
context.addServlet(holderHome, "/*");
171171

172172

173173
// Lastly, the default servlet for root content (always needed, to satisfy servlet spec)
174174
ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
175-
holderPwd.setInitParameter("dirAllowed", "true");
175+
holderPwd.setInitParameter("dirAllowed", "false");
176176
context.addServlet(holderPwd, "/");
177177

178178
metricsRegistry.startMetricsReporters(conf);

storm-webapp/src/main/java/org/apache/storm/daemon/ui/filters/AuthorizedUserFilter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ public void filter(ContainerRequestContext containerRequestContext) {
154154
);
155155
return;
156156
}
157-
158-
LOG.warn(" principal {} is trying to impersonate {} but {} has no authorizer configured. "
157+
} else {
158+
LOG.warn("Principal {} is trying to impersonate {} but {} is not configured. "
159159
+ "This is a potential security hole. Please see SECURITY.MD to learn how to "
160160
+ "configure an impersonation authorizer.",
161161
reqContext.realPrincipal().toString(), reqContext.principal().toString(),
162-
conf.get(DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER));
162+
DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER);
163163
}
164164
}
165165

0 commit comments

Comments
 (0)