Skip to content

Commit f9a9bf1

Browse files
creechyvikrantpuppalaxx-db
authored
Basic server access control (unitycatalog#378)
**PR Checklist** - [ ] A description of the changes is added to the description of this PR. - [ ] If there is a related issue, make sure it is linked to this PR. - [ ] If you've fixed a bug or added code that should be tested, add tests! - [ ] If you've added or modified a feature, documentation in `docs` is updated **Description of changes** This feature builds upon the authentication functionality to add access controls to UC server endpoints. - Access control is optionally enabled through server server configuration (`server.authorization=enable`) - Resource endpoints all have access control rules that match access rules for Databricks Unity Catalog. - An access control is defined by a principal, a resource and a privilege. - Principals are currently only user entities. - Access controls are hierarchically applied - A privilege assigned to a resource for a principal is active for all the children. - A command-line interface is available to add, update, and delete authorizations. - The root of the hierarchy is a catalog. - There is a new concept of a metastore, which is primarily a resource to assign server level privileges with. The initial set of privileges defined are - `CREATE CATALOG` - allows the principal to create catalogs - `USE CATALOG` - allows the principal to access/use a catalog - `CREATE SCHEMA` - allows the principal to create schemas within a catalog - `USE SCHEMA` - allows the principal to access/use the schema and child tables - `CREATE_TABLE` - allows the principal to create tables in the schema - `SELECT` - allows the principal to run queries against table(s) - `CREATE_FUNCTION` - allows principal to create functions in the schema - `EXECUTE` - allows the principal to execute function(s) - `CREATE_VOLUME` - allows principal to create volumes in the schema - `READ VOLUME` - allows the principal to access volumes within the catalog - `CREATE MODEL` - allows the principal to create models within a schema The rules are generally more complex than summarized above, check Databricks documentation or the code for more details. The CLI interface provides a new `permission` command. Examples ``` bin/uc permission create --securable_type catalog \ --name mycatalog --principal [email protected] --privilege "CREATE CATALOG` bin/uc permission get --securable_type catalog --name unity binuc permission delete --securable_type table \ --name mycatalog.myschema.table --principal [email protected] --privilege SELECT ``` The framework attempts to separate the concern of access control from the rest of the request handling and processing through annotation-based configuration, via `@AuthorizeExpression`, `@AuthorizeKey` and `@AuthorizeKeys` added to each service entry point. All requests are routed through the `UnityAccessDecorator` which decodes the request, parameters and authorization configuration to evaluate whether access should be allowed or denied. --------- Co-authored-by: Vikrant Puppala <[email protected]> Co-authored-by: Vikrant Puppala <[email protected]> Co-authored-by: Xiang Xu <[email protected]>
1 parent aee192c commit f9a9bf1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4582
-93
lines changed

api/all.yaml

+144
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ tags:
3232
- name: TemporaryCredentials
3333
description: |
3434
Temporary credentials are used to access storage locations or assets for a limited lifetime.
35+
- name: Permissions
36+
description: |
37+
In Unity Catalog, data is secure by default. Access can be granted.
3538
paths:
3639
/catalogs:
3740
post:
@@ -1056,6 +1059,61 @@ paths:
10561059
application/json:
10571060
schema:
10581061
$ref: '#/components/schemas/TemporaryCredentials'
1062+
/permissions/{securable_type}/{full_name}:
1063+
parameters:
1064+
- name: securable_type
1065+
in: path
1066+
description: Type of securable.
1067+
required: true
1068+
schema:
1069+
$ref: '#/components/schemas/SecurableType'
1070+
- name: full_name
1071+
in: path
1072+
description: Full name of securable.
1073+
required: true
1074+
schema:
1075+
type: string
1076+
get:
1077+
tags:
1078+
- Grants
1079+
parameters:
1080+
- name: principal
1081+
in: query
1082+
description: |
1083+
If provided, only the permissions for the specified principal (user or group) are returned.
1084+
schema:
1085+
type: string
1086+
required: false
1087+
operationId: get
1088+
summary: Get permissions
1089+
description: |
1090+
Gets the permissions for a securable.
1091+
responses:
1092+
'200':
1093+
description: The permissions list for securable was successfully retrieved.
1094+
content:
1095+
application/json:
1096+
schema:
1097+
$ref: '#/components/schemas/PermissionsList'
1098+
patch:
1099+
tags:
1100+
- Grants
1101+
operationId: update
1102+
summary: Update a permission
1103+
description: |
1104+
Updates the permissions for a securable.
1105+
requestBody:
1106+
content:
1107+
application/json:
1108+
schema:
1109+
$ref: '#/components/schemas/UpdatePermissions'
1110+
responses:
1111+
'200':
1112+
description: The permissions list for securable was successfully updated.
1113+
content:
1114+
application/json:
1115+
schema:
1116+
$ref: '#/components/schemas/PermissionsList'
10591117
components:
10601118
schemas:
10611119
SecurablePropertiesMap:
@@ -2145,6 +2203,92 @@ components:
21452203
- PATH_CREATE_TABLE
21462204
- PATH_REFRESH
21472205
type: string
2206+
PrincipalType:
2207+
type: string
2208+
enum:
2209+
- USER
2210+
- GROUP
2211+
description: The type of the principal.
2212+
SecurableType:
2213+
type: string
2214+
enum:
2215+
- metastore
2216+
- catalog
2217+
- schema
2218+
- table
2219+
- function
2220+
- volume
2221+
- registered_model
2222+
description: The type of the resource.
2223+
Privilege:
2224+
type: string
2225+
enum:
2226+
- CREATE CATALOG
2227+
- USE CATALOG
2228+
- CREATE SCHEMA
2229+
- USE SCHEMA
2230+
- CREATE TABLE
2231+
- SELECT
2232+
- CREATE FUNCTION
2233+
- EXECUTE
2234+
- CREATE VOLUME
2235+
- READ VOLUME
2236+
- CREATE MODEL
2237+
description: The privilege to grant.
2238+
UpdatePermissions:
2239+
type: object
2240+
properties:
2241+
changes:
2242+
description: Array of permissions change objects.
2243+
type: array
2244+
items:
2245+
$ref: '#/components/schemas/PermissionsChange'
2246+
required:
2247+
- changes
2248+
PermissionsChange:
2249+
type: object
2250+
properties:
2251+
principal:
2252+
description: The principal whose privileges we are changing.
2253+
type: string
2254+
add:
2255+
description: The set of privileges to add.
2256+
type: array
2257+
items:
2258+
$ref: '#/components/schemas/Privilege'
2259+
remove:
2260+
description: The set of privileges to remove.
2261+
type: array
2262+
items:
2263+
$ref: '#/components/schemas/Privilege'
2264+
required:
2265+
- principal
2266+
- add
2267+
- remove
2268+
PermissionsList:
2269+
type: object
2270+
properties:
2271+
privilege_assignments:
2272+
description: The privileges assigned to each principal.
2273+
type: array
2274+
items:
2275+
$ref: '#/components/schemas/PrivilegeAssignment'
2276+
required:
2277+
- privilege_assignments
2278+
PrivilegeAssignment:
2279+
type: object
2280+
properties:
2281+
principal:
2282+
description: The principal (user email address or group name).
2283+
type: string
2284+
privileges:
2285+
description: The privileges assigned to the principal.
2286+
type: array
2287+
items:
2288+
$ref: '#/components/schemas/Privilege'
2289+
required:
2290+
- principal
2291+
- privileges
21482292
info:
21492293
title: Unity Catalog API
21502294
version: '0.1'

build.sbt

+5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ lazy val commonSettings = Seq(
8484
case DepModuleInfo("org.hibernate.orm", _, _) => true
8585
case DepModuleInfo("com.unboundid.scim2", _, _) => true
8686
case DepModuleInfo("com.unboundid.product.scim2", _, _) => true
87+
case DepModuleInfo("com.googlecode.aviator", _, _) => true
8788
// Duo license:
8889
// - Eclipse Public License 2.0
8990
// - GNU General Public License, version 2 with the GNU Classpath Exception
@@ -301,6 +302,10 @@ lazy val server = (project in file("server"))
301302

302303
// Auth dependencies
303304
"com.unboundid.product.scim2" % "scim2-sdk-common" % "3.1.0",
305+
"org.casbin" % "jcasbin" % "1.55.0",
306+
"org.casbin" % "jdbc-adapter" % "2.7.0"
307+
exclude("com.microsoft.sqlserver", "mssql-jdbc")
308+
exclude("com.oracle.database.jdbc", "ojdbc6"),
304309
"org.springframework" % "spring-expression" % "6.1.11",
305310
"com.auth0" % "java-jwt" % "4.4.0",
306311
"com.auth0" % "jwks-rsa" % "0.22.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.unitycatalog.cli;
2+
3+
import static io.unitycatalog.cli.utils.CliUtils.*;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.ObjectWriter;
7+
import io.unitycatalog.cli.utils.CliParams;
8+
import io.unitycatalog.cli.utils.CliUtils;
9+
import io.unitycatalog.client.ApiClient;
10+
import io.unitycatalog.client.ApiException;
11+
import io.unitycatalog.client.api.GrantsApi;
12+
import io.unitycatalog.client.model.PermissionsChange;
13+
import io.unitycatalog.client.model.Privilege;
14+
import io.unitycatalog.client.model.SecurableType;
15+
import io.unitycatalog.client.model.UpdatePermissions;
16+
import java.util.List;
17+
import org.apache.commons.cli.CommandLine;
18+
import org.json.JSONObject;
19+
20+
public class PermissionCli {
21+
private static ObjectWriter objectWriter;
22+
23+
public static void handle(CommandLine cmd, ApiClient apiClient)
24+
throws JsonProcessingException, ApiException {
25+
GrantsApi grantsApi = new GrantsApi(apiClient);
26+
String[] subArgs = cmd.getArgs();
27+
objectWriter = CliUtils.getObjectWriter(cmd);
28+
String subCommand = subArgs[1];
29+
JSONObject json = CliUtils.createJsonFromOptions(cmd);
30+
String output = EMPTY;
31+
switch (subCommand) {
32+
case CREATE:
33+
case DELETE:
34+
output = updatePermission(grantsApi, json, subCommand);
35+
break;
36+
case GET:
37+
output = getPermission(grantsApi, json);
38+
break;
39+
default:
40+
printEntityHelp(PERMISSION);
41+
}
42+
postProcessAndPrintOutput(cmd, output, subCommand);
43+
}
44+
45+
private static String updatePermission(GrantsApi grantsApi, JSONObject json, String subCommand)
46+
throws JsonProcessingException, ApiException {
47+
SecurableType securableType =
48+
SecurableType.fromValue(json.getString(CliParams.SECURABLE_TYPE.getServerParam()));
49+
String name = json.getString(CliParams.NAME.getServerParam());
50+
List<Privilege> add =
51+
subCommand.equals(CREATE)
52+
? List.of(Privilege.fromValue(json.getString(CliParams.PRIVILEGE.getServerParam())))
53+
: List.of();
54+
List<Privilege> remove =
55+
subCommand.equals(DELETE)
56+
? List.of(Privilege.fromValue(json.getString(CliParams.PRIVILEGE.getServerParam())))
57+
: List.of();
58+
PermissionsChange permissionsChange =
59+
new PermissionsChange()
60+
.principal(json.getString(CliParams.PRINCIPAL.getServerParam()))
61+
.add(add)
62+
.remove(remove);
63+
UpdatePermissions updatePermissions =
64+
new UpdatePermissions().changes(List.of(permissionsChange));
65+
66+
return objectWriter.writeValueAsString(
67+
grantsApi.update(securableType, name, updatePermissions).getPrivilegeAssignments());
68+
}
69+
70+
private static String getPermission(GrantsApi grantsApi, JSONObject json)
71+
throws JsonProcessingException, ApiException {
72+
SecurableType securableType =
73+
SecurableType.fromValue(json.getString(CliParams.SECURABLE_TYPE.getServerParam()));
74+
String name = json.getString(CliParams.NAME.getServerParam());
75+
String principal = null;
76+
if (json.has(CliParams.PRINCIPAL.getServerParam())) {
77+
principal = json.getString(CliParams.PRINCIPAL.getServerParam());
78+
}
79+
return objectWriter.writeValueAsString(
80+
grantsApi.get(securableType, name, principal).getPrivilegeAssignments());
81+
}
82+
}

examples/cli/src/main/java/io/unitycatalog/cli/UnityCatalogCli.java

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ public static void main(String[] args) {
144144
case CliUtils.MODEL_VERSION:
145145
ModelVersionCli.handle(cmd, apiClient);
146146
break;
147+
case CliUtils.PERMISSION:
148+
PermissionCli.handle(cmd, apiClient);
149+
break;
147150
case CliUtils.USER:
148151
UserCli.handle(cmd, getControlClient(cmd));
149152
break;

examples/cli/src/main/java/io/unitycatalog/cli/utils/CliParams.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,15 @@ public enum CliParams {
5252
"output"),
5353
VERSION("version", "Version number of a registered model entity.", "version"),
5454
FORCE("force", "To force delete the entity", "force"),
55-
RESOURCE_TYPE("resource_type", "The type of the resource", "resource_type"),
55+
SECURABLE_TYPE("securable_type", "The type of the securable", "securable_type"),
5656
PRINCIPAL("principal", "The target principal of the permission change", "principal"),
5757
PRIVILEGE("privilege", "The privilege to grant or revoke", "privilege"),
5858
ID("id", "The unique id of the user", "id"),
5959
EXTERNAL_ID("external_id", "The identity provider's id for the user", "externalId"),
6060
EMAIL("email", "The email address for the user", "email"),
6161
FILTER("filter", "Query by which the results have to be filtered", "filter"),
6262
START_INDEX(
63-
"startIndex",
64-
"Specifies the index of the first result. First item is number 1",
65-
"startIndex"),
63+
"start_index", "Specifies the index (starting at 1) of the first result.", "startIndex"),
6664
COUNT("count", "Desired number of results per page", "count");
6765
private final String value;
6866
private final String helpMessage;

examples/cli/src/main/java/io/unitycatalog/cli/utils/CliUtils.java

+30
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class CliUtils {
3333
public static final String FUNCTION = "function";
3434
public static final String REGISTERED_MODEL = "registered_model";
3535
public static final String MODEL_VERSION = "model_version";
36+
public static final String PERMISSION = "permission";
3637
public static final String USER = "user";
3738
public static final String CREATE = "create";
3839
public static final String LIST = "list";
@@ -277,6 +278,35 @@ public List<CliParams> getOptionalParams() {
277278
put(DELETE, new CliOptions(List.of(CliParams.ID), List.of()));
278279
}
279280
});
281+
put(
282+
PERMISSION,
283+
new HashMap<String, CliOptions>() {
284+
{
285+
put(
286+
CREATE,
287+
new CliOptions(
288+
List.of(
289+
CliParams.SECURABLE_TYPE,
290+
CliParams.NAME,
291+
CliParams.PRINCIPAL,
292+
CliParams.PRIVILEGE),
293+
List.of()));
294+
put(
295+
DELETE,
296+
new CliOptions(
297+
List.of(
298+
CliParams.SECURABLE_TYPE,
299+
CliParams.NAME,
300+
CliParams.PRINCIPAL,
301+
CliParams.PRIVILEGE),
302+
List.of()));
303+
put(
304+
GET,
305+
new CliOptions(
306+
List.of(CliParams.SECURABLE_TYPE, CliParams.NAME),
307+
List.of(CliParams.PRINCIPAL)));
308+
}
309+
});
280310
}
281311
};
282312

0 commit comments

Comments
 (0)