Skip to content

Commit

Permalink
Add "attach storage namespace [to repository]" IAM action type (#2430)
Browse files Browse the repository at this point in the history
* Document proposed solution: add AttachStorageNamespace action

* Test for AttachStorageNamespace permission in CreateRepository

* Correctly AND fs:CreateRepository and fs:AttachStorageNamespace

Multiple permissions passed to `Authorize` are disjunctive ("OR").  To get a
conjection ("AND") need to call `Authorize` twice.

* Add migrate {up,down} scripts for AttachStorageNamespace

Allow `fs:AttachStorageNamespace` for _all_ storage namespaces on all rules
that allow a user to perform some `fs:CreateRepository` action (no matter
how restricted).  This preserves current semantics.

Skip doing this if the rule already allows some `fs:AttachStorageNamespace`
action (no matter how restricted) -- so UP -> DOWN -> UP changes nothing.

Do nothing.  Keep existing `fs:AttachStorageNamespace` rules.  These will do
nothing until re-upgrading.  On re-upgrade, these rules are unchanged.

* Add changelog entry

* Make upgrade script work with PostgreSQL v11

This is our minimal supported version of PostgreSQL, so don't use (useful!)
`jsonb_*` functions that were added in v12.  (Downgrade the SQL in the SQL
upgrade script, essentially...).
  • Loading branch information
arielshaqed authored Sep 14, 2021
1 parent c00d972 commit 78078b7
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

## Unreleased - XXXX-XX-XX

- Add new "AttachStorageNamespace" IAM action. Controls users' ability to
create repositories with particular storage namespaces (bucket names).
(#2220)

## v0.50.0 - 2021-09-05

- Fix double slash bug in storage namespace (#2397)
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ For the full list of actions and their required permissions see the following ta
|Create Commit |`fs:CreateCommit` |`arn:lakefs:fs:::repository/{repositoryId}/branch/{branchId}` |POST /repositories/{repositoryId}/branches/{branchId}/commits |- |
|Get Commit log |`fs:ReadBranch` |`arn:lakefs:fs:::repository/{repositoryId}/branch/{branchId}` |GET /repositories/{repositoryId}/branches/{branchId}/commits |- |
|Create Repository |`fs:CreateRepository` |`arn:lakefs:fs:::repository/{repositoryId}` |POST /repositories |- |
| Namespace Attach to Repository |`fs:AttachStorageNamespace` |`arn:lakefs:fs:::namespace/{storageNamespace}` |POST /repositories |- |
|Delete Repository |`fs:DeleteRepository` |`arn:lakefs:fs:::repository/{repositoryId}` |DELETE /repositories/{repositoryId} |- |
|List Branches |`fs:ListBranches` |`arn:lakefs:fs:::repository/{repositoryId}` |GET /repositories/{repositoryId}/branches |ListObjects/ListObjectsV2 (with delimiter = `/` and empty prefix) |
|Get Branch |`fs:ReadBranch` |`arn:lakefs:fs:::repository/{repositoryId}/branch/{branchId}` |GET /repositories/{repositoryId}/branches/{branchId} |- |
Expand Down Expand Up @@ -161,6 +162,11 @@ For the full list of actions and their required permissions see the following ta
|Set Garbage Collection Rules |`retention:SetGarbageCollectionRules` |`arn:lakefs:fs:::repository/{repositoryId}` |POST /repositories/{repositoryId}/gc/rules |- |
|Prepare Garbage Collection Commits|`retention:PrepareGarbageCollectionCommits`|`arn:lakefs:fs:::repository/{repositoryId}` |POST /repositories/{repositoryId}/gc/prepare_commits |- |

Some APIs may require more than one action. For instance, in order to
create a repository (`POST /repositories`) you need permission to
`fs:CreateRepository` for the _name_ of the repository and also
`fs:AttachStorageNamespace` for the _storage namespace_ used.

### Preconfigured Policies

The following Policies are created during initial setup:
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,11 @@ func (c *Controller) CreateRepository(w http.ResponseWriter, r *http.Request, bo
Action: permissions.CreateRepositoryAction,
Resource: permissions.RepoArn(body.Name),
},
}) || !c.authorize(w, r, []permissions.Permission{
{
Action: permissions.AttachStorageNamespace,
Resource: permissions.StorageNamespace(body.StorageNamespace),
},
}) {
return
}
Expand Down
1 change: 1 addition & 0 deletions pkg/ddl/000032_iam_add_attach_namespace.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- Keep all "fs:AttachStorageNamespace" operations, they will merely have no effect.
55 changes: 55 additions & 0 deletions pkg/ddl/000032_iam_add_attach_namespace.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
BEGIN;

-- wild_stars returns T if pattern matches hay using IAM-style wildcards: *
-- is any string, ? is a single char. It fails if pattern contains % or _
-- chars.
CREATE OR REPLACE FUNCTION pg_temp.wild_stars(pattern VARCHAR, hay VARCHAR)
RETURNS BOOLEAN LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
unsafe BOOLEAN;
match BOOLEAN;

BEGIN
SELECT pattern LIKE '%\%%' OR pattern LIKE '%\_%' INTO STRICT unsafe;
IF unsafe THEN
RAISE EXCEPTION 'unsafe pattern % contains "%%" or "_"', pattern;
END IF;
SELECT hay LIKE replace(replace(pattern, '*', '%'), '?', '_') INTO STRICT match;
RETURN match;
END;
$$;

-- jsonb_string translates a JSONB string object to SQL TEXT. See
-- https://stackoverflow.com/a/58755595.
CREATE OR REPLACE FUNCTION pg_temp.jsonb_string(j JSONB) RETURNS TEXT LANGUAGE sql IMMUTABLE AS $$
SELECT j ->> 0
$$;

-- statement_has_action returns T if statement (of the JSON type stored in
-- auth_policies) mentions action.
CREATE OR REPLACE FUNCTION pg_temp.statement_has_action(statement JSONB, action VARCHAR)
RETURNS BOOLEAN LANGUAGE sql IMMUTABLE AS $$
SELECT action IN (
SELECT jsonb_array_elements_text(value->'Action')
FROM jsonb_array_elements(statement)
);
$$;

UPDATE auth_policies
SET statement = statement || '[{"Action": ["fs:AttachStorageNamespace"], "Effect": "allow", "Resource": "*"}]'::jsonb
WHERE id IN (
SELECT DISTINCT id FROM (
SELECT id, pg_temp.jsonb_string(s->'Effect') AS effect, jsonb_array_elements(s->'Action') AS action
FROM (
SELECT id, jsonb_array_elements(statement) s FROM auth_policies
-- Update only statements that never mention
-- AttachStorageNamespace. So downgrade can do nothing, and
-- re-upgrading will not re-add an existing statement or harm
-- pre-existing policies.
WHERE NOT pg_temp.statement_has_action(statement, 'fs:AttachStorageNamespace')
) y
) x
WHERE effect = 'allow' AND pg_temp.wild_stars(pg_temp.jsonb_string(action), 'fs:CreateRepository')
);

END;
1 change: 1 addition & 0 deletions pkg/permissions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var (
const (
ReadRepositoryAction = "fs:ReadRepository"
CreateRepositoryAction = "fs:CreateRepository"
AttachStorageNamespace = "fs:AttachStorageNamespace"
DeleteRepositoryAction = "fs:DeleteRepository"
ListRepositoriesAction = "fs:ListRepositories"
ReadObjectAction = "fs:ReadObject"
Expand Down
14 changes: 9 additions & 5 deletions pkg/permissions/permission.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package permissions

const (
fSArnPrefix = "arn:lakefs:fs:::"
fsArnPrefix = "arn:lakefs:fs:::"
authArnPrefix = "arn:lakefs:auth:::"

All = "*"
Expand All @@ -13,19 +13,23 @@ type Permission struct {
}

func RepoArn(repoID string) string {
return fSArnPrefix + "repository/" + repoID
return fsArnPrefix + "repository/" + repoID
}

func StorageNamespace(namespace string) string {
return fsArnPrefix + "namespace/" + namespace
}

func ObjectArn(repoID, key string) string {
return fSArnPrefix + "repository/" + repoID + "/object/" + key
return fsArnPrefix + "repository/" + repoID + "/object/" + key
}

func BranchArn(repoID, branchID string) string {
return fSArnPrefix + "repository/" + repoID + "/branch/" + branchID
return fsArnPrefix + "repository/" + repoID + "/branch/" + branchID
}

func TagArn(repoID, tagID string) string {
return fSArnPrefix + "repository/" + repoID + "/tag/" + tagID
return fsArnPrefix + "repository/" + repoID + "/tag/" + tagID
}

func UserArn(userID string) string {
Expand Down

0 comments on commit 78078b7

Please sign in to comment.