[ 
https://issues.apache.org/jira/browse/CASSANDRA-7557?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14393190#comment-14393190
 ] 

Sam Tunnicliffe commented on CASSANDRA-7557:
--------------------------------------------

I've pushed a preliminary version 
[here|https://github.com/beobal/cassandra/tree/7557] which depends on 
CASSANDRA-9037 (and so transitively on 
https://datastax-oss.atlassian.net/browse/JAVA-700).

These changes touch a lot of classes, but in most cases they're rather trivial. 
Aside from the basic machinery around the {{IResource}} hierarchy and 
granting/revoking of permissions, the bulk of the changes pertain to two things:

* Identifying all of the functions involved in the execution of a CQL statement
* Passing some context information into the preparation step which transforms 
raw terms into their prepared equivalents

The former is to enable us to check at execution time that the logged in user 
has sufficient permissions to execute a given statement. As there are many 
places in the class hierarchy of {{SelectStatement}} and 
{{ModificationStatement}} where a {{FunctionCall}} might be, I added a method 
{{Iterable<Function> getFunctions()}} to a number of classes & interfaces. The 
result is that at the top level, in {{checkAccess()}}, we can extract a list of 
all the functions involved in the execution and check the user's permissions on 
each of them. I took guidance from the existing {{usesFunction}} method, which 
determines prepared statements that need to be removed from cache when a 
function is dropped. Also, I used the code coverage check to verify that I'd 
hit all the places where a function might be used, but it is possible that I 
missed some.

Not all functions get evaluated at execution time however. A pure function with 
only terminal arguments is executed at prepare time, hence the addition of the 
{{PreparationContext}} argument to {{Term.Raw#prepare}}. This is (currently) 
just a wrapper around a {{ClientState}} instance which enables us to do the 
permissions check before executing terminal functions (in 
{{FunctionCall.Raw#execute}}). The unfortunate effect of this is that it 
cascades down to every {{Term.Raw}} implementation, most of which currently do 
nothing with it. The only alternative I could come up with was to defer 
execution of terminal functions depending on the configured {{IAuthorizer}}, 
but tbh that seemed worse.

h4.Permissions

This introduces a new {{EXECUTE}} permission, which is fairly self explanatory. 
Aside from the obvious though, {{EXECUTE}} is also required on a scalar 
function when defining it as either the state or final function in an 
aggregate. So to run 
{code}
CREATE AGGREGATE ks.aggregate_func(int)
SFUNC state_func
STYPE int
FINALFUNCTION final_func
INITCOND 0
{code}
would require {{EXECUTE}} on both {{state_func}} and {{final_func}}. 

In addition, any user who actually run a statement using {{aggregate_func}} 
will require {{EXECUTE}} on {{state_func}}, {{final_func}} and 
{{aggregate_func}}.

The exception to this is that native functions, those in the {{system}} 
keyspace do not require {{EXECUTE}}. So {{token()}}, {{now()}}, {{uuid}} and 
{{timeuuid}} functions, plus the various type conversion functions 
{{Xasblob()}} and {{blobasX()}} remain available by all users.

h4.FunctionResource

In order to apply permissions to UDFs and Aggregates, I've added a new 
{{IResource}} implementation, {{FunctionResource}}

The hierarchy for {{FunctionResource}} looks like:
{code}
 <all functions>                   - root level resource representing all 
functions defined across every keyspace
 <all functions in ks>             - keyspace level resource to apply 
permissions to all functions within a keyspace
 <function ks.function(arg_types)> - a specific function, scoped to a specific 
keyspace
{code}

The Collection types support {{CREATE}}, {{ALTER}}, {{DROP}}, {{AUTHORIZE}} and 
{{EXECUTE}} permissions, while individual functions support
{{ALTER}}, {{DROP}}, {{AUTHORIZE}} and {{EXECUTE}} (i.e. it doesn't make sense 
to grant {{CREATE}} on an existing function).

The fact that we use a distinct resource hierarchy makes it possible seperate 
{{CREATE TABLE|INDEX}} privileges from {{CREATE FUNCTION|AGGREGATE}}. In turn, 
this has a couple of effects.

* {{CREATE}} on a keyspace (or {{ALL KEYSPACES}}) is no longer sufficient to 
create new functions/aggregates.
* The perms automatically granted to creators of db objects (CASSANDRA-7216) 
have been extended to include the new {{FunctionResource}} representing all 
functions in that keyspace. The upshot is that if you create a keyspace, you 
have rights to create functions in it (and/or grant those rights to other 
roles).

h4.Syntax
{code:title=Grant/Revoke permissions on a specific function}
        GRANT EXECUTE ON test_ks.some_func(int, int, text) TO my_role
        REVOKE EXECUTE ON test_ks.some_func(int, int, text) FROM my_role
{code}

{code:title=Grant/Revoke permissions on all functions in a specific keyspace}
        GRANT EXECUTE ON ALL FUNCTIONS IN KEYSPACE test_ks TO my_role
        REVOKE EXECUTE ON ALL FUNCTIONS IN KEYSPACE test_ks FROM my_role
{code}

{code:title=Grant/Revoke permissions on all functions across all keyspaces 
(excluding system)}
        GRANT EXECUTE ON ALL FUNCTIONS TO my_role
        REVOKE EXECUTE ON ALL FUNCTIONS FROM my_role
{code}

h4.Tests
There is some degree of duplication between the 2 unit tests included in the 
patch, however I think it can be justified. 
{{UFIdentificationTest}} only checks that collection of {{Function}} objects 
returned by {{CQLStatement.getFunction}} matches expectations. This is intended 
to verify that the various subcomponents of the statement ({{Operation}}, 
{{ColumnCondition}}, {{Term}}, {{Restriction}}, {{RestrictionSet}}, 
{{Selection}}, {{Selector}}, {{SelectorFactory}} etc) properly report any 
functions they use. As mentioned above, purely terminal functions are resolved 
& executed during preparation, so those are not included in the output of 
{{getFunctions}} and {{UFAuthTest}} includes tests which do exercise this path. 

tl;dr it's quicker and simpler to add tests for the various permutations to 
{{UFIdentificationTest}}, but it is less comprehensive. 

I've also added higher level dtests in 
[this|https://github.com/beobal/cassandra-dtest/tree/7557] branch. Those are 
all passing (along with the existing auth & cql tests), but I want to also 
refactor {{auth_test.py}} and {{auth_roles_test.py}}, so I'll open a PR when 
I've done that.

> User permissions for UDFs
> -------------------------
>
>                 Key: CASSANDRA-7557
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-7557
>             Project: Cassandra
>          Issue Type: Sub-task
>          Components: Core
>            Reporter: Tyler Hobbs
>            Assignee: Sam Tunnicliffe
>              Labels: client-impacting, cql, udf
>             Fix For: 3.0
>
>
> We probably want some new permissions for user defined functions.  Most 
> RDBMSes split function permissions roughly into {{EXECUTE}} and 
> {{CREATE}}/{{ALTER}}/{{DROP}} permissions.



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to