Skip to content

Commit bc52e9e

Browse files
authored
Merge pull request #3 from ErmiasG/access-control
access control
2 parents 7a8c46e + 1143a6a commit bc52e9e

9 files changed

Lines changed: 328 additions & 26 deletions

Jenkinsfile

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
@Library("jenkins-library@main")
2+
3+
import com.logicalclocks.jenkins.k8s.ImageBuilder
4+
5+
node("local") {
6+
stage('Clone repository') {
7+
checkout scm
8+
}
9+
10+
// Set defaults via env (scripted pipeline doesn't support a declarative `environment` block)
11+
env.ARCH = 'amd64'
12+
env.TRINO_VERSION = ''
13+
env.TAG_PREFIX = 'trino'
14+
env.SERVER_ARTIFACT = 'trino-server'
15+
env.JDKS_PATH = 'core/jdk'
16+
env.SKIP_TESTS = 'false'
17+
env.WORK_DIR = 'core/docker'
18+
19+
stage('Init') {
20+
// use scripted pipeline style
21+
def current = sh(script: "cat \"${env.JDKS_PATH}/current\" | tr -d '\\n\\r'", returnStdout: true).trim()
22+
env.JDK_RELEASE = current
23+
24+
def jdk_download_link = sh(script: "grep '^distributionUrl=' \"${env.JDKS_PATH}/${env.JDK_RELEASE}/${env.ARCH}\" | cut -d'=' -f2-", returnStdout: true).trim()
25+
26+
env.JDK_DOWNLOAD_LINK = jdk_download_link
27+
28+
env.TRINO_VERSION = sh(script: './mvnw -f pom.xml --quiet help:evaluate -Dexpression=project.version -DforceStdout', returnStdout: true).trim()
29+
30+
echo "TRINO_VERSION=${env.TRINO_VERSION}"
31+
echo "JDK_DOWNLOAD_LINK=${env.JDK_DOWNLOAD_LINK}"
32+
}
33+
34+
stage('Maven Build') {
35+
// ensure the wrapper is present and executable
36+
sh "test -f ${env.WORKSPACE}/mvnw || (echo 'mvnw not found' && exit 1)"
37+
sh "chmod +x ${env.WORKSPACE}/mvnw"
38+
39+
sh "wget -O jdk24.tar.gz \"${env.JDK_DOWNLOAD_LINK}\""
40+
sh "mkdir -p ${env.WORKSPACE}/jdk"
41+
sh "tar -xzf jdk24.tar.gz -C ${env.WORKSPACE}/jdk --strip-components=1"
42+
sh "rm jdk24.tar.gz"
43+
44+
sh "JAVA_HOME=${env.WORKSPACE}/jdk ${env.WORKSPACE}/mvnw clean package -DskipTests"
45+
46+
// Archive artifacts
47+
archiveArtifacts artifacts: "core/${env.SERVER_ARTIFACT}/target/${env.SERVER_ARTIFACT}-${env.TRINO_VERSION}.tar.gz", fingerprint: true, allowEmptyArchive: true
48+
archiveArtifacts artifacts: "client/trino-cli/target/trino-cli-${env.TRINO_VERSION}-executable.jar", fingerprint: true, allowEmptyArchive: true
49+
}
50+
51+
stage('Build and push trino') {
52+
withCredentials([usernamePassword(credentialsId: 'a0770738-4ef3-4acc-a6ba-097ee6c85b44', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')]) {
53+
// copy artifacts into workspace
54+
sh """#!/usr/bin/env bash
55+
set -euo pipefail
56+
57+
cp -f \"core/${env.SERVER_ARTIFACT}/target/${env.SERVER_ARTIFACT}-${env.TRINO_VERSION}.tar.gz\" \"${env.WORK_DIR}/\"
58+
cp -f \"client/trino-cli/target/trino-cli-${env.TRINO_VERSION}-executable.jar\" \"${env.WORK_DIR}/trino-cli.jar\"
59+
tar -C \"${env.WORK_DIR}\" -xzf \"${WORK_DIR}/${env.SERVER_ARTIFACT}-${env.TRINO_VERSION}.tar.gz\"
60+
rm -f \"${env.WORK_DIR}/${env.SERVER_ARTIFACT}-${env.TRINO_VERSION}.tar.gz\"
61+
62+
# Ensure the destination does not exist to avoid 'Directory not empty' errors
63+
if [ -d "${env.WORK_DIR}/trino-server" ]; then
64+
echo "Removing existing `trino-server` directory"
65+
rm -rf "${env.WORK_DIR}/trino-server"
66+
fi
67+
68+
mv \"${env.WORK_DIR}/${env.SERVER_ARTIFACT}-${env.TRINO_VERSION}\" \"${WORK_DIR}/trino-server\"
69+
70+
cp -R core/docker/bin \"${env.WORK_DIR}/trino-server\"
71+
# same file core/docker == > WORK_DIR, so no need to copy
72+
# cp -R core/docker/default \"${env.WORK_DIR}/\"
73+
"""
74+
75+
withEnv(["TAG_VERSION=${env.TRINO_VERSION}-${env.ARCH}", "JDK_RELEASE=${env.JDK_RELEASE}", "JDK_DOWNLOAD_LINK=${env.JDK_DOWNLOAD_LINK}", "ARCH=${env.ARCH}"]) {
76+
def builder = new ImageBuilder(this)
77+
def m = readFile "${env.WORKSPACE}/build-manifest.json"
78+
builder.run(m)
79+
}
80+
}
81+
}
82+
}

build-manifest.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[
2+
{
3+
"name": "hopsworks/trino",
4+
"version": "env:TAG_VERSION",
5+
"excludeRegistries": [
6+
"ecr_prod"
7+
],
8+
"variables": [
9+
"JDK_RELEASE",
10+
"JDK_DOWNLOAD_LINK",
11+
"ARCH"
12+
],
13+
"extraDockerArgs": "--build-arg ARCH=\"${ARCH}\" --build-arg JDK_VERSION=\"${JDK_RELEASE}\" --build-arg JDK_DOWNLOAD_LINK=\"${JDK_DOWNLOAD_LINK}\"",
14+
"dockerFile": "core/docker/Dockerfile"
15+
}
16+
]

lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AnyCatalogSchemaPermissionsRule.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@ public AnyCatalogSchemaPermissionsRule(Optional<Pattern> userRegex, Optional<Pat
3737

3838
public boolean match(String user, Set<String> roles, Set<String> groups, String catalogName, String schemaName)
3939
{
40-
return userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
41-
roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
42-
groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
43-
catalogRegex.map(regex -> regex.matcher(catalogName).matches()).orElse(true) &&
44-
schemaRegex.map(regex -> regex.matcher(schemaName).matches()).orElse(true);
40+
// return userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
41+
// roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
42+
// groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
43+
// catalogRegex.map(regex -> regex.matcher(catalogName).matches()).orElse(true) &&
44+
// schemaRegex.map(regex -> regex.matcher(schemaName).matches()).orElse(true);
45+
46+
ReplacePatternMatcher replacePatternMatcher = new ReplacePatternMatcher(userRegex, roleRegex, groupRegex, user, roles, groups);
47+
48+
return replacePatternMatcher.matchCatalogAndSchema(catalogRegex, catalogName, schemaRegex, schemaName);
4549
}
4650

4751
@Override

lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AnySchemaPermissionsRule.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ public AnySchemaPermissionsRule(Optional<Pattern> userRegex, Optional<Pattern> r
3535

3636
public boolean match(String user, Set<String> roles, Set<String> groups, String schemaName)
3737
{
38-
return userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
39-
roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
40-
groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
41-
schemaRegex.map(regex -> regex.matcher(schemaName).matches()).orElse(true);
38+
// return userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
39+
// roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
40+
// groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
41+
// schemaRegex.map(regex -> regex.matcher(schemaName).matches()).orElse(true);
42+
43+
ReplacePatternMatcher replacePatternMatcher = new ReplacePatternMatcher(userRegex, roleRegex, groupRegex, user, roles, groups);
44+
45+
return replacePatternMatcher.matchSchema(schemaRegex, schemaName);
4246
}
4347

4448
@Override

lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/CatalogAccessControlRule.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,16 @@ public CatalogAccessControlRule(
6060

6161
public Optional<AccessMode> match(String user, Set<String> roles, Set<String> groups, String catalog)
6262
{
63-
if (userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
64-
roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
65-
groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
66-
catalogRegex.map(regex -> regex.matcher(catalog).matches()).orElse(true)) {
63+
// if (userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
64+
// roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
65+
// groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
66+
// catalogRegex.map(regex -> regex.matcher(catalog).matches()).orElse(true)) {
67+
// return Optional.of(accessMode);
68+
// }
69+
70+
ReplacePatternMatcher replacePatternMatcher = new ReplacePatternMatcher(userRegex, roleRegex, groupRegex, user, roles, groups);
71+
72+
if (replacePatternMatcher.matchCatalog(catalogRegex, catalog)) {
6773
return Optional.of(accessMode);
6874
}
6975
return Optional.empty();

lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/QueryAccessRule.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,16 @@ public QueryAccessRule(
5959

6060
public Optional<Set<AccessMode>> match(String user, Set<String> roles, Set<String> groups, Optional<String> queryOwner)
6161
{
62-
if (userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
63-
roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
64-
groupRegex.map(regex -> groups.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
65-
((queryOwner.isEmpty() && queryOwnerRegex.isEmpty()) || (queryOwner.isPresent() && queryOwnerRegex.map(regex -> regex.matcher(queryOwner.get()).matches()).orElse(true)))) {
62+
// if (userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
63+
// roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
64+
// groupRegex.map(regex -> groups.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
65+
// ((queryOwner.isEmpty() && queryOwnerRegex.isEmpty()) || (queryOwner.isPresent() && queryOwnerRegex.map(regex -> regex.matcher(queryOwner.get()).matches()).orElse(true)))) {
66+
// return Optional.of(allow);
67+
// }
68+
69+
ReplacePatternMatcher replacePatternMatcher = new ReplacePatternMatcher(userRegex, roleRegex, groupRegex, user, roles, groups);
70+
71+
if (replacePatternMatcher.matchQueryAccessRule(queryOwnerRegex, queryOwner)) {
6672
return Optional.of(allow);
6773
}
6874
return Optional.empty();
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.plugin.base.security;
15+
16+
import io.trino.spi.TrinoException;
17+
18+
import java.util.Optional;
19+
import java.util.Set;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
import static io.trino.spi.StandardErrorCode.CONFIGURATION_INVALID;
24+
25+
public class ReplacePatternMatcher
26+
{
27+
private final Optional<Pattern> userRegex;
28+
private final Optional<Pattern> roleRegex;
29+
private final Optional<Pattern> groupRegex;
30+
31+
private final String user;
32+
private final Set<String> roles;
33+
private final Set<String> groups;
34+
35+
private final Optional<Matcher> userMatcher;
36+
private final Optional<Matcher> roleMatcher;
37+
private final Optional<Matcher> groupMatcher;
38+
39+
public ReplacePatternMatcher(Optional<Pattern> userRegex, Optional<Pattern> roleRegex, Optional<Pattern> groupRegex, String user, Set<String> roles, Set<String> groups)
40+
{
41+
this.userRegex = userRegex;
42+
this.roleRegex = roleRegex;
43+
this.groupRegex = groupRegex;
44+
45+
this.user = user;
46+
this.roles = roles;
47+
this.groups = groups;
48+
49+
this.userMatcher = userRegex.map(regex -> regex.matcher(user));
50+
this.roleMatcher = roleRegex.flatMap(regex -> roles.stream().map(regex::matcher).filter(Matcher::matches).findAny());
51+
this.groupMatcher = groupRegex.flatMap(regex -> groups.stream().map(regex::matcher).filter(Matcher::matches).findAny());
52+
}
53+
54+
private boolean match()
55+
{
56+
return userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
57+
roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
58+
groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true);
59+
}
60+
61+
private boolean matchCatalogInternal(final Optional<Pattern> catalogRegex, String catalog)
62+
{
63+
validateMatchers(userMatcher, roleMatcher, groupMatcher);
64+
65+
Optional<Pattern> userReplacedCatalogPattern = replaceRegex(userMatcher, catalogRegex, "catalog", "user");
66+
Optional<Pattern> roleReplacedCatalogPattern = replaceRegex(roleMatcher, catalogRegex, "catalog", "role");
67+
Optional<Pattern> groupReplacedCatalogPattern = replaceRegex(groupMatcher, catalogRegex, "catalog", "group");
68+
69+
return matchPattern(userReplacedCatalogPattern, catalog) || matchPattern(roleReplacedCatalogPattern, catalog)
70+
|| matchPattern(groupReplacedCatalogPattern, catalog);
71+
}
72+
73+
public boolean matchCatalog(final Optional<Pattern> catalogRegex, String catalog)
74+
{
75+
return match() && matchCatalogInternal(catalogRegex, catalog);
76+
}
77+
78+
private boolean matchSchemaInternal(final Optional<Pattern> schemaRegex, String schema)
79+
{
80+
validateMatchers(userMatcher, roleMatcher, groupMatcher);
81+
82+
Optional<Pattern> userReplacedSchemaPattern = replaceRegex(userMatcher, schemaRegex, "schema", "user");
83+
Optional<Pattern> roleReplacedSchemaPattern = replaceRegex(roleMatcher, schemaRegex, "schema", "role");
84+
Optional<Pattern> groupReplacedSchemaPattern = replaceRegex(groupMatcher, schemaRegex, "schema", "group");
85+
86+
return matchPattern(userReplacedSchemaPattern, schema) || matchPattern(roleReplacedSchemaPattern, schema)
87+
|| matchPattern(groupReplacedSchemaPattern, schema);
88+
}
89+
90+
public boolean matchSchema(final Optional<Pattern> schemaRegex, String schema)
91+
{
92+
return match() && matchSchemaInternal(schemaRegex, schema);
93+
}
94+
95+
public boolean matchCatalogAndSchema(final Optional<Pattern> catalogRegex, String catalog, final Optional<Pattern> schemaRegex, String schema)
96+
{
97+
return match() && matchCatalogInternal(catalogRegex, catalog) && matchSchemaInternal(schemaRegex, schema);
98+
}
99+
100+
private boolean matchTableInternal(final Optional<Pattern> tableRegex, String table)
101+
{
102+
validateMatchers(userMatcher, roleMatcher, groupMatcher);
103+
104+
Optional<Pattern> userReplacedTablePattern = replaceRegex(userMatcher, tableRegex, "table", "user");
105+
Optional<Pattern> roleReplacedTablePattern = replaceRegex(roleMatcher, tableRegex, "table", "role");
106+
Optional<Pattern> groupReplacedTablePattern = replaceRegex(groupMatcher, tableRegex, "table", "group");
107+
108+
return matchPattern(userReplacedTablePattern, table) || matchPattern(roleReplacedTablePattern, table)
109+
|| matchPattern(groupReplacedTablePattern, table);
110+
}
111+
112+
public boolean matchTable(final Optional<Pattern> tableRegex, String table)
113+
{
114+
return match() && matchTableInternal(tableRegex, table);
115+
}
116+
117+
public boolean matchSchemaAndTable(final Optional<Pattern> schemaRegex, String schema, final Optional<Pattern> tableRegex, String table)
118+
{
119+
return match() && matchSchemaInternal(schemaRegex, schema) && matchTableInternal(tableRegex, table);
120+
}
121+
122+
public boolean matchQueryAccessRule(final Optional<Pattern> queryOwnerRegex, Optional<String> queryOwner)
123+
{
124+
if (queryOwner.isEmpty() && queryOwnerRegex.isEmpty()) {
125+
return true;
126+
}
127+
128+
validateMatchers(userMatcher, roleMatcher, groupMatcher);
129+
130+
Optional<Pattern> userReplacedQueryAccessRulePattern = replaceRegex(userMatcher, queryOwnerRegex, "query_owner", "user");
131+
Optional<Pattern> roleReplacedQueryAccessRulePattern = replaceRegex(roleMatcher, queryOwnerRegex, "query_owner", "role");
132+
Optional<Pattern> groupReplacedQueryAccessRulePattern = replaceRegex(groupMatcher, queryOwnerRegex, "query_owner", "group");
133+
134+
return match() && queryOwner.isPresent() && (matchPattern(userReplacedQueryAccessRulePattern, queryOwner.get())
135+
|| matchPattern(roleReplacedQueryAccessRulePattern, queryOwner.get()) || matchPattern(groupReplacedQueryAccessRulePattern, queryOwner.get()));
136+
}
137+
138+
// check if more than one matcher is used to replace patterns
139+
private void validateMatchers(Optional<Matcher> userMatcher, Optional<Matcher> roleMatcher, Optional<Matcher> groupMatcher)
140+
{
141+
if ((userMatcher.isPresent() && userMatcher.get().matches() && userMatcher.get().groupCount() > 0 ? 1 : 0) +
142+
(roleMatcher.isPresent() && roleMatcher.get().matches() && roleMatcher.get().groupCount() > 0 ? 1 : 0) +
143+
(groupMatcher.isPresent() && groupMatcher.get().matches() && groupMatcher.get().groupCount() > 0 ? 1 : 0) > 1) {
144+
throw new TrinoException(CONFIGURATION_INVALID, "Multiple matchers that contain capturing groups are used" +
145+
" to replace patterns. This may lead to unexpected results.");
146+
}
147+
}
148+
149+
private Optional<Pattern> replaceRegex(Optional<Matcher> matcher, Optional<Pattern> patternToReplace, String toReplace, String capturingGroup)
150+
{
151+
if (matcher.isEmpty()) {
152+
return patternToReplace;
153+
}
154+
if (patternToReplace.isPresent() && patternToReplace.get().pattern().matches(".*\\$\\d+.*")) {
155+
if (matcher.get().matches() && matcher.get().groupCount() > 0) {
156+
StringBuilder stringBuilder = new StringBuilder();
157+
try {
158+
matcher.get().appendReplacement(stringBuilder, patternToReplace.get().pattern());
159+
}
160+
catch (IndexOutOfBoundsException e) {
161+
throw new TrinoException(CONFIGURATION_INVALID,
162+
toReplace + " in replace pattern refers to a capturing group that does not exist in " + capturingGroup, e);
163+
}
164+
return Optional.of(Pattern.compile(stringBuilder.toString()));
165+
}
166+
}
167+
return patternToReplace;
168+
}
169+
170+
private boolean matchPattern(Optional<Pattern> pattern, String value)
171+
{
172+
return pattern.map(regex -> regex.matcher(value).matches()).orElse(true);
173+
}
174+
}

lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/SchemaAccessControlRule.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,16 @@ public SchemaAccessControlRule(
5454

5555
public Optional<Boolean> match(String user, Set<String> roles, Set<String> groups, String schema)
5656
{
57-
if (userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
58-
roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
59-
groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
60-
schemaRegex.map(regex -> regex.matcher(schema).matches()).orElse(true)) {
57+
// if (userRegex.map(regex -> regex.matcher(user).matches()).orElse(true) &&
58+
// roleRegex.map(regex -> roles.stream().anyMatch(role -> regex.matcher(role).matches())).orElse(true) &&
59+
// groupRegex.map(regex -> groups.stream().anyMatch(group -> regex.matcher(group).matches())).orElse(true) &&
60+
// schemaRegex.map(regex -> regex.matcher(schema).matches()).orElse(true)) {
61+
// return Optional.of(owner);
62+
// }
63+
64+
ReplacePatternMatcher replacePatternMatcher = new ReplacePatternMatcher(userRegex, roleRegex, groupRegex, user, roles, groups);
65+
66+
if (replacePatternMatcher.matchSchema(schemaRegex, schema)) {
6167
return Optional.of(owner);
6268
}
6369
return Optional.empty();

0 commit comments

Comments
 (0)