Skip to content

Commit

Permalink
Add access control to temporary credentials endpoints. (unitycatalog#511
Browse files Browse the repository at this point in the history
)

**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 is a follow on to the overall access control feature to add access
control to the credential vending endpoints. The access control rules
for credential vending follows the same rules as getting the entity.

This resolve Issue
unitycatalog#501

Signed-off-by: Teghan Nightengale <tnightengale@gmail.com>
  • Loading branch information
creechy authored and tnightengale committed Oct 2, 2024
1 parent 5196beb commit 748f0b7
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ private void addServices(ServerBuilder sb) {
ModelService modelService = new ModelService(authorizer);
// TODO: combine these into a single service in a follow-up PR
TemporaryTableCredentialsService temporaryTableCredentialsService =
new TemporaryTableCredentialsService(credentialOperations);
new TemporaryTableCredentialsService(authorizer, credentialOperations);
TemporaryVolumeCredentialsService temporaryVolumeCredentialsService =
new TemporaryVolumeCredentialsService(credentialOperations);
new TemporaryVolumeCredentialsService(authorizer, credentialOperations);
TemporaryModelVersionCredentialsService temporaryModelVersionCredentialsService =
new TemporaryModelVersionCredentialsService(credentialOperations);
new TemporaryModelVersionCredentialsService(authorizer, credentialOperations);
TemporaryPathCredentialsService temporaryPathCredentialsService =
new TemporaryPathCredentialsService(credentialOperations);
sb.service("/", (ctx, req) -> HttpResponse.of("Hello, Unity Catalog!"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package io.unitycatalog.server.auth.decorator;

import static io.unitycatalog.server.model.SecurableType.CATALOG;
import static io.unitycatalog.server.model.SecurableType.FUNCTION;
import static io.unitycatalog.server.model.SecurableType.METASTORE;
import static io.unitycatalog.server.model.SecurableType.REGISTERED_MODEL;
import static io.unitycatalog.server.model.SecurableType.SCHEMA;
import static io.unitycatalog.server.model.SecurableType.TABLE;
import static io.unitycatalog.server.model.SecurableType.VOLUME;

import io.unitycatalog.server.model.CatalogInfo;
import io.unitycatalog.server.model.FunctionInfo;
import io.unitycatalog.server.model.RegisteredModelInfo;
import io.unitycatalog.server.model.SchemaInfo;
import io.unitycatalog.server.model.SecurableType;
import io.unitycatalog.server.model.TableInfo;
import io.unitycatalog.server.model.VolumeInfo;
import io.unitycatalog.server.persist.CatalogRepository;
import io.unitycatalog.server.persist.FunctionRepository;
import io.unitycatalog.server.persist.MetastoreRepository;
import io.unitycatalog.server.persist.ModelRepository;
import io.unitycatalog.server.persist.SchemaRepository;
import io.unitycatalog.server.persist.TableRepository;
import io.unitycatalog.server.persist.VolumeRepository;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class KeyMapperUtil {
public static Map<SecurableType, Object> mapResourceKeys(
Map<SecurableType, Object> resourceKeys) {
Map<SecurableType, Object> resourceIds = new HashMap<>();

if (resourceKeys.containsKey(CATALOG)
&& resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(TABLE)) {
String fullName =
resourceKeys.get(CATALOG)
+ "."
+ resourceKeys.get(SCHEMA)
+ "."
+ resourceKeys.get(TABLE);
TableInfo table = TableRepository.getInstance().getTable(fullName);
resourceIds.put(TABLE, UUID.fromString(table.getTableId()));
}

// If only TABLE is specified, assuming its value is a full table name (including catalog and
// schema)
if (!resourceKeys.containsKey(CATALOG)
&& !resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(TABLE)) {
String fullName = (String) resourceKeys.get(TABLE);
// If the full name contains a dot, we assume it's a full name, otherwise we assume it's an id
TableInfo table =
fullName.contains(".")
? TableRepository.getInstance().getTable(fullName)
: TableRepository.getInstance().getTableById(fullName);
String fullSchemaName = table.getCatalogName() + "." + table.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(table.getCatalogName());
resourceIds.put(TABLE, UUID.fromString(table.getTableId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG)
&& resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(VOLUME)) {
String fullName =
resourceKeys.get(CATALOG)
+ "."
+ resourceKeys.get(SCHEMA)
+ "."
+ resourceKeys.get(VOLUME);
VolumeInfo volume = VolumeRepository.getInstance().getVolume(fullName);
resourceIds.put(VOLUME, UUID.fromString(volume.getVolumeId()));
}

// If only VOLUME is specified, assuming its value is a full volume name (including catalog and
// schema)
if (!resourceKeys.containsKey(CATALOG)
&& !resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(VOLUME)) {
String fullName = (String) resourceKeys.get(VOLUME);
// If the full name contains a dot, we assume it's a full name, otherwise we assume it's an id
VolumeInfo volume =
(fullName.contains("."))
? VolumeRepository.getInstance().getVolume(fullName)
: VolumeRepository.getInstance().getVolumeById(fullName);
String fullSchemaName = volume.getCatalogName() + "." + volume.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(volume.getCatalogName());
resourceIds.put(VOLUME, UUID.fromString(volume.getVolumeId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG)
&& resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(FUNCTION)) {
String fullName =
resourceKeys.get(CATALOG)
+ "."
+ resourceKeys.get(SCHEMA)
+ "."
+ resourceKeys.get(FUNCTION);
FunctionInfo function = FunctionRepository.getInstance().getFunction(fullName);
resourceIds.put(FUNCTION, UUID.fromString(function.getFunctionId()));
}

// If only FUNCTION is specified, assuming its value is a full volume name (including catalog
// and schema)
if (!resourceKeys.containsKey(CATALOG)
&& !resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(FUNCTION)) {
String fullName = (String) resourceKeys.get(FUNCTION);
FunctionInfo function = FunctionRepository.getInstance().getFunction(fullName);
String fullSchemaName = function.getCatalogName() + "." + function.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(function.getCatalogName());
resourceIds.put(FUNCTION, UUID.fromString(function.getFunctionId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG)
&& resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(REGISTERED_MODEL)) {
String fullName =
resourceKeys.get(CATALOG)
+ "."
+ resourceKeys.get(SCHEMA)
+ "."
+ resourceKeys.get(REGISTERED_MODEL);
RegisteredModelInfo model = ModelRepository.getInstance().getRegisteredModel(fullName);
resourceIds.put(REGISTERED_MODEL, UUID.fromString(model.getId()));
}

// If only REGISTERED_MODEL is specified, assuming its value is a full volume name (including
// catalog and schema)
if (!resourceKeys.containsKey(CATALOG)
&& !resourceKeys.containsKey(SCHEMA)
&& resourceKeys.containsKey(REGISTERED_MODEL)) {
String fullName = (String) resourceKeys.get(REGISTERED_MODEL);
RegisteredModelInfo model = ModelRepository.getInstance().getRegisteredModel(fullName);
String fullSchemaName = model.getCatalogName() + "." + model.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(model.getCatalogName());
resourceIds.put(REGISTERED_MODEL, UUID.fromString(model.getId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA)) {
String fullName = resourceKeys.get(CATALOG) + "." + resourceKeys.get(SCHEMA);
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullName);
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
}

// if only SCHEMA is specified, assuming its value is a full schema name (including catalog)
if (!resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA)) {
String fullName = (String) resourceKeys.get(SCHEMA);
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(schema.getCatalogName());
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG)) {
String fullName = (String) resourceKeys.get(CATALOG);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(fullName);
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(METASTORE)) {
resourceIds.put(METASTORE, MetastoreRepository.getInstance().getMetastoreId());
}

return resourceIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,7 @@
import io.unitycatalog.server.auth.annotation.AuthorizeKeys;
import io.unitycatalog.server.exception.BaseException;
import io.unitycatalog.server.exception.ErrorCode;
import io.unitycatalog.server.model.CatalogInfo;
import io.unitycatalog.server.model.FunctionInfo;
import io.unitycatalog.server.model.RegisteredModelInfo;
import io.unitycatalog.server.model.SchemaInfo;
import io.unitycatalog.server.model.SecurableType;
import io.unitycatalog.server.model.TableInfo;
import io.unitycatalog.server.model.VolumeInfo;
import io.unitycatalog.server.persist.CatalogRepository;
import io.unitycatalog.server.persist.FunctionRepository;
import io.unitycatalog.server.persist.MetastoreRepository;
import io.unitycatalog.server.persist.ModelRepository;
import io.unitycatalog.server.persist.SchemaRepository;
import io.unitycatalog.server.persist.TableRepository;
import io.unitycatalog.server.persist.VolumeRepository;
import io.unitycatalog.server.utils.IdentityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -50,13 +37,6 @@
import static io.unitycatalog.server.auth.decorator.KeyLocator.Source.PARAM;
import static io.unitycatalog.server.auth.decorator.KeyLocator.Source.PAYLOAD;
import static io.unitycatalog.server.auth.decorator.KeyLocator.Source.SYSTEM;
import static io.unitycatalog.server.model.SecurableType.CATALOG;
import static io.unitycatalog.server.model.SecurableType.FUNCTION;
import static io.unitycatalog.server.model.SecurableType.METASTORE;
import static io.unitycatalog.server.model.SecurableType.REGISTERED_MODEL;
import static io.unitycatalog.server.model.SecurableType.SCHEMA;
import static io.unitycatalog.server.model.SecurableType.TABLE;
import static io.unitycatalog.server.model.SecurableType.VOLUME;

/**
* Armeria access control Decorator.
Expand Down Expand Up @@ -192,7 +172,7 @@ private static Object findPayloadValue(String key, Map<String, Object> payload)
private void checkAuthorization(UUID principal, String expression, Map<SecurableType, Object> resourceKeys) {
LOGGER.debug("resourceKeys = {}", resourceKeys);

Map<SecurableType, Object> resourceIds = mapResourceKeys(resourceKeys);
Map<SecurableType, Object> resourceIds = KeyMapperUtil.mapResourceKeys(resourceKeys);

if (!resourceIds.keySet().containsAll(resourceKeys.keySet())) {
LOGGER.warn("Some resource keys have unresolved ids.");
Expand All @@ -205,110 +185,6 @@ private void checkAuthorization(UUID principal, String expression, Map<Securable
}
}

private Map<SecurableType, Object> mapResourceKeys(Map<SecurableType, Object> resourceKeys) {
Map<SecurableType, Object> resourceIds = new HashMap<>();

if (resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(TABLE)) {
String fullName = resourceKeys.get(CATALOG) + "." + resourceKeys.get(SCHEMA) + "." + resourceKeys.get(TABLE);
TableInfo table = TableRepository.getInstance().getTable(fullName);
resourceIds.put(TABLE, UUID.fromString(table.getTableId()));
}

// If only TABLE is specified, assuming its value is a full table name (including catalog and schema)
if (!resourceKeys.containsKey(CATALOG) && !resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(TABLE)) {
String fullName = (String) resourceKeys.get(TABLE);
TableInfo table = TableRepository.getInstance().getTable(fullName);
String fullSchemaName = table.getCatalogName() + "." + table.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(table.getCatalogName());
resourceIds.put(TABLE, UUID.fromString(table.getTableId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(VOLUME)) {
String fullName = resourceKeys.get(CATALOG) + "." + resourceKeys.get(SCHEMA) + "." + resourceKeys.get(VOLUME);
VolumeInfo volume = VolumeRepository.getInstance().getVolume(fullName);
resourceIds.put(VOLUME, UUID.fromString(volume.getVolumeId()));
}

// If only VOLUME is specified, assuming its value is a full volume name (including catalog and schema)
if (!resourceKeys.containsKey(CATALOG) && !resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(VOLUME)) {
String fullName = (String) resourceKeys.get(VOLUME);
VolumeInfo volume = VolumeRepository.getInstance().getVolume(fullName);
String fullSchemaName = volume.getCatalogName() + "." + volume.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(volume.getCatalogName());
resourceIds.put(VOLUME, UUID.fromString(volume.getVolumeId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(FUNCTION)) {
String fullName = resourceKeys.get(CATALOG) + "." + resourceKeys.get(SCHEMA) + "." + resourceKeys.get(FUNCTION);
FunctionInfo function = FunctionRepository.getInstance().getFunction(fullName);
resourceIds.put(FUNCTION, UUID.fromString(function.getFunctionId()));
}

// If only FUNCTION is specified, assuming its value is a full volume name (including catalog and schema)
if (!resourceKeys.containsKey(CATALOG) && !resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(FUNCTION)) {
String fullName = (String) resourceKeys.get(FUNCTION);
FunctionInfo function = FunctionRepository.getInstance().getFunction(fullName);
String fullSchemaName = function.getCatalogName() + "." + function.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(function.getCatalogName());
resourceIds.put(FUNCTION, UUID.fromString(function.getFunctionId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(REGISTERED_MODEL)) {
String fullName = resourceKeys.get(CATALOG) + "." + resourceKeys.get(SCHEMA) + "." + resourceKeys.get(REGISTERED_MODEL);
RegisteredModelInfo model = ModelRepository.getInstance().getRegisteredModel(fullName);
resourceIds.put(REGISTERED_MODEL, UUID.fromString(model.getId()));
}

// If only REGISTERED_MODEL is specified, assuming its value is a full volume name (including catalog and schema)
if (!resourceKeys.containsKey(CATALOG) && !resourceKeys.containsKey(SCHEMA) && resourceKeys.containsKey(REGISTERED_MODEL)) {
String fullName = (String) resourceKeys.get(REGISTERED_MODEL);
RegisteredModelInfo model = ModelRepository.getInstance().getRegisteredModel(fullName);
String fullSchemaName = model.getCatalogName() + "." + model.getSchemaName();
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullSchemaName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(model.getCatalogName());
resourceIds.put(REGISTERED_MODEL, UUID.fromString(model.getId()));
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}


if (resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA)) {
String fullName = resourceKeys.get(CATALOG) + "." + resourceKeys.get(SCHEMA);
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullName);
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
}

// if only SCHEMA is specified, assuming its value is a full schema name (including catalog)
if (!resourceKeys.containsKey(CATALOG) && resourceKeys.containsKey(SCHEMA)) {
String fullName = (String) resourceKeys.get(SCHEMA);
SchemaInfo schema = SchemaRepository.getInstance().getSchema(fullName);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(schema.getCatalogName());
resourceIds.put(SCHEMA, UUID.fromString(schema.getSchemaId()));
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(CATALOG)) {
String fullName = (String) resourceKeys.get(CATALOG);
CatalogInfo catalog = CatalogRepository.getInstance().getCatalog(fullName);
resourceIds.put(CATALOG, UUID.fromString(catalog.getId()));
}

if (resourceKeys.containsKey(METASTORE)) {
resourceIds.put(METASTORE, MetastoreRepository.getInstance().getMetastoreId());
}

return resourceIds;
}

private static String findAuthorizeExpression(Method method) {
// TODO: Cache this by method

Expand Down
Loading

0 comments on commit 748f0b7

Please sign in to comment.