Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ contents:
The config file is specified in JSON format.

* It contains the rules defining which catalog can be accessed by which user (see Catalog Rules below).
* The schema access rules defining which schema can be accessed by which user (see Schema Rules below).
* The principal rules specifying what principals can identify as what users (see Principal Rules below).

This plugin currently only supports catalog access control rules and principal
rules. If you want to limit access on a system level in any other way, you
This plugin currently supports catalog access control rules, schema access control rules
and principal rules. If you want to limit access on a system level in any other way, you
must implement a custom SystemAccessControl plugin
(see :doc:`/develop/system-access-control`).

Expand Down Expand Up @@ -136,6 +137,48 @@ and deny all other access, you can use the following rules:
]
}

Schema Rules
------------

These rules allow you to grant ownership of a schema. Having ownership of an
schema allows users to execute ``DROP SCHEMA``, ``ALTER SCHEMA`` and
``CREATE SCHEMA``. The user is granted ownership of a schema, based on
the first matching rule read from top to bottom. If no rule matches, ownership
is not granted. Each rule is composed of the following fields:

* ``user`` (optional): regex to match against user name. Defaults to ``.*``.
* ``schema`` (optional): regex to match against schema name. Defaults to ``.*``.
* ``owner`` (required): boolean indicating whether the user is to be considered
an owner of the schema. Defaults to ``false``.

For example, to provide ownership of all schemas to user ``admin``, treat all
users as owners of ``default`` schema and prevent user ``guest`` from ownership
of any schema, you can use the following rules:

.. code-block:: json
{
"catalogs": [
{
"allow": true
}
],
"schemas": [
{
"user": "admin",
"schema": ".*",
"owner": true
},
{
"user": "guest",
"owner": false
},
{
"schema": "default",
"owner": true
}
]
}

Principal Rules
---------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.facebook.airlift.log.Logger;
import com.facebook.presto.common.CatalogSchemaName;
import com.facebook.presto.plugin.base.security.ForwardingSystemAccessControl;
import com.facebook.presto.plugin.base.security.SchemaAccessControlRule;
import com.facebook.presto.security.CatalogAccessControlRule.AccessMode;
import com.facebook.presto.spi.CatalogSchemaTableName;
import com.facebook.presto.spi.PrestoException;
Expand Down Expand Up @@ -77,11 +78,13 @@ public class FileBasedSystemAccessControl

private final List<CatalogAccessControlRule> catalogRules;
private final Optional<List<PrincipalUserMatchRule>> principalUserMatchRules;
private final Optional<List<SchemaAccessControlRule>> schemaRules;

private FileBasedSystemAccessControl(List<CatalogAccessControlRule> catalogRules, Optional<List<PrincipalUserMatchRule>> principalUserMatchRules)
private FileBasedSystemAccessControl(List<CatalogAccessControlRule> catalogRules, Optional<List<PrincipalUserMatchRule>> principalUserMatchRules, Optional<List<SchemaAccessControlRule>> schemaRules)
{
this.catalogRules = catalogRules;
this.principalUserMatchRules = principalUserMatchRules;
this.schemaRules = schemaRules;
}

public static class Factory
Expand Down Expand Up @@ -144,7 +147,7 @@ private SystemAccessControl create(String configFileName)
Optional.of(Pattern.compile(".*")),
Optional.of(Pattern.compile("system"))));

return new FileBasedSystemAccessControl(catalogRulesBuilder.build(), rules.getPrincipalUserMatchRules());
return new FileBasedSystemAccessControl(catalogRulesBuilder.build(), rules.getPrincipalUserMatchRules(), rules.getSchemaRules());
}
}

Expand Down Expand Up @@ -221,23 +224,23 @@ private boolean canAccessCatalog(Identity identity, String catalogName, AccessMo
@Override
public void checkCanCreateSchema(Identity identity, AccessControlContext context, CatalogSchemaName schema)
{
if (!canAccessCatalog(identity, schema.getCatalogName(), ALL)) {
if (!isSchemaOwner(identity, schema)) {
denyCreateSchema(schema.toString());
}
}

@Override
public void checkCanDropSchema(Identity identity, AccessControlContext context, CatalogSchemaName schema)
{
if (!canAccessCatalog(identity, schema.getCatalogName(), ALL)) {
if (!isSchemaOwner(identity, schema)) {
denyDropSchema(schema.toString());
}
}

@Override
public void checkCanRenameSchema(Identity identity, AccessControlContext context, CatalogSchemaName schema, String newSchemaName)
{
if (!canAccessCatalog(identity, schema.getCatalogName(), ALL)) {
if (!isSchemaOwner(identity, schema) || !isSchemaOwner(identity, new CatalogSchemaName(schema.getCatalogName(), newSchemaName))) {
denyRenameSchema(schema.toString(), newSchemaName);
}
}
Expand Down Expand Up @@ -385,4 +388,23 @@ public void checkCanRevokeTablePrivilege(Identity identity, AccessControlContext
denyRevokeTablePrivilege(privilege.toString(), table.toString());
}
}

private boolean isSchemaOwner(Identity identity, CatalogSchemaName schema)
{
if (!canAccessCatalog(identity, schema.getCatalogName(), ALL)) {
return false;
}

if (!schemaRules.isPresent()) {
return true;
}

for (SchemaAccessControlRule rule : schemaRules.get()) {
Optional<Boolean> owner = rule.match(identity.getUser(), schema.getSchemaName());
if (owner.isPresent()) {
return owner.get();
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
package com.facebook.presto.security;

import com.facebook.presto.plugin.base.security.SchemaAccessControlRule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
Expand All @@ -24,14 +25,17 @@ public class FileBasedSystemAccessControlRules
{
private final List<CatalogAccessControlRule> catalogRules;
private final Optional<List<PrincipalUserMatchRule>> principalUserMatchRules;
private final Optional<List<SchemaAccessControlRule>> schemaRules;

@JsonCreator
public FileBasedSystemAccessControlRules(
@JsonProperty("catalogs") Optional<List<CatalogAccessControlRule>> catalogRules,
@JsonProperty("principals") Optional<List<PrincipalUserMatchRule>> principalUserMatchRules)
@JsonProperty("principals") Optional<List<PrincipalUserMatchRule>> principalUserMatchRules,
@JsonProperty("schemas") Optional<List<SchemaAccessControlRule>> schemaRules)
{
this.catalogRules = catalogRules.map(ImmutableList::copyOf).orElse(ImmutableList.of());
this.principalUserMatchRules = principalUserMatchRules.map(ImmutableList::copyOf);
this.schemaRules = schemaRules.map(ImmutableList::copyOf);
}

public List<CatalogAccessControlRule> getCatalogRules()
Expand All @@ -43,4 +47,9 @@ public Optional<List<PrincipalUserMatchRule>> getPrincipalUserMatchRules()
{
return principalUserMatchRules;
}

public Optional<List<SchemaAccessControlRule>> getSchemaRules()
{
return schemaRules;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,125 @@ public void testSchemaOperations()
}));
}

@Test
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.

We seem to deviate a lot on the tests? Are there "extra" tests that we don't need to cover? Can we do a better job of synchronizing?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@aweisberg I have tried covering all positive and negative tests for all the 3 schema operations. If you think something is missing here, I can add more tests to this.

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 can see why we deviated in several ways like not supporting groups and thus not needing to test groups.

If you fix the assertThrows I think it will be fine.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@aweisberg I have made the required changes.

public void testSchemaRulesForCheckCanCreateSchema()
{
TransactionManager transactionManager = createTestTransactionManager();
AccessControlManager accessControlManager = newAccessControlManager(transactionManager, "file-based-system-access-schema.json");

transaction(transactionManager, accessControlManager)
.execute(transactionId -> {
accessControlManager.checkCanCreateSchema(transactionId, bob, context, new CatalogSchemaName("alice-catalog", "bob"));
accessControlManager.checkCanCreateSchema(transactionId, bob, context, new CatalogSchemaName("bob-catalog", "bob"));
accessControlManager.checkCanCreateSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "some-schema"));
accessControlManager.checkCanCreateSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "bob"));
accessControlManager.checkCanCreateSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "alice"));
});

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
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.

Why does one assertThrows have a bunch of different things in it? It will stop executing after the first one fails?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Made the changes.

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.

There are three cases now where there were five previously? It looks like it isn't validating whether the catalog can be accessed since coverage shows no cases where it is denied because the user can't access the catalog.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes look like those got removed while making other changes in test, added them back.

accessControlManager.checkCanCreateSchema(transactionId, bob, context, new CatalogSchemaName("alice-catalog", "alice"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanCreateSchema(transactionId, bob, context, new CatalogSchemaName("bob-catalog", "alice"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanCreateSchema(transactionId, bob, context, new CatalogSchemaName("secret-catalog", "secret"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanCreateSchema(transactionId, alice, context, new CatalogSchemaName("secret-catalog", "secret"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanCreateSchema(transactionId, admin, context, new CatalogSchemaName("secret-catalog", "secret"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanCreateSchema(transactionId, alice, context, new CatalogSchemaName("alice-catalog", "alice"));
}));
}

@Test
public void testSchemaRulesForCheckCanDropSchema()
{
TransactionManager transactionManager = createTestTransactionManager();
AccessControlManager accessControlManager = newAccessControlManager(transactionManager, "file-based-system-access-schema.json");

transaction(transactionManager, accessControlManager)
.execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, bob, context, new CatalogSchemaName("alice-catalog", "bob"));
accessControlManager.checkCanDropSchema(transactionId, bob, context, new CatalogSchemaName("bob-catalog", "bob"));
accessControlManager.checkCanDropSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "bob"));
accessControlManager.checkCanDropSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "alice"));
accessControlManager.checkCanDropSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "some-schema"));
});

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, bob, context, new CatalogSchemaName("alice-catalog", "alice"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, bob, context, new CatalogSchemaName("bob-catalog", "alice"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, bob, context, new CatalogSchemaName("secret-catalog", "secret"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, alice, context, new CatalogSchemaName("secret-catalog", "secret"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, admin, context, new CatalogSchemaName("secret-catalog", "secret"));
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanDropSchema(transactionId, alice, context, new CatalogSchemaName("alice-catalog", "alice"));
}));
}

@Test
public void testSchemaRulesForCheckCanRenameSchema()
{
TransactionManager transactionManager = createTestTransactionManager();
AccessControlManager accessControlManager = newAccessControlManager(transactionManager, "file-based-system-access-schema.json");

transaction(transactionManager, accessControlManager)
.execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, bob, context, new CatalogSchemaName("alice-catalog", "bob"), "some-schema");
accessControlManager.checkCanRenameSchema(transactionId, bob, context, new CatalogSchemaName("bob-catalog", "bob"), "some-schema");
accessControlManager.checkCanRenameSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "bob"), "new-schema-name");
accessControlManager.checkCanRenameSchema(transactionId, admin, context, new CatalogSchemaName("some-catalog", "alice"), "new-schema-name");
});

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, bob, context, new CatalogSchemaName("alice-catalog", "alice"), "new-schema-name");
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, bob, context, new CatalogSchemaName("bob-catalog", "alice"), "new-schema-name");
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, bob, context, new CatalogSchemaName("secret-catalog", "secret"), "new-schema-name");
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, alice, context, new CatalogSchemaName("secret-catalog", "secret"), "new-schema-name");
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, admin, context, new CatalogSchemaName("secret-catalog", "secret"), "new-schema-name");
}));

assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> {
accessControlManager.checkCanRenameSchema(transactionId, alice, context, new CatalogSchemaName("alice-catalog", "alice"), "new-schema-name");
}));
}

@Test
public void testSchemaOperationsReadOnly()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"catalogs": [
{
"user": "alice",
"catalog": "alice-catalog",
"allow": "read-only"
},
{
"allow": true
}
],
"schemas": [
{
"schema": "secret",
"owner": false
},
{
"user": "admin",
"schema": ".*",
"owner": true
},
{
"user": "bob",
"schema": "bob|some-schema",
"owner": true
},
{
"user": "alice",
"schema": "alice",
"owner": true
}
]
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"schemas": [
"sessionProperties": [
{
"user": "(alice|bob)",
"schema": "(default|pv)",
"owner": true
"user": "admin",
"property": "max_split_size",
"allow": true
}
]
}