Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.facebook.presto.spi.connector.ConnectorSplitManager;
import com.facebook.presto.spi.connector.ConnectorSplitManager.SplitSchedulingContext;
import com.facebook.presto.spi.connector.ConnectorTransactionHandle;
import com.facebook.presto.spi.security.ConnectorIdentity;
import com.facebook.presto.testing.TestingConnectorContext;
import com.facebook.presto.testing.TestingConnectorSession;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -80,6 +81,7 @@ public class TestCassandraConnector
private static final Date DATE = new Date();
private static final ConnectorSession SESSION = new TestingConnectorSession(
"user",
new ConnectorIdentity("user", Optional.empty(), Optional.empty()),
Optional.of("test"),
Optional.empty(),
UTC_KEY,
Expand Down
84 changes: 84 additions & 0 deletions presto-docs/src/main/sphinx/connector/hive.rst
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,90 @@ or credentials for a specific use case (e.g., bucket/user specific credentials).
This Hadoop configuration property must be set in the Hadoop configuration
files referenced by the ``hive.config.resources`` Hive connector property.

AWS Security Mapping
^^^^^^^^^^^^^^^^^^^^

Presto supports flexible mapping for AWS Lake Formation and AWS S3 API calls, allowing for separate
credentials or IAM roles for specific users.

The mappings can be of two types: ``S3`` or ``LAKEFORMATION``.

The mapping entries are processed in the order listed in the configuration
file. More specific mappings should be specified before less specific mappings.
You can set default configuration by not including any match criteria for the last
entry in the list.

Each mapping entry when mapping type is ``S3`` may specify one match criteria. Available match criteria:

* ``user``: Regular expression to match against username. Example: ``alice|bob``

The mapping must provide one or more configuration settings:

* ``accessKey`` and ``secretKey``: AWS access key and secret key. This overrides
any globally configured credentials, such as access key or instance credentials.

* ``iamRole``: IAM role to use. This overrides any globally configured IAM role.

Example JSON configuration file for s3:

.. code-block:: json

{
"mappings": [
{
"user": "admin",
"accessKey": "AKIAxxxaccess",
"secretKey": "iXbXxxxsecret"
},
{
"user": "analyst|scientist",
"iamRole": "arn:aws:iam::123456789101:role/analyst_and_scientist_role"
},
{
"iamRole": "arn:aws:iam::123456789101:role/default"
}
]
}

Each mapping entry when mapping type is ``LAKEFORMATION`` may specify one match criteria. Available match criteria:

* ``user``: Regular expression to match against username. Example: ``alice|bob``

The mapping must provide one configuration setting:

* ``iamRole``: IAM role to use. This overrides any globally configured IAM role.

Example JSON configuration file for lakeformation:

.. code-block:: json

{
"mappings": [
{
"user": "admin",
"iamRole": "arn:aws:iam::123456789101:role/admin_role"
},
{
"user": "analyst",
"iamRole": "arn:aws:iam::123456789101:role/analyst_role"
},
{
"iamRole": "arn:aws:iam::123456789101:role/default_role"
}
]
}

======================================================= =================================================================
Property Name Description
======================================================= =================================================================
``hive.aws.security-mapping.type`` AWS Security Mapping Type. Possible values: S3 or LAKEFORMATION

``hive.aws.security-mapping.config-file`` JSON configuration file containing AWS IAM Security mappings

``hive.aws.security-mapping.refresh-period`` Time interval after which AWS IAM security mapping configuration
will be refreshed
======================================================= =================================================================

Tuning Properties
^^^^^^^^^^^^^^^^^

Expand Down
10 changes: 10 additions & 0 deletions presto-hive-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
<artifactId>configuration</artifactId>
</dependency>

<dependency>
<groupId>com.facebook.airlift</groupId>
<artifactId>log</artifactId>
</dependency>

<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
Expand All @@ -50,6 +55,11 @@
<artifactId>presto-orc</artifactId>
</dependency>

<dependency>
<groupId>com.facebook.presto</groupId>
<artifactId>presto-plugin-toolkit</artifactId>
</dependency>

<dependency>
<groupId>com.facebook.presto.hadoop</groupId>
<artifactId>hadoop-apache2</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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 com.facebook.presto.hive.aws.security;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

public class AWSSecurityMapping
{
private final Predicate<String> user;
private final Optional<String> iamRole;
private final Optional<BasicAWSCredentials> credentials;

@JsonCreator
public AWSSecurityMapping(
@JsonProperty("user") Optional<Pattern> user,
@JsonProperty("iamRole") Optional<String> iamRole,
@JsonProperty("accessKey") Optional<String> accessKey,
@JsonProperty("secretKey") Optional<String> secretKey)
{
this.user = requireNonNull(user, "user is null")
.map(AWSSecurityMapping::toPredicate)
.orElse(x -> true);

this.iamRole = requireNonNull(iamRole, "iamRole is null");

requireNonNull(accessKey, "accessKey is null");
requireNonNull(secretKey, "secretKey is null");
checkArgument(accessKey.isPresent() == secretKey.isPresent(), "accessKey and secretKey must be provided together");
this.credentials = accessKey.map(access -> new BasicAWSCredentials(access, secretKey.get()));
}

public boolean matches(String user)
{
return this.user.test(user);
}

public Optional<String> getIamRole()
{
return iamRole;
}

public Optional<BasicAWSCredentials> getCredentials()
{
return credentials;
}

@Override
public String toString()
{
return toStringHelper(this)
.add("user", user)
.add("iamRole", iamRole)
.add("credentials", credentials)
.toString();
}

private static Predicate<String> toPredicate(Pattern pattern)
{
return value -> pattern.matcher(value).matches();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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 com.facebook.presto.hive.aws.security;

import com.facebook.airlift.configuration.Config;
import com.facebook.airlift.configuration.ConfigDescription;
import io.airlift.units.Duration;
import io.airlift.units.MinDuration;

import javax.annotation.Nullable;
import javax.validation.constraints.AssertTrue;

import java.io.File;
import java.util.Optional;

import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS;

public class AWSSecurityMappingConfig
{
private static final String MAPPING_TYPE = "hive.aws.security-mapping.type";
private static final String CONFIG_FILE = "hive.aws.security-mapping.config-file";
private static final String REFRESH_PERIOD = "hive.aws.security-mapping.refresh-period";
private AWSSecurityMappingType mappingType;
private File configFile;
private Duration refreshPeriod = new Duration(30, SECONDS);

public AWSSecurityMappingType getMappingType()
{
return mappingType;
}

@Config(MAPPING_TYPE)
@ConfigDescription("AWS Security Mapping Type. Possible values: S3 or LAKEFORMATION")
public AWSSecurityMappingConfig setMappingType(AWSSecurityMappingType mappingType)
{
this.mappingType = mappingType;
return this;
}

public Optional<File> getConfigFile()
{
return Optional.ofNullable(configFile);
}

@Nullable
@Config(CONFIG_FILE)
@ConfigDescription("JSON configuration file containing AWS IAM Security mappings")
public AWSSecurityMappingConfig setConfigFile(File configFile)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we explicitly mark things nullable? This is nullable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added the annotation

{
this.configFile = configFile;
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, definitely don't check in getConfig. Possibly check when the object is created or when the file is read, but not in the getter method.

return this;
}

public Duration getRefreshPeriod()
{
return refreshPeriod;
}

@MinDuration("0ms")
@Config(REFRESH_PERIOD)
@ConfigDescription("Time interval after which AWS IAM security mapping configuration will be refreshed")
public AWSSecurityMappingConfig setRefreshPeriod(Duration refreshPeriod)
Copy link
Contributor

Choose a reason for hiding this comment

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

do you want this to be nullable? It is.

I'm a little surprised the object is mutable.

Copy link
Member Author

Choose a reason for hiding this comment

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

do you want this to be nullable? It is.

I don't think this will ever be null since null is not an acceptable value for Duration. Do you see any issue?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm a little surprised the object is mutable.

Since new users can be added or credentials for existing users might be modified at any time. To make that change dynamic without a cluster restart, I have made this object mutable.

Copy link
Contributor

Choose a reason for hiding this comment

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

All someone has to do to make this null is pass null to this method. If that's nopt OK, put a requireNonNull here. If it is OK, annotate it as nullable.

{
this.refreshPeriod = requireNonNull(refreshPeriod, "refreshPeriod is null");
return this;
}

@AssertTrue(message = "MAPPING TYPE(" + MAPPING_TYPE + ") must be configured when AWS Security Mapping Config File(" + CONFIG_FILE + ") is set and vice versa")
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't seen this annotation before. Where is this coming from?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is part of the below dependency:

<dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
</dependency>

This is being used in a few more places already in Presto like com.facebook.presto.server.InternalCommunicationConfig#isRequiredSharedSecretSet, com.facebook.presto.sql.analyzer.FeaturesConfig#isSpillerSpillPathsConfiguredIfSpillEnabled

public boolean isValidConfiguration()
{
return (getConfigFile().isPresent() && getMappingType() != null) || (!getConfigFile().isPresent() && getMappingType() == null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 com.facebook.presto.hive.aws.security;

public enum AWSSecurityMappingType
{
S3,
LAKEFORMATION,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 com.facebook.presto.hive.aws.security;

import com.facebook.presto.spi.security.AccessDeniedException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;

import java.util.List;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;

public class AWSSecurityMappings
{
private final List<AWSSecurityMapping> awsSecurityMappings;

@JsonCreator
public AWSSecurityMappings(@JsonProperty("mappings") List<AWSSecurityMapping> awsSecurityMappings)
{
checkArgument(awsSecurityMappings != null, "No AWS Security mappings configured");

this.awsSecurityMappings = ImmutableList.copyOf(awsSecurityMappings);
}

public AWSSecurityMapping getAWSLakeFormationSecurityMapping(String user)
{
Optional<AWSSecurityMapping> awsSecurityMapping = awsSecurityMappings.stream()
.filter(mapping -> (mapping.matches(user)))
.findFirst();

if (!awsSecurityMapping.isPresent()) {
throw new AccessDeniedException("No matching AWS Lake Formation Security Mapping");
}

verify(!awsSecurityMapping.get().getCredentials().isPresent(),
"Basic AWS Credentials are not supported for AWS Lake Formation Security Mapping");

verify(awsSecurityMapping.get().getIamRole().isPresent(),
"iamRole is mandatory for AWS Lake Formation Security Mapping");

return awsSecurityMapping.get();
}

public AWSSecurityMapping getAWSS3SecurityMapping(String user)
{
Optional<AWSSecurityMapping> awsSecurityMapping = awsSecurityMappings.stream()
.filter(mapping -> mapping.matches(user))
.findFirst();

return awsSecurityMapping.orElseThrow(() -> new AccessDeniedException("No matching AWS S3 Security Mapping"));
}
}
Loading