Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add table functions support to File-based access control #13713

Merged
merged 1 commit into from
Oct 22, 2022

Conversation

huberty89
Copy link
Contributor

@huberty89 huberty89 commented Aug 17, 2022

Description

This PR adds File-based access control support for table functions.
Before this PR with File-based access control enabled every table functions was denied.

By default every rule kind (access to catalogs, schemas, tables) is allow when user does not specify any rule.
The same behavior is also here. After upgrading Trino to newer version and without making any changes to access rules, access to every table function will change and by default it will be allow. To restore previous behavior user needs to add:

{
    "functions": [
        {
            "function_kinds": [
                "SCALAR",
                "AGGREGATE",
                "WINDOW"
            ],
            "privileges": [
                "EXECUTE",
                "GRANT_EXECUTE"
            ]
        },
        {
            "function_kinds": [
                "TABLE"
            ],
            "privileges": []
        }
    ]
}

Examples System-level access control file

  1. Allow postgresql.system.query for admin user.
{
    "functions": [
        {
            "function_kinds": [
                "SCALAR",
                "AGGREGATE",
                "WINDOW"
            ],
            "privileges": [
                "EXECUTE",
                "GRANT_EXECUTE"
            ]
        },
        {
            "user": "admin",
            "catalog": "postgresql",
            "schema": "system",
            "function_kinds": [
                "TABLE"
            ],
            "function": "query",
            "privileges": [
                "EXECUTE",
                "GRANT_EXECUTE"
            ]
        }
    ]
}
  1. Allow query table function from every catalog & schema for admin user.
{
    "functions": [
        {
            "function_kinds": [
                "SCALAR",
                "AGGREGATE",
                "WINDOW"
            ],
            "privileges": [
                "EXECUTE",
                "GRANT_EXECUTE"
            ]
        },
        {
            "user": "admin",
            "function_kinds": [
                "TABLE"
            ],
            "function": "query",
            "privileges": [
                "EXECUTE",
                "GRANT_EXECUTE"
            ]
        }
    ]
}
  1. Deny mysql.system.query for anyone.
{
    "functions": [
        {
            "function_kinds": [
                "SCALAR",
                "AGGREGATE",
                "WINDOW"
            ],
            "privileges": [
                "EXECUTE",
                "GRANT_EXECUTE"
            ]
        },
        {
            "catalog": "mysql",
            "schema": "system",
            "function_kinds": [
                "TABLE"
            ],
            "function": "query",
            "privileges": []
        }
    ]
}
  • Possible privileges values: EXECUTE and GRANT_EXECUTE - the second one allows to execute a function from SECURITY DEFINER view
  • Possible function_kinds values: SCALAR, AGGREGATE, WINDOW and TABLES, it's required to provide at least one value, also when user define a rule for AGGREGATE, SCALAR or WINDOW function, catalog and schema are disallowed

Documentation

( ) No documentation is needed.
( ) Sufficient documentation is included in this PR.
(x) Documentation PR is available with #14774.
( ) Documentation issue #issuenumber is filed, and can be handled later.

Release notes

( ) No release notes entries required.
(x) Release notes entries required with the following suggested text:

# Section
* Add support for PTF in File-based access control. ({issue}`13713`)

@cla-bot
Copy link

cla-bot bot commented Aug 17, 2022

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Hubert Łojek.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 17, 2022

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Hubert Łojek.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

Copy link
Member

@kokosing kokosing left a comment

Choose a reason for hiding this comment

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

I think you can continue with testing.

looks good to me

@@ -273,6 +284,7 @@ private static SystemAccessControl create(String configFileName)
.setTableRules(rules.getTableRules().orElse(ImmutableList.of(CatalogTableAccessControlRule.ALLOW_ALL)))
.setSessionPropertyRules(rules.getSessionPropertyRules().orElse(ImmutableList.of(SessionPropertyAccessControlRule.ALLOW_ALL)))
.setCatalogSessionPropertyRules(rules.getCatalogSessionPropertyRules().orElse(ImmutableList.of(CatalogSessionPropertyAccessControlRule.ALLOW_ALL)))
.setTableFunctionRules(rules.getTableFunctionRules().orElse(ImmutableList.of(CatalogTableFunctionAccessControlRule.ALLOW_ALL)))
Copy link
Member

Choose a reason for hiding this comment

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

This is breaking change. Previously it was denied by default.

However I think it is safe to go this way as it is coherent with other access control checks here and so far I think nobody used file based access control to use table functions.

CC: @kasiafi @dain

@@ -953,7 +965,7 @@ public void checkCanExecuteFunction(SystemSecurityContext systemSecurityContext,
@Override
public void checkCanExecuteFunction(SystemSecurityContext systemSecurityContext, FunctionKind functionKind, CatalogSchemaRoutineName functionName)
{
if (functionKind == TABLE) {
if (functionKind == TABLE && !checkTableFunctionPermission(systemSecurityContext, functionName)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

May be that's good enough for table functions, but IIRC the eventual goal is to use CatalogSchemaRoutineName for functionName for other FunctionKinds too.

The proposed config syntax is too specific to table functions too:

{
    "table_functions": [
        {
            "user": "admin",
            "catalog": "postgresql",
            "schema": "system",
            "table_function": "query"
        }
    ]
}

I think it worth to go with something like

{
    "function": [
        {
            "user": "admin",
            "function_kind": "TABLE",
            "catalog": "postgresql",
            "schema": "system",
            "function": "query"
        }
    ]
}

I'd base code for functions on the code for tables, e.g.

    @Override
    public void checkCanExecuteFunction(SystemSecurityContext systemSecurityContext, FunctionKind functionKind, CatalogSchemaRoutineName function)
    {
        // TODO config to disable for old function kinds
        AccessMode requiredCatalogAccess = FunctionKind.TABLE.equals(functionKind) ? ALL : READ_ONLY;
        if (!checkFunctionPermission(systemSecurityContext, function, requiredCatalogAccess, FunctionPrivilege.SELECT)) {
            denyExecuteFunction(function.toString());
        }
    }

    private boolean checkFunctionPermission(SystemSecurityContext context, CatalogSchemaRoutineName function, AccessMode requiredCatalogAccess, FunctionPrivilege checkPrivilege)
    {

        if (!canAccessCatalog(context, function.getCatalogName(), requiredCatalogAccess)) {
            return false;
        }

        if (INFORMATION_SCHEMA_NAME.equals(function.getSchemaName())) {
            return true;
        }

        Identity identity = context.getIdentity();
        for (CatalogFunctionAccessControlRule rule : functionRules) {
            if (rule.matches(identity.getUser(), identity.getEnabledRoles(), identity.getGroups(), function)) {
                return rule.getPrivileges().contains(checkPrivilege);
            }
        }
        return false;
    }

Copy link
Member

Choose a reason for hiding this comment

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

I think it's a valid concern. If the plan is to have other catalog/schema function kinds in the future then the current syntax would require backward incompatible change or keeping TABLE as the default function kind. Neither of which is desired.

Copy link
Member

Choose a reason for hiding this comment

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

@lukasz-walkiewicz I confirm that the discussed method checkCanExecuteFunction() will be used for all function kinds in the future.

@ssheikin, regarding this part:

AccessMode requiredCatalogAccess = FunctionKind.TABLE.equals(functionKind) ? ALL : READ_ONLY;
        if (!checkFunctionPermission(systemSecurityContext, function, requiredCatalogAccess, FunctionPrivilege.SELECT)) {
            denyExecuteFunction(function.toString());
        }

requiring catalog access ALL is safe, but might be unnecessarily restrictive for some Table Functions (think: sequence generator, random generator etc. which do not access any data even for reading).

I also wonder how it fits the future model where all built-in functions are provided by the Global catalog. The built-in table functions should be easily available to anyone, without granting them that level of access to the catalog.

Ideally, the required catalog access should be set for each table function independently.

What does FunctionPrivilege.SELECT refer to?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ugly initial draft based on file based access control of tables: #13903 . NOT to review. Only for sake of history.

Copy link
Member

Choose a reason for hiding this comment

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

@dain your suggestions would be very helpful here:

  1. What catalog access should we require for table functions? How about the built-in functions provided by the Global catalog?
  2. Could we possibly specify the required access per table function? Some functions read data (query pass-through), some might modify the catalog, and some don't touch anything (sequence).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I add support for every function kind

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kasiafi

Ideally, the required catalog access should be set for each table function independently.

Now I think this makes sense in case of UDFs as well.
I will take a look into it.

Copy link
Contributor Author

@huberty89 huberty89 Sep 7, 2022

Choose a reason for hiding this comment

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

In theory user might prepare UDF which will make some modifications - this is such edge case probably we should not take care of this. Also we do not have an information which catalog would be modified in such UDF.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it's time for that eventually #12544 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

or at least

this is such edge case probably we should not take care of this

.... YET

@ssheikin
Copy link
Contributor

ssheikin commented Sep 6, 2022

This PR resolves #12833

@huberty89 huberty89 force-pushed the fbac-table-function-support branch 2 times, most recently from 1a2553b to a47bfe9 Compare September 6, 2022 19:57
{
this.schemaRules = schemaRules.orElse(ImmutableList.of(SchemaAccessControlRule.ALLOW_ALL));
this.tableRules = tableRules.orElse(ImmutableList.of(TableAccessControlRule.ALLOW_ALL));
this.sessionPropertyRules = sessionPropertyRules.orElse(ImmutableList.of(SessionPropertyAccessControlRule.ALLOW_ALL));
this.functionRules = functionRules.orElse(ImmutableList.of(FunctionAccessControlRule.ALLOW_ALL));
Copy link
Contributor

Choose a reason for hiding this comment

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

Before this PR with File-based access control enabled every table functions was denied.

By default every rule kind (access to catalogs, schemas, tables) is allow when user does not specify any rule.
The same behavior is also here. After upgrading Trino to newer version and without making any changes to access rules, access to every table function will change and by default it will be allow. Is that good behavior? Maybe in case of table functions new default should be deny but this makes those new rules inconsistent with the rest.

Let's discuss this in the separate thread.

Before this PR with File-based access control enabled every table functions was denied.

I think it's a correct implementation, fast one, but which was not really thought that deep to be very compatible with FBAC.

Maybe in case of table functions new default should be deny but this makes those new rules inconsistent with the rest.

I guess access to ptf should obey the same rules as other entities of FBAC do.

By default every rule kind (access to catalogs, schemas, tables) is allow when user does not specify any rule.
The same behavior is also here. After upgrading Trino to newer version and without making any changes to access rules, access to every table function will change and by default it will be allow. Is that good behavior?

ptf were just introduced and with only one built in ptf.Query function. It's questionable if there are cases where FBAC with PTF is used by someone.

Consistency it's a long term thing.

@ksobolew
Copy link
Contributor

I finally got to reviewing this PR ;) As I understand it, it's blocked on adding additional methods to the access control interfaces, right?

Also, tests will be needed.

@huberty89 huberty89 force-pushed the fbac-table-function-support branch 2 times, most recently from dc3ff73 to 8528641 Compare October 12, 2022 12:23
@huberty89
Copy link
Contributor Author

@dain @findepi @kasiafi Would you like to take a look?

@huberty89 huberty89 force-pushed the fbac-table-function-support branch 2 times, most recently from 42f0289 to fb19a4d Compare October 19, 2022 15:15
@huberty89 huberty89 force-pushed the fbac-table-function-support branch 3 times, most recently from 067f379 to 36ac692 Compare October 20, 2022 10:28
@huberty89
Copy link
Contributor Author

I added more real case scenario test TestViewsWithPtfFileBasedSystemAccessControl

@Test
public void testPtfViewsCreatedByAlice()
{
QueryError queryError = executeAsUser("CREATE VIEW blackhole.default.view_ptf_alice_security_definer SECURITY DEFINER AS SELECT * FROM TABLE(mock.system.simple_table_function())", ALICE_USER);
Copy link
Contributor

Choose a reason for hiding this comment

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

inline everywhere except where it is reused.

@huberty89 huberty89 force-pushed the fbac-table-function-support branch 4 times, most recently from 380877c to ce723a3 Compare October 20, 2022 13:31
Copy link
Member

@lukasz-walkiewicz lukasz-walkiewicz left a comment

Choose a reason for hiding this comment

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

LGTM

assertQuerySucceeds(BOB_USER, "CREATE VIEW blackhole.default.view_ptf_bob_security_definer SECURITY DEFINER AS SELECT * FROM TABLE(mock.system.simple_table_function())");
String securityDefinerQuery = "SELECT * FROM blackhole.default.view_ptf_bob_security_definer";
assertAccessDenied(ALICE_USER, securityDefinerQuery, "View owner does not have sufficient privileges: 'bob' cannot grant 'mock\\.system\\.simple_table_function' execution to user 'alice'");
assertQuerySucceeds(BOB_USER, securityDefinerQuery);
Copy link
Member

Choose a reason for hiding this comment

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

That's an interesting behaviour, that Bob himself can actually query the view but no one else can. It's probably because view owner = view invoker.

Copy link
Member

Choose a reason for hiding this comment

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

io.trino.sql.analyzer.StatementAnalyzer.Visitor#analyzeView

@kokosing kokosing merged commit 96d3e52 into trinodb:master Oct 22, 2022
@github-actions github-actions bot added this to the 401 milestone Oct 22, 2022
@ssheikin
Copy link
Contributor

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

6 participants