Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
9 changes: 9 additions & 0 deletions docs/_includes/themes/zeppelin/_navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
<li><a href="{{BASE_PATH}}/pleasecontribute.html">Tajo</a></li>
</ul>
</li>
<li>
<a href="#" data-toggle="dropdown" class="dropdown-toggle">Security<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{{BASE_PATH}}/security/overviwe.html">Overview</a></li>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @prasadwagle : )
There is a typo : overviwe.html -> overview.html

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @AhyoungRyu! I fixed the typo. I am having trouble installing jekyll to test the docs. Please let me know if you see any other issues.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prasadwagle Thx for your quick response!
I left some comments again in this PR just a moment ago.

<li><a href="{{BASE_PATH}}/security/authentication.html#html">Authentication</a></li>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "#html" next to the {{BASE_PATH}}/security/authentication.html is not necessary.

<li><a href="{{BASE_PATH}}/security/notebook_authorization.html">Notebook Authorization</a></li>
<li><a href="{{BASE_PATH}}/security/interpreter_authorization.html">Interpreter Authorization</a></li>
</ul>
</li>
<li>
<a href="#" data-toggle="dropdown" class="dropdown-toggle">Display System <b class="caret"></b></a>
<ul class="dropdown-menu">
Expand Down
31 changes: 31 additions & 0 deletions docs/security/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
layout: page
title: "Authentication"
description: "Authentication"
group: security
---
<!--
Licensed 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.
-->
# Zeppelin Authentication

Authentication is company-specific.

One option is to use [Basic Access Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)

Anoteher is to have an authentication server that can verify user credentials in an LDAP server.
If an incoming request to the Zeppelin server does not have a cookie with user information encrypted with the authentication server public key, the user
is redirected to the authentication server. Once the user is verified, the authentication server redirects the browser to a specific
URL in the Zeppelin server which sets the authentication cookie in the browser.
The end result is that all requests to the Zeppelin
web server have the authentication cookie which contains user and groups information.
28 changes: 28 additions & 0 deletions docs/security/interpreter_authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
layout: page
title: "Notebook Authorization"
description: "Notebook Authorization"
group: security
---
<!--
Licensed 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.
-->
# Zeppelin Interpreter Authorization

The Interpreter authorization problem is more complex. Different interpreters require different strategies.

For the Hive interpreter, we need to maintain per-user connection pools.
The interpreter method takes the user string as parameter and executes the jdbc call using a connection in the user's connection pool.

In case of Presto, we don't need password if the Presto DB server runs backend code using HDFS authorization for the user.
For databases like Vertica and Mysql we would have to store password information for users.
66 changes: 66 additions & 0 deletions docs/security/notebook_authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
layout: page
title: "Notebook Authorization"
description: "Notebook Authorization"
group: security
---
<!--
Licensed 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.
-->
# Zeppelin Notebook Authorization

## Overview
There are different aspects to Zeppelin security:

* Authentication: is the user who they say they are?
* Notebook authorization: does the user have permissions to read or write to a note?
* Interpreter authorization: does the user have permissions to perform interpreter operations e.g. access data source objects?


## Authentication

Authentication is company-specific. One option is to have an authentication server that can verify user credentials in an LDAP server.
If an incoming request to the Zeppelin server does not have a cookie with user information encrypted with the authentication server public key, the user
is redirected to the authentication server. Once the user is verified, the authentication server redirects the browser to a specific
URL in the Zeppelin server which sets the authentication cookie in the browser.
The end result is that all requests to the Zeppelin
web server have the authentication cookie which contains user and groups information.


## Notebook Authorization

We assume that there is an authentication component that associates a user string and a set of group strings with every NotebookSocket.

Each note has the following:
* set of owner entities (users or groups)
* set of reader entities (users or groups)
* set of writer entities (users or groups)

If a set is empty, it means that any user can perform that operation.

The NotebookServer classifies every Note operation into three categories: read, write, manage.
Before executing a Note operation, it checks if the user and the groups associated with the NotebookSocket have permissions. For example, before executing an read
operation, it checks if the user and the groups have at least one entity that belongs to the reader entities.

To initialize and modify note permissions, we provide UI like "Interpreter binding". The user inputs comma separated entities for owners, readers and writers.
We execute a rest api call with this information. In the backend we get the user information for the connection and allow the operation if the user and groups
associated with the current user have at least one entity that belongs to owner entities for the note.

## Interpreter Authorization
The Interpreter authorization problem is more complex. Different interpreters require different strategies.

For the Hive interpreter, we need to maintain per-user connection pools.
The interpreter method takes the user string as parameter and executes the jdbc call using a connection in the user's connection pool.

In case of Presto, we don't need password if the Presto DB server runs backend code using HDFS authorization for the user.
For databases like Vertica and Mysql we would have to store password information for users.
29 changes: 29 additions & 0 deletions docs/security/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
layout: page
title: "Security Overview"
description: "Security Overview"
group: security
---
<!--
Licensed 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.
-->
{% include JB/setup %}

# Security Overview

There are three aspects to Zeppelin security:

* Authentication: is the user who they say they are? [More](overview.html)
* Notebook authorization: does the user have permissions to read or write to a note? [More](notebook_authorization.html)
* Interpreter authorization: does the user have permissions to perform interpreter operations e.g. access data source objects? [More](interpreter_authorization.html)

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package org.apache.zeppelin.rest;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -46,6 +48,7 @@
import org.apache.zeppelin.search.SearchService;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.socket.NotebookServer;
import org.apache.zeppelin.utils.SecurityUtils;
import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -75,6 +78,66 @@ public NotebookRestApi(Notebook notebook, NotebookServer notebookServer, SearchS
this.notebookIndex = search;
}

/**
* list note owners
*/
@GET
@Path("{noteId}/permissions")
public Response getNotePermissions(@PathParam("noteId") String noteId) {
Note note = notebook.getNote(noteId);
HashMap<String, HashSet> permissionsMap = new HashMap<String, HashSet>();
permissionsMap.put("owners", note.getOwners());
permissionsMap.put("readers", note.getReaders());
permissionsMap.put("writers", note.getWriters());
return new JsonResponse<>(Status.OK, "", permissionsMap).build();
}

String ownerPermissionError(HashSet<String> current,
HashSet<String> allowed) throws IOException {
LOG.info("Cannot change permissions. Connection owners {}. Allowed owners {}",
current.toString(), allowed.toString());
return "Insufficient privileges to change permissions.\n\n" +
"Allowed owners: " + allowed.toString() + "\n\n" +
"User belongs to: " + current.toString();
}

/**
* Set note owners
*/
@PUT
@Path("{noteId}/permissions")
public Response putNotePermissions(@PathParam("noteId") String noteId, String req)
throws IOException {
HashMap<String, HashSet> permMap = gson.fromJson(req,
new TypeToken<HashMap<String, HashSet>>(){}.getType());
Note note = notebook.getNote(noteId);
String principal = SecurityUtils.getPrincipal();
HashSet<String> roles = SecurityUtils.getRoles();
LOG.info("Set permissions {} {} {} {} {}",
noteId,
principal,
permMap.get("owners"),
permMap.get("readers"),
permMap.get("writers")
);

HashSet<String> userAndRoles = new HashSet<String>();
userAndRoles.add(principal);
userAndRoles.addAll(roles);
if (!note.isOwner(userAndRoles)) {
return new JsonResponse<>(Status.FORBIDDEN, ownerPermissionError(userAndRoles,
note.getOwners())).build();
}
note.setOwners(permMap.get("owners"));
note.setReaders(permMap.get("readers"));
note.setWriters(permMap.get("writers"));
LOG.debug("After set permissions {} {} {}", note.getOwners(), note.getReaders(),
note.getWriters());
note.persist();
notebookServer.broadcastNote(note);
return new JsonResponse<>(Status.OK).build();
}

/**
* bind a setting to note
* @throws IOException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.utils.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
Expand All @@ -36,6 +39,8 @@
@Path("/security")
@Produces("application/json")
public class SecurityRestApi {
private static final Logger LOG = LoggerFactory.getLogger(SecurityRestApi.class);

/**
* Required by Swagger.
*/
Expand All @@ -56,6 +61,7 @@ public SecurityRestApi() {
public Response ticket() {
ZeppelinConfiguration conf = ZeppelinConfiguration.create();
String principal = SecurityUtils.getPrincipal();
HashSet<String> roles = SecurityUtils.getRoles();
JsonResponse response;
// ticket set to anonymous for anonymous user. Simplify testing.
String ticket;
Expand All @@ -66,9 +72,11 @@ public Response ticket() {

Map<String, String> data = new HashMap<>();
data.put("principal", principal);
data.put("roles", roles.toString());
data.put("ticket", ticket);

response = new JsonResponse(Response.Status.OK, "", data);
LOG.warn(response.toString());
return response.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public static enum OP {
PARAGRAPH_APPEND_OUTPUT, // [s-c] append output
PARAGRAPH_UPDATE_OUTPUT, // [s-c] update (replace) output
PING,
AUTH_INFO,

ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object
ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del
Expand All @@ -111,6 +112,7 @@ public static enum OP {
public Map<String, Object> data = new HashMap<String, Object>();
public String ticket = "anonymous";
public String principal = "anonymous";
public String roles = "";

public Message(OP op) {
this.op = op;
Expand Down
Loading