Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions conf/shiro.ini.template
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ user3 = password4, role2
# authc = org.apache.zeppelin.realm.kerberos.KerberosAuthenticationFilter

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
### Match URL rules case-insensitively so case variants cannot bypass a protected chain.
filterChainResolver.caseInsensitive = true

### If caching of user is required then uncomment below lines
#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
Expand All @@ -90,11 +92,13 @@ cookie.httpOnly = true
### Restrict the session cookie to same-site requests by default. Set to NONE only when
### Zeppelin is intentionally embedded into a different origin (and 'cookie.secure = true').
cookie.sameSite = LAX
### Uncomment the below line only when Zeppelin is running over HTTPS
#cookie.secure = true
### Shiro emits Secure only for requests that are HTTPS (including correctly forwarded HTTPS).
cookie.secure = true
sessionManager.sessionIdCookie = $cookie

securityManager.sessionManager = $sessionManager
### Zeppelin does not use remember-me authentication. Disable it explicitly until Shiro 3.
securityManager.rememberMeManager = null
# 86,400,000 milliseconds = 24 hour
securityManager.sessionManager.globalSessionTimeout = 86400000
shiro.loginUrl = /api/login
Expand All @@ -115,12 +119,15 @@ admin = *
#
# IMPORTANT: Order matters: URL path expressions are evaluated against an incoming request
# in the order they are defined and the FIRST MATCH WINS.
# The same Shiro filter handles REST requests and the notebook WebSocket handshake. Keep `/ws`
# authenticated unless anonymous notebook access is intentional; use `/ws = anon` to opt out.
#
# To allow anonymous access to all but the stated urls,
# uncomment the line second last line (/** = anon) and comment the last line (/** = authc)
#
/api/version = anon
/api/cluster/address = anon
/ws = authc
# Allow all authenticated users to restart interpreters on a notebook page.
# Comment out the following line if you would like to authorize only admin users to restart interpreters.
/api/interpreter/setting/restart/** = authc
Expand Down
10 changes: 8 additions & 2 deletions conf/zeppelin-site.xml.template
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@

<property>
<name>zeppelin.server.allowed.origins</name>
<value>*</value>
<description>Allowed sources for REST and WebSocket requests (i.e. http://onehost:8080,http://otherhost.com). If you leave * you are vulnerable to https://issues.apache.org/jira/browse/ZEPPELIN-173</description>
<value></value>
<description>Exact allowed origins for credentialed REST and WebSocket requests (i.e. http://onehost:8080,https://otherhost.com). Empty allows only the configured local server origin. Use * only for an intentionally public deployment.</description>
</property>

<property>
Expand All @@ -559,6 +559,12 @@
<description>Size in characters of the maximum text message to be received by websocket. Defaults to 10240000</description>
</property>

<property>
<name>zeppelin.websocket.authorization.roles.refresh.interval.ms</name>
<value>1000</value>
<description>Maximum age in milliseconds of a role snapshot reused for WebSocket broadcasts. The Shiro session is still validated before every delivery. Use 0 to refresh roles for every broadcast.</description>
</property>

<property>
<name>zeppelin.server.default.dir.allowed</name>
<value>false</value>
Expand Down
10 changes: 8 additions & 2 deletions docs/setup/operation/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ Sources descending by priority:
<tr>
<td><h6 class="properties">ZEPPELIN_ALLOWED_ORIGINS</h6></td>
<td><h6 class="properties">zeppelin.server.allowed.origins</h6></td>
<td>*</td>
<td>Enables a way to specify a ',' separated list of allowed origins for REST and websockets. <br /> e.g. http://localhost:8080</td>
<td>(empty)</td>
<td>Comma-separated exact origins allowed for credentialed REST and WebSocket requests. Empty permits only the configured local server origin. Include scheme and port, e.g. http://localhost:8080.</td>
</tr>
<tr>
<td><h6 class="properties">ZEPPELIN_CREDENTIALS_PERSIST</h6></td>
Expand Down Expand Up @@ -406,6 +406,12 @@ Sources descending by priority:
<td>1024000</td>
<td>Size(in characters) of the maximum text message that can be received by websocket.</td>
</tr>
<tr>
<td><h6 class="properties">ZEPPELIN_WEBSOCKET_AUTHORIZATION_ROLES_REFRESH_INTERVAL_MS</h6></td>
<td><h6 class="properties">zeppelin.websocket.authorization.roles.refresh.interval.ms</h6></td>
<td>1000</td>
<td>Maximum age in milliseconds of a role snapshot reused for WebSocket broadcasts. Session logout and expiry are still validated before every delivery. Use 0 to refresh roles for every broadcast.</td>
</tr>
<tr>
<td><h6 class="properties">ZEPPELIN_SERVER_DEFAULT_DIR_ALLOWED</h6></td>
<td><h6 class="properties">zeppelin.server.default.dir.allowed</h6></td>
Expand Down
40 changes: 40 additions & 0 deletions docs/setup/operation/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,46 @@ So, copying `notebook` and `conf` directory should be enough.

## Migration Guide

### WebSocket authentication migration

This release updates Apache Shiro 1.13 to Shiro 2.2.1's Jakarta artifacts while retaining
Zeppelin's Java 11 baseline. This is the smallest supported bridge for the unified authentication
change. Shiro 3 requires Java 17 and should be handled as a separate runtime upgrade rather than
bundling a JDK transition into this security change.

Shiro 2.x is now end-of-life, so this bridge is transitional. Zeppelin does not include the
`shiro-guice` module affected by CVE-2026-56091, and the shipped configuration disables the
RememberMe feature affected by CVE-2026-56130. Operators with custom Shiro configuration should
also keep RememberMe disabled. A Java 17 and Shiro 3 migration remains the long-term follow-up.

When Shiro is enabled, notebook WebSockets now authenticate during the HTTP upgrade with the same
Shiro session cookie as REST. Custom `shiro.ini` files should add an explicit `/ws = authc` rule
before a broader anonymous rule. The shipped template already contains this rule.

Existing `shiro.ini` files are preserved during upgrades and do not inherit new template defaults.
Under `[main]`, also set `filterChainResolver.caseInsensitive = true`, configure the injected
`JSESSIONID` cookie with `httpOnly = true`, `sameSite = LAX`, and `secure = true`, and set
`securityManager.rememberMeManager = null`. Shiro emits `Secure` only for requests Jetty recognizes
as HTTPS; TLS-terminating proxies must forward the original scheme. Zeppelin does not use
remember-me authentication, so disabling that unused facility reduces the exposed authentication
surface until the separate JDK 17 / Shiro 3 upgrade.

Browser and Java clients must retain the REST login cookie and send it when opening `/ws`.
WebSocket message fields such as `principal`, `roles`, and `ticket` are no longer authentication
credentials. Proxies must continue forwarding `Cookie`, `Origin`, `Upgrade`, and `Connection`
headers to the `/ws` endpoint.

The default value of `zeppelin.server.allowed.origins` is now empty instead of `*`. An empty value
permits only the configured local server origin, so deployments accessed through another hostname,
port, or reverse proxy must list each trusted browser origin explicitly before upgrading.

`ZeppelinClient` now owns an isolated REST session and implements `AutoCloseable`; applications
should close each client after use. `ZSession` reuses that session cookie for `/ws`. HTTPS and WSS
both keep the JVM's normal certificate and hostname verification, including for Knox deployments;
configure the JVM trust store when an internal certificate authority is required.
Process-global `Unirest.config()` and `Unirest.shutDown()` no longer configure or close these
isolated clients; use JVM networking properties and close each `ZeppelinClient` directly.

### Upgrading from Zeppelin 0.9, 0.10 to 0.11
- From 0.11, The type of `Pegdown` for parsing markdown was deprecated ([ZEPPELIN-5529](https://issues.apache.org/jira/browse/ZEPPELIN-2619)). It will use `Flexmark` instead.

Expand Down
55 changes: 49 additions & 6 deletions docs/setup/security/shiro_authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ limitations under the License.
When you connect to Apache Zeppelin, you will be asked to enter your credentials. Once you logged in, then you have access to all notes including other user's notes.

## Important Note
By default, Zeppelin allows anonymous access. It is strongly recommended that you consider setting up Apache Shiro for authentication (as described in this document, see 2 Secure the Websocket channel), or only deploy and use Zeppelin in a secured and trusted environment.
By default, Zeppelin allows anonymous access. It is strongly recommended that you configure
Apache Shiro for both REST and WebSocket authentication as described below, or only deploy and
use Zeppelin in a secured and trusted environment.

## Security Setup
You can setup **Zeppelin notebook authentication** in some simple steps.
Expand Down Expand Up @@ -67,6 +69,42 @@ user3 = password4, role2
```
You can set the roles for each users next to the password.

### REST and WebSocket authentication

REST requests under `/api/*` and the notebook WebSocket handshake at `/ws` pass through the same
Shiro filter and use the same Shiro session cookie. The browser sends the `JSESSIONID` cookie during
the WebSocket HTTP upgrade; identity fields in WebSocket messages are not authentication
credentials. A logout closes WebSockets for that exact session immediately; an expired session is
closed when the next WebSocket frame is validated.

The `[urls]` section is the include/exclude policy for both transports. Rules use first-match-wins
ordering. For example, the following keeps the version endpoint public while requiring one
authenticated session for all notebook WebSocket connections and remaining REST endpoints:

```
[urls]
/api/version = anon
/ws = authc
/** = authc
```

Use `anon` only for paths that are deliberately excluded from authentication. To allow an
anonymous notebook WebSocket explicitly, configure `/ws = anon`. Shiro sees `/ws` as one upgrade
URL, so operation-level notebook permissions are still enforced by Zeppelin's notebook ACL and
service authorization rather than separate Shiro URL patterns.

Origin validation is performed before the WebSocket upgrade. Configure
`zeppelin.server.allowed.origins` with the exact trusted browser origins; avoid `*` in secured
deployments.

For streamed output, Zeppelin revalidates the Shiro session before every WebSocket delivery while
reusing a recent role snapshot to avoid querying remote realms for every output chunk. The default
`zeppelin.websocket.authorization.roles.refresh.interval.ms` value is `1000`, so Zeppelin's own
snapshot adds at most one second to directory role-revocation handling for passive WebSocket
broadcasts. Realm-specific authorization caches can add their own delay. Set the value to `0` to
refresh roles for every broadcast. This setting does not delay logout or session-expiry detection,
and direct notebook ACL changes are checked on every broadcast.

## Groups and permissions (optional)
In case you want to leverage user groups and permissions, use one of the following configuration for LDAP or AD under `[main]` segment in `shiro.ini`.

Expand Down Expand Up @@ -293,18 +331,22 @@ chown hdfs:hadoop /etc/security/http_secret
chmod 440 /etc/security/http_secret
```

## Secure Cookie for Zeppelin Sessions (optional)
Zeppelin can be configured to set `HttpOnly` flag in the session cookie. With this configuration, Zeppelin cookies can
not be accessed via client side scripts thus preventing majority of Cross-site scripting (XSS) attacks.
## Secure Cookie for Zeppelin Sessions
Zeppelin configures `HttpOnly`, `SameSite=Lax`, and HTTPS-aware `Secure` handling for the Shiro
session cookie by default. `HttpOnly` prevents client-side scripts from reading the cookie. Shiro
emits the `Secure` attribute only when the servlet request is secure, so local HTTP development
continues to work while HTTPS deployments keep the cookie off clear-text requests. When TLS is
terminated at a reverse proxy, configure forwarded request handling so Jetty sees the original
HTTPS scheme.

To enable secure cookie support via Shiro, add the following lines in `conf/shiro.ini` under `[main]` section, after
defining a `sessionManager`.
The default `conf/shiro.ini.template` contains the following settings under `[main]`:

```
cookie = org.apache.shiro.web.servlet.SimpleCookie
cookie.name = JSESSIONID
cookie.secure = true
cookie.httpOnly = true
cookie.sameSite = LAX
sessionManager.sessionIdCookie = $cookie
```

Expand All @@ -316,6 +358,7 @@ Since Shiro provides **url-based security**, you can hide the information by com
```
[urls]

/ws = authc
/api/interpreter/** = authc, roles[admin]
/api/configurations/** = authc, roles[admin]
/api/credential/** = authc, roles[admin]
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/rest_api/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
"zeppelin.notebook.homescreen": "",
"zeppelin.notebook.storage": "org.apache.zeppelin.notebook.repo.VFSNotebookRepo",
"zeppelin.interpreter.connect.timeout": "30000",
"zeppelin.server.allowed.origins":"*",
"zeppelin.server.allowed.origins":"",
"zeppelin.encoding": "UTF-8"
}
}
Expand Down
55 changes: 30 additions & 25 deletions docs/usage/zeppelin_sdk/client_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,34 +36,39 @@ The entry point of zeppelin client api is class `ZeppelinClient`. All the operat

{% highlight java %}
ClientConfig clientConfig = new ClientConfig("http://localhost:8080");
ZeppelinClient zClient = new ZeppelinClient(clientConfig);

String zeppelinVersion = zClient.getVersion();
System.out.println("Zeppelin version: " + zeppelinVersion);

// execute note 2A94M5J1Z paragraph by paragraph
try {
ParagraphResult paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150210-015259_1403135953");
System.out.println("Execute the 1st spark tutorial paragraph, paragraph result: " + paragraphResult);

paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150210-015302_1492795503");
System.out.println("Execute the 2nd spark tutorial paragraph, paragraph result: " + paragraphResult);

Map<String, String> parameters = new HashMap<>();
parameters.put("maxAge", "40");
paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150212-145404_867439529", parameters);
System.out.println("Execute the 3rd spark tutorial paragraph, paragraph result: " + paragraphResult);

parameters = new HashMap<>();
parameters.put("marital", "married");
paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150213-230422_1600658137", parameters);
System.out.println("Execute the 4th spark tutorial paragraph, paragraph result: " + paragraphResult);
} finally {
// you need to stop interpreter explicitly if you are running paragraph separately.
zClient.stopInterpreter("2A94M5J1Z", "spark");
try (ZeppelinClient zClient = new ZeppelinClient(clientConfig)) {
String zeppelinVersion = zClient.getVersion();
System.out.println("Zeppelin version: " + zeppelinVersion);

// execute note 2A94M5J1Z paragraph by paragraph
try {
ParagraphResult paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150210-015259_1403135953");
System.out.println("Execute the 1st spark tutorial paragraph, paragraph result: " + paragraphResult);

paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150210-015302_1492795503");
System.out.println("Execute the 2nd spark tutorial paragraph, paragraph result: " + paragraphResult);

Map<String, String> parameters = new HashMap<>();
parameters.put("maxAge", "40");
paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150212-145404_867439529", parameters);
System.out.println("Execute the 3rd spark tutorial paragraph, paragraph result: " + paragraphResult);

parameters = new HashMap<>();
parameters.put("marital", "married");
paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150213-230422_1600658137", parameters);
System.out.println("Execute the 4th spark tutorial paragraph, paragraph result: " + paragraphResult);
} finally {
// you need to stop interpreter explicitly if you are running paragraph separately.
zClient.stopInterpreter("2A94M5J1Z", "spark");
}
}
{% endhighlight %}

Each `ZeppelinClient` owns an isolated HTTP session and connection pool. Close it after use, as in
the try-with-resources example above. Its REST session cookie is reused automatically when a
`ZSession` opens the notebook WebSocket. HTTPS and WSS use normal JVM certificate and hostname
verification; configure the JVM trust store for an internal certificate authority.

Here we list some importance apis of ZeppelinClient, for the completed api, please refer its javadoc.

{% highlight java %}
Expand Down
11 changes: 9 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@
<commons.io.version>2.15.1</commons.io.version>
<commons.collections.version>3.2.2</commons.collections.version>
<commons.cli.version>1.4</commons.cli.version>
<shiro.version>1.13.0</shiro.version>
<bouncycastle.version>1.80</bouncycastle.version>
<shiro.version>2.2.1</shiro.version>
<bouncycastle.version>1.84</bouncycastle.version>
<maven.version>3.6.3</maven.version>
<dropwizard.version>4.2.29</dropwizard.version>
<micrometer.version>1.14.2</micrometer.version>
Expand Down Expand Up @@ -374,6 +374,13 @@
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
<classifier>jakarta</classifier>
<exclusions>
<!-- Keep Jakarta modules from reintroducing unclassified Shiro artifacts. -->
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
Expand Down
Loading
Loading