-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Introduce AWS Security Mapping #21622
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
Changes from all commits
1a615b8
e0a0a9c
0742ef2
d23c401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we explicitly mark things nullable? This is nullable.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the annotation |
||
| { | ||
| this.configFile = configFile; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think this will ever be null since null is not an acceptable value for Duration. Do you see any issue?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't seen this annotation before. Where is this coming from?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is part of the below dependency: This is being used in a few more places already in Presto like |
||
| 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")); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.