Skip to content

Commit

Permalink
Add file group provider
Browse files Browse the repository at this point in the history
  • Loading branch information
dain committed Sep 28, 2020
1 parent d148de2 commit 5961d29
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 0 deletions.
1 change: 1 addition & 0 deletions presto-docs/src/main/sphinx/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Security
security/cli
security/ldap
security/password-file
security/group-file
security/salesforce
security/user-mapping
security/tls
Expand Down
42 changes: 42 additions & 0 deletions presto-docs/src/main/sphinx/security/group-file.rst
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,4 +34,12 @@ public Iterable<PasswordAuthenticatorFactory> getPasswordAuthenticatorFactories(
.add(new SalesforceAuthenticatorFactory())
.build();
}

@Override
public Iterable<GroupProviderFactory> getGroupProviderFactories()
{
return ImmutableList.<GroupProviderFactory>builder()
.add(new FileGroupProviderFactory())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Function<String, Set<String>>> userGroupSupplier;

@Inject
public FileGroupProvider(FileGroupConfig config)
{
File file = config.getGroupFile();

userGroupSupplier = memoizeWithExpiration(
() -> loadGroupFile(file),
config.getRefreshPeriod().toMillis(),
MILLISECONDS);
}

@Override
public Set<String> getGroups(String user)
{
requireNonNull(user, "user is null");
return userGroupSupplier.get().apply(user);
}

private static Function<String, Set<String>> loadGroupFile(File file)
{
Map<String, Set<String>> groups = loadGroupFile(readGroupFile(file));
return user -> groups.getOrDefault(user, ImmutableSet.of());
}

private static Map<String, Set<String>> loadGroupFile(List<String> lines)
{
Multimap<String, String> 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<String> 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<String> readGroupFile(File file)
{
try {
return Files.readAllLines(file.toPath());
}
catch (IOException e) {
throw new PrestoException(CONFIGURATION_UNAVAILABLE, "Failed to read group file: " + file, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> properties = new ImmutableMap.Builder<String, String>()
.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);
}
}
Loading

0 comments on commit 5961d29

Please sign in to comment.