From 5961d29d3dbd0010d14ec85592ff0071ad73c42e Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sun, 30 Aug 2020 12:49:54 -0700 Subject: [PATCH] Add file group provider --- presto-docs/src/main/sphinx/security.rst | 1 + .../src/main/sphinx/security/group-file.rst | 42 +++++++ .../password/PasswordAuthenticatorPlugin.java | 10 ++ .../plugin/password/file/FileGroupConfig.java | 62 ++++++++++ .../password/file/FileGroupProvider.java | 110 ++++++++++++++++++ .../file/FileGroupProviderFactory.java | 52 +++++++++ .../password/file/TestFileGroupConfig.java | 58 +++++++++ .../password/file/TestFileGroupProvider.java | 54 +++++++++ .../src/test/resources/group.txt | 4 + 9 files changed, 393 insertions(+) create mode 100644 presto-docs/src/main/sphinx/security/group-file.rst create mode 100644 presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupConfig.java create mode 100644 presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProvider.java create mode 100644 presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProviderFactory.java create mode 100644 presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupConfig.java create mode 100644 presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupProvider.java create mode 100644 presto-password-authenticators/src/test/resources/group.txt diff --git a/presto-docs/src/main/sphinx/security.rst b/presto-docs/src/main/sphinx/security.rst index 95bedff92f234..37e60d9a6a22f 100644 --- a/presto-docs/src/main/sphinx/security.rst +++ b/presto-docs/src/main/sphinx/security.rst @@ -9,6 +9,7 @@ Security security/cli security/ldap security/password-file + security/group-file security/salesforce security/user-mapping security/tls diff --git a/presto-docs/src/main/sphinx/security/group-file.rst b/presto-docs/src/main/sphinx/security/group-file.rst new file mode 100644 index 0000000000000..a462983d254f0 --- /dev/null +++ b/presto-docs/src/main/sphinx/security/group-file.rst @@ -0,0 +1,42 @@ +========================= +File Based Group Provider +========================= + +Presto can map user names onto groups for easier access control and +resource group management. Group file resolves group membership using +a file on the coordinator. + +Group File Configuration +------------------------ + +Enable group file by creating an ``etc/group-provider.properties`` +file on the coordinator: + +.. code-block:: none + + group-provider.name=file + file.group-file=/path/to/group.txt + +The following configuration properties are available: + +==================================== ============================================== +Property Description +==================================== ============================================== +``file.group-file`` Path of the group file. + +``file.refresh-period`` How often to reload the group file. + Defaults to ``5s``. +==================================== ============================================== + +Group Files +----------- + +File Format +^^^^^^^^^^^ + +The group file contains a list of groups and members, one per line, +separated by a colon. Users are separated by a comma. + +.. code-block:: none + + group_name:user_1,user_2,user_3 diff --git a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/PasswordAuthenticatorPlugin.java b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/PasswordAuthenticatorPlugin.java index 22791e4bea185..86a3e776e48fa 100644 --- a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/PasswordAuthenticatorPlugin.java +++ b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/PasswordAuthenticatorPlugin.java @@ -15,9 +15,11 @@ import com.google.common.collect.ImmutableList; import io.prestosql.plugin.password.file.FileAuthenticatorFactory; +import io.prestosql.plugin.password.file.FileGroupProviderFactory; import io.prestosql.plugin.password.ldap.LdapAuthenticatorFactory; import io.prestosql.plugin.password.salesforce.SalesforceAuthenticatorFactory; import io.prestosql.spi.Plugin; +import io.prestosql.spi.security.GroupProviderFactory; import io.prestosql.spi.security.PasswordAuthenticatorFactory; public class PasswordAuthenticatorPlugin @@ -32,4 +34,12 @@ public Iterable getPasswordAuthenticatorFactories( .add(new SalesforceAuthenticatorFactory()) .build(); } + + @Override + public Iterable getGroupProviderFactories() + { + return ImmutableList.builder() + .add(new FileGroupProviderFactory()) + .build(); + } } diff --git a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupConfig.java b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupConfig.java new file mode 100644 index 0000000000000..b9e45d9ec9167 --- /dev/null +++ b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupConfig.java @@ -0,0 +1,62 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.password.file; + +import io.airlift.configuration.Config; +import io.airlift.configuration.ConfigDescription; +import io.airlift.configuration.validation.FileExists; +import io.airlift.units.Duration; +import io.airlift.units.MinDuration; + +import javax.validation.constraints.NotNull; + +import java.io.File; + +import static java.util.concurrent.TimeUnit.SECONDS; + +public class FileGroupConfig +{ + private File groupFile; + private Duration refreshPeriod = new Duration(5, SECONDS); + + @NotNull + @FileExists + public File getGroupFile() + { + return groupFile; + } + + @Config("file.group-file") + @ConfigDescription("Location of the file that provides user group membership") + public FileGroupConfig setGroupFile(File groupFile) + { + this.groupFile = groupFile; + return this; + } + + @NotNull + @MinDuration("1ms") + public Duration getRefreshPeriod() + { + return refreshPeriod; + } + + @Config("file.refresh-period") + @ConfigDescription("How often to reload the group file") + public FileGroupConfig setRefreshPeriod(Duration refreshPeriod) + { + this.refreshPeriod = refreshPeriod; + return this; + } +} diff --git a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProvider.java b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProvider.java new file mode 100644 index 0000000000000..a1866b61bd7a9 --- /dev/null +++ b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProvider.java @@ -0,0 +1,110 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.password.file; + +import com.google.common.base.Splitter; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import io.prestosql.spi.PrestoException; +import io.prestosql.spi.security.GroupProvider; + +import javax.inject.Inject; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import static com.google.common.base.Suppliers.memoizeWithExpiration; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.prestosql.spi.StandardErrorCode.CONFIGURATION_INVALID; +import static io.prestosql.spi.StandardErrorCode.CONFIGURATION_UNAVAILABLE; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +public class FileGroupProvider + implements GroupProvider +{ + private static final Splitter LINE_SPLITTER = Splitter.on(":").limit(2).trimResults(); + private static final Splitter GROUP_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); + + private final Supplier>> userGroupSupplier; + + @Inject + public FileGroupProvider(FileGroupConfig config) + { + File file = config.getGroupFile(); + + userGroupSupplier = memoizeWithExpiration( + () -> loadGroupFile(file), + config.getRefreshPeriod().toMillis(), + MILLISECONDS); + } + + @Override + public Set getGroups(String user) + { + requireNonNull(user, "user is null"); + return userGroupSupplier.get().apply(user); + } + + private static Function> loadGroupFile(File file) + { + Map> groups = loadGroupFile(readGroupFile(file)); + return user -> groups.getOrDefault(user, ImmutableSet.of()); + } + + private static Map> loadGroupFile(List lines) + { + Multimap userGroups = HashMultimap.create(); + for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) { + String line = lines.get(lineNumber - 1).trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + List parts = LINE_SPLITTER.splitToList(line); + if (parts.size() != 2) { + throw invalidFile(lineNumber, "Expected two parts for group and users", null); + } + String group = parts.get(0); + GROUP_SPLITTER.splitToStream(parts.get(1)) + .forEach(user -> userGroups.put(user, group)); + } + return userGroups.asMap().entrySet().stream() + .collect(toImmutableMap(Entry::getKey, entry -> ImmutableSet.copyOf(entry.getValue()))); + } + + private static RuntimeException invalidFile(int lineNumber, String message, Throwable cause) + { + return new PrestoException(CONFIGURATION_INVALID, format("Error in group file line %s: %s", lineNumber, message), cause); + } + + private static List readGroupFile(File file) + { + try { + return Files.readAllLines(file.toPath()); + } + catch (IOException e) { + throw new PrestoException(CONFIGURATION_UNAVAILABLE, "Failed to read group file: " + file, e); + } + } +} diff --git a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProviderFactory.java b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProviderFactory.java new file mode 100644 index 0000000000000..bc6d976047d71 --- /dev/null +++ b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/file/FileGroupProviderFactory.java @@ -0,0 +1,52 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.password.file; + +import com.google.inject.Injector; +import com.google.inject.Scopes; +import io.airlift.bootstrap.Bootstrap; +import io.prestosql.spi.security.GroupProvider; +import io.prestosql.spi.security.GroupProviderFactory; + +import java.util.Map; + +import static io.airlift.configuration.ConfigBinder.configBinder; + +public class FileGroupProviderFactory + implements GroupProviderFactory +{ + @Override + public String getName() + { + return "file"; + } + + @Override + public GroupProvider create(Map config) + { + Bootstrap app = new Bootstrap( + binder -> { + configBinder(binder).bindConfig(FileGroupConfig.class); + binder.bind(FileGroupProvider.class).in(Scopes.SINGLETON); + }); + + Injector injector = app + .strictConfig() + .doNotInitializeLogging() + .setRequiredConfigurationProperties(config) + .initialize(); + + return injector.getInstance(FileGroupProvider.class); + } +} diff --git a/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupConfig.java b/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupConfig.java new file mode 100644 index 0000000000000..d2c8d3ee55911 --- /dev/null +++ b/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupConfig.java @@ -0,0 +1,58 @@ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.password.file; + +import com.google.common.collect.ImmutableMap; +import io.airlift.units.Duration; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; +import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; +import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class TestFileGroupConfig +{ + @Test + public void testDefault() + { + assertRecordedDefaults(recordDefaults(FileGroupConfig.class) + .setGroupFile(null) + .setRefreshPeriod(new Duration(5, SECONDS))); + } + + @Test + public void testExplicitConfig() + throws IOException + { + Path groupFile = Files.createTempFile(null, null); + + Map properties = new ImmutableMap.Builder() + .put("file.group-file", groupFile.toString()) + .put("file.refresh-period", "42s") + .build(); + + FileGroupConfig expected = new FileGroupConfig() + .setGroupFile(groupFile.toFile()) + .setRefreshPeriod(new Duration(42, SECONDS)); + + assertFullMapping(properties, expected); + } +} diff --git a/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupProvider.java b/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupProvider.java new file mode 100644 index 0000000000000..069c59ad83dea --- /dev/null +++ b/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/file/TestFileGroupProvider.java @@ -0,0 +1,54 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.password.file; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; +import io.prestosql.spi.security.GroupProvider; +import org.testng.annotations.Test; + +import java.io.File; + +import static org.testng.Assert.assertEquals; + +public class TestFileGroupProvider +{ + @Test + public void test() + throws Exception + { + File groupFile = new File(Resources.getResource("group.txt").toURI().getPath()); + GroupProvider groupProvider = new FileGroupProvider(new FileGroupConfig().setGroupFile(groupFile)); + assertGroupProvider(groupProvider); + } + + @Test + public void testGroupProviderFactory() + throws Exception + { + File groupFile = new File(Resources.getResource("group.txt").toURI().getPath()); + GroupProvider groupProvider = new FileGroupProviderFactory() + .create(ImmutableMap.of("file.group-file", groupFile.getAbsolutePath())); + assertGroupProvider(groupProvider); + } + + private static void assertGroupProvider(GroupProvider groupProvider) + { + assertEquals(groupProvider.getGroups("user_1"), ImmutableSet.of("group_a", "group_b")); + assertEquals(groupProvider.getGroups("user_2"), ImmutableSet.of("group_a")); + assertEquals(groupProvider.getGroups("user_3"), ImmutableSet.of("group_b")); + assertEquals(groupProvider.getGroups("unknown"), ImmutableSet.of()); + } +} diff --git a/presto-password-authenticators/src/test/resources/group.txt b/presto-password-authenticators/src/test/resources/group.txt new file mode 100644 index 0000000000000..618f2db50dee1 --- /dev/null +++ b/presto-password-authenticators/src/test/resources/group.txt @@ -0,0 +1,4 @@ +# Comment +group_a:user_1,user_2 +group_b: user_3, user_1, +