├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── THIRD-PARTY ├── emr-user-role-mapper-application ├── .gitignore ├── conf │ ├── emr-user-role-mapper.properties │ └── mappings.json ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── amazon │ │ │ │ └── aws │ │ │ │ └── emr │ │ │ │ ├── ApplicationConfiguration.java │ │ │ │ ├── UserRoleMappingServer.java │ │ │ │ ├── api │ │ │ │ ├── MetadataController.java │ │ │ │ └── RequestFilter.java │ │ │ │ ├── common │ │ │ │ ├── Constants.java │ │ │ │ └── system │ │ │ │ │ ├── PrincipalResolver.java │ │ │ │ │ ├── factory │ │ │ │ │ └── PrincipalResolverFactory.java │ │ │ │ │ ├── impl │ │ │ │ │ ├── AbstractPrincipalResolver.java │ │ │ │ │ ├── CommandBasedPrincipalResolver.java │ │ │ │ │ └── JniBasedPrincipalResolver.java │ │ │ │ │ └── user │ │ │ │ │ ├── LinuxUserIdService.java │ │ │ │ │ └── UserIdService.java │ │ │ │ ├── credentials │ │ │ │ ├── MetadataCredentialsProvider.java │ │ │ │ ├── STSClient.java │ │ │ │ ├── STSClientImpl.java │ │ │ │ └── STSCredentialsProvider.java │ │ │ │ ├── mapping │ │ │ │ ├── DefaultUserRoleMapperImpl.java │ │ │ │ ├── ManagedPolicyBasedUserRoleMapperImpl.java │ │ │ │ ├── MappingInvoker.java │ │ │ │ └── S3BasedUserMappingImplBase.java │ │ │ │ ├── model │ │ │ │ ├── Group.java │ │ │ │ ├── PrincipalPolicyMapping.java │ │ │ │ ├── PrincipalPolicyMappings.java │ │ │ │ ├── PrincipalRoleMapping.java │ │ │ │ ├── PrincipalRoleMappings.java │ │ │ │ └── User.java │ │ │ │ └── ws │ │ │ │ ├── ImmediateFeature.java │ │ │ │ ├── UserRoleMapperApplication.java │ │ │ │ └── UserRoleMapperBinder.java │ │ └── resources │ │ │ └── jar-with-deps-with-exclude.xml │ ├── test-data │ │ ├── log4j.properties │ │ ├── tcp │ │ ├── tcp6 │ │ ├── test-users │ │ └── user-role-mapper.properties │ └── test │ │ └── com │ │ └── amazon │ │ └── aws │ │ └── emr │ │ ├── api │ │ └── MetadataControllerTest.java │ │ ├── common │ │ ├── TestConstants.java │ │ └── system │ │ │ ├── impl │ │ │ ├── CommandBasedPrincipalResolverTest.java │ │ │ ├── JniBasedPrincipalResolverTest.java │ │ │ └── PrincipalResolverTestBase.java │ │ │ └── user │ │ │ ├── LinuxUserIdServiceTest.java │ │ │ └── TestCommandBasedPrincipalResolver.java │ │ ├── credentials │ │ ├── STSCredentialsProviderTest.java │ │ └── TestMetadataCredentialsProvider.java │ │ ├── integration │ │ ├── IntegrationTestBase.java │ │ ├── IntegrationTestsUserService.java │ │ ├── defaultmapper │ │ │ ├── DefaultMapperImplApplicationConfig.java │ │ │ ├── DefaultMapperIntegrationBinder.java │ │ │ ├── DefaultMappingProviderImplIntegrationTest.java │ │ │ └── DefaultProviderImplIntegrationApplication.java │ │ ├── policyunionmapper │ │ │ ├── PoliciesUnionMapperImplApplicationConfig.java │ │ │ ├── PoliciesUnionMapperIntegrationBinder.java │ │ │ ├── PoliciesUnionMappingProviderImplIntegrationTest.java │ │ │ └── PoliciesUnionProviderImplIntegrationApplication.java │ │ └── utils │ │ │ ├── IAMUtils.java │ │ │ ├── S3Utils.java │ │ │ └── STSUtils.java │ │ ├── mapping │ │ ├── ManagedPolicyBasedUserRoleMapperImplTest.java │ │ ├── MappingInvokerTest.java │ │ └── TestUserRoleMapperImpl.java │ │ └── model │ │ ├── GroupTest.java │ │ ├── PrincipalPolicyMappingsTest.java │ │ ├── PrincipalRoleMappingsTest.java │ │ └── UserTest.java └── usr │ └── install │ ├── al1 │ └── emr-user-role-mapper.conf │ ├── al2 │ ├── emr-user-role-mapper │ └── emr-user-role-mapper.service │ ├── ba-script.sh │ ├── log4j.properties │ └── user-role-mapper.properties ├── emr-user-role-mapper-credentials-provider ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── amazonaws │ │ ├── auth │ │ └── URMCredentialsFetcher.java │ │ └── emr │ │ └── urm │ │ └── credentialsprovider │ │ ├── URMCredentialsProvider.java │ │ └── URMCredentialsProviderChain.java │ └── test │ └── java │ └── com │ └── amazonaws │ └── emr │ └── urm │ └── credentialsprovider │ └── URMCredentialsProviderTest.java ├── emr-user-role-mapper-interface ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── amazon │ └── aws │ └── emr │ └── rolemapper │ └── UserRoleMapperProvider.java ├── emr-user-role-mapper-s3storagebasedauthorizationmanager ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── amazonaws │ │ └── emr │ │ └── urm │ │ ├── glue │ │ └── credentialsprovider │ │ │ └── URMCredentialsProviderFactory.java │ │ └── hive │ │ └── urmstoragebasedauthorizer │ │ ├── S3Action.java │ │ ├── S3StorageBasedAuthorizationProvider.java │ │ └── URMCredentialsRetriever.java │ └── test │ └── java │ └── com │ └── amazonaws │ └── emr │ └── urm │ └── hive │ └── urmstoragebasedauthorizer │ └── S3StorageBasedAuthorizationProviderTest.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/.idea/ 3 | **/*.iml -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Amazon EMR User Role Mapper 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /target/ 3 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/conf/emr-user-role-mapper.properties: -------------------------------------------------------------------------------- 1 | #rolemapper.class.name=com.mycompany.mapper.RoleMapperImpl 2 | 3 | rolemapper.s3.bucket=my-urm-bucket 4 | rolemapper.s3.key=mappings.json 5 | 6 | rolemapper.refresh.interval.minutes=1 7 | 8 | rolemapper.max.threads=35 9 | rolemapper.min.threads=15 10 | 11 | rolemapper.impersonation.allowed.users=hive,presto 12 | 13 | # By default JNI is used to resolve local users and groups 14 | #principal.resolver.strategy=command 15 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/conf/mappings.json: -------------------------------------------------------------------------------- 1 | { 2 | "PrincipalRoleMappings": [ 3 | { 4 | "username": "user1", 5 | "rolearn": "arn:aws:iam:::role/" 6 | }, 7 | { 8 | "groupname": "group1", 9 | "rolearn": "arn:aws:iam:::role/", 10 | "duration": 1800 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/ApplicationConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr; 5 | 6 | import com.amazon.aws.emr.common.Constants; 7 | import com.google.common.collect.ImmutableSet; 8 | import com.google.common.collect.Maps; 9 | import java.util.Map; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.glassfish.hk2.api.Immediate; 12 | 13 | import javax.annotation.PostConstruct; 14 | import java.io.InputStream; 15 | import java.util.Properties; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * The configuration singleton for this application. 21 | */ 22 | @Slf4j 23 | @Immediate 24 | public class ApplicationConfiguration { 25 | 26 | private final static String PROPS_FILE = "/user-role-mapper.properties"; 27 | protected Properties properties = new Properties(); 28 | private ImmutableSet IMPERSONATION_ALLOWED_USERS = ImmutableSet.of(); 29 | 30 | @PostConstruct 31 | public void init() { 32 | try (final InputStream stream = 33 | this.getClass().getResourceAsStream(PROPS_FILE)) { 34 | properties.load(stream); 35 | if (!isValidConfig()) { 36 | throw new RuntimeException("Invalid configuration!"); 37 | } 38 | log.info("Loaded " + properties.toString()); 39 | 40 | if (properties.containsKey(Constants.IMPERSONATION_ALLOWED_USERS)) { 41 | IMPERSONATION_ALLOWED_USERS = ImmutableSet 42 | .copyOf(properties.getProperty(Constants.IMPERSONATION_ALLOWED_USERS).split(",")) 43 | .stream().map(String::trim).collect(ImmutableSet.toImmutableSet()); 44 | log.info("Loaded allowed users for impersonation: {}", IMPERSONATION_ALLOWED_USERS); 45 | } 46 | } catch (Exception e) { 47 | throw new RuntimeException("Could not load properties file", e); 48 | } 49 | } 50 | 51 | private boolean isValidConfig() { 52 | boolean isValid = true; 53 | if ((getProperty(Constants.ROLE_MAPPER_CLASS, null) == null) && 54 | (getProperty(Constants.ROLE_MAPPING_S3_BUCKET, null) == null && 55 | getProperty(Constants.ROLE_MAPPING_S3_KEY, null) == null)) { 56 | log.error("Both custom class name and bucket/key can't be null."); 57 | isValid = false; 58 | } 59 | 60 | return isValid; 61 | } 62 | 63 | /** 64 | * @return all the property names 65 | */ 66 | public Set getAllPropertyNames() { 67 | return properties.keySet().stream() 68 | .map(Object::toString) 69 | .collect(Collectors.toSet()); 70 | } 71 | 72 | /** 73 | * @param propertyName a property that may not exist 74 | * @param defaultValue default value if it does not exist 75 | * @return value in properties or default if not present 76 | */ 77 | public String getProperty(String propertyName, String defaultValue) { 78 | return properties.getProperty(propertyName, defaultValue); 79 | } 80 | 81 | /** 82 | * @param propertyName a property that may not exist 83 | * @param defaultValue default value if it does not exist 84 | * @return value in properties or default if not present 85 | */ 86 | public int getProperty(String propertyName, int defaultValue) { 87 | return Integer.parseInt(properties.getProperty(propertyName, String.valueOf(defaultValue))); 88 | } 89 | 90 | /** 91 | * Set a property, overriding any previous value. 92 | * 93 | * @param propertyName name of property 94 | * @param value value to set 95 | */ 96 | public void setProperty(String propertyName, String value) { 97 | properties.put(propertyName, value); 98 | } 99 | 100 | public Set getAllowedUsersForImpersonation() { 101 | return this.IMPERSONATION_ALLOWED_USERS; 102 | } 103 | 104 | public boolean isRegionalStsEnabled() { 105 | return Boolean.parseBoolean(properties.getProperty(Constants.REGIONAL_STS_ENDPOINT_ENABLED, 106 | String.valueOf("true"))); 107 | } 108 | 109 | 110 | public boolean isSetSourceIdentityEnabled() { 111 | return Boolean.parseBoolean(properties.getProperty(Constants.SET_SOURCE_IDENTITY_ENABLED, 112 | String.valueOf("false"))); 113 | } 114 | 115 | public Map asMap() { 116 | return Maps.fromProperties(properties); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/UserRoleMappingServer.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr; 5 | 6 | import com.amazon.aws.emr.common.Constants; 7 | import com.amazon.aws.emr.ws.UserRoleMapperApplication; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.eclipse.jetty.server.Server; 10 | import org.eclipse.jetty.server.ServerConnector; 11 | import org.eclipse.jetty.servlet.ServletContextHandler; 12 | import org.eclipse.jetty.servlet.ServletHolder; 13 | import org.eclipse.jetty.util.thread.QueuedThreadPool; 14 | import org.glassfish.jersey.servlet.ServletContainer; 15 | 16 | /** 17 | * Server that handles all user role mapping requests. 18 | */ 19 | @Slf4j 20 | public class UserRoleMappingServer { 21 | 22 | public static void main(String[] args) { 23 | 24 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); 25 | context.setContextPath("/"); 26 | 27 | ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration(); 28 | applicationConfiguration.init(); 29 | int maxThreads = applicationConfiguration.getProperty(Constants.ROLE_MAPPING_MAX_THREADS, Constants.ROLE_MAPPING_DEFAULT_MAX_THREADS); 30 | int minThreads = applicationConfiguration.getProperty(Constants.ROLE_MAPPING_MIN_THREADS, Constants.ROLE_MAPPING_DEFAULT_MIN_THREADS); 31 | log.info("Starting with max {} and min {} threads", maxThreads, minThreads); 32 | 33 | QueuedThreadPool pool = new QueuedThreadPool(); 34 | pool.setMaxThreads(maxThreads); 35 | pool.setMinThreads(minThreads); 36 | pool.setIdleTimeout(Constants.ROLE_MAPPING_DEFAULT_IDLE_TIMEOUT_MS); 37 | pool.setName("worker-thread"); 38 | 39 | Server jettyServer = new Server(pool); 40 | jettyServer.setHandler(context); 41 | 42 | ServerConnector httpConnector = new ServerConnector(jettyServer); 43 | httpConnector.setPort(Constants.JETTY_PORT); 44 | jettyServer.addConnector(httpConnector); 45 | 46 | ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/*"); 47 | jerseyServlet.setInitOrder(0); 48 | 49 | // Tells the Jersey Servlet which REST service/class to load. 50 | jerseyServlet.setInitParameter("jersey.config.server.provider.packages", "com.amazon.emr.api"); 51 | jerseyServlet.setInitParameter("javax.ws.rs.Application", UserRoleMapperApplication.class.getName()); 52 | 53 | try { 54 | log.info("Starting the user role mapping server"); 55 | jettyServer.start(); 56 | jettyServer.join(); 57 | } catch (Exception e) { 58 | log.error("Error in user role mapping server", e); 59 | } finally { 60 | jettyServer.destroy(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/api/RequestFilter.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.api; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import javax.ws.rs.container.ContainerRequestContext; 10 | import javax.ws.rs.container.ContainerRequestFilter; 11 | import javax.ws.rs.container.PreMatching; 12 | import javax.ws.rs.core.Response; 13 | import javax.ws.rs.core.UriBuilder; 14 | import javax.ws.rs.core.UriInfo; 15 | import javax.ws.rs.ext.Provider; 16 | import java.net.URI; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | /** 21 | * This class implements the request filter before request matching. 22 | *

23 | * We are sanitizing the request URI to remove repeating forward slashes, which can trick the request matching. 24 | * e.g. "////latest/meta-data/iam/security-credentials////EMR_EC2_DefaultRole" will be matched to default handler. 25 | * So the instance role credentials will be returned if the unprivileged user utilizes this vulnerability. 26 | *

27 | * More info: https://jersey.github.io/documentation/latest/filters-and-interceptors.html#d0e9365 28 | */ 29 | @Provider 30 | @PreMatching 31 | @Slf4j 32 | public class RequestFilter implements ContainerRequestFilter { 33 | 34 | private static final List STATIC_SENSITIVE_RESOURCES = 35 | Collections.singletonList("user-data"); 36 | 37 | @Override 38 | public void filter(ContainerRequestContext ctx) { 39 | UriInfo uriInfo = ctx.getUriInfo(); 40 | URI sanitizedUri = sanitizeRequestUri(uriInfo); 41 | log.debug("Sanitized URI {}", sanitizedUri); 42 | if (isAuthorizedUri(sanitizedUri)) { 43 | ctx.setRequestUri(sanitizedUri); 44 | } else { 45 | ctx.abortWith(Response 46 | .status(Response.Status.UNAUTHORIZED) 47 | .entity("Permission denied to access the resource") 48 | .build()); 49 | } 50 | } 51 | 52 | private URI sanitizeRequestUri(UriInfo uriInfo) { 53 | String sanitizedUri = uriInfo.getPath().replaceAll("\\/+", "/"); 54 | String decodedUri = decodeURL(sanitizedUri); 55 | UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); 56 | URI newUri = uriBuilder.path(decodedUri).build(); 57 | return newUri.normalize(); 58 | } 59 | 60 | private boolean isAuthorizedUri(URI sanitizedUri) { 61 | String path = sanitizedUri.getPath(); 62 | 63 | for (String staticSensitiveResource : STATIC_SENSITIVE_RESOURCES) { 64 | if (path.contains(staticSensitiveResource)) { 65 | return false; 66 | } 67 | } 68 | return true; 69 | } 70 | 71 | /** 72 | * Recursively decodes a URL. 73 | * 74 | * This is important as a malicious caller could try to access protected URIs 75 | * by recursively encoding the URL. To find the true intent of the request we 76 | * need to recursively decode it. 77 | * 78 | * @param url the URL to decode 79 | * @return the decoded URL 80 | */ 81 | public String decodeURL(String url) { 82 | try { 83 | String decoded = java.net.URLDecoder.decode(url, "UTF-8"); 84 | if (!decoded.equals(url)) { 85 | return decodeURL(decoded); 86 | } else { 87 | return decoded; 88 | } 89 | } catch (UnsupportedEncodingException e) { 90 | throw new RuntimeException("Could not decode URL", e); 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/Constants.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common; 5 | 6 | import com.amazon.aws.emr.mapping.DefaultUserRoleMapperImpl; 7 | 8 | import com.amazon.aws.emr.mapping.ManagedPolicyBasedUserRoleMapperImpl; 9 | import java.time.ZoneOffset; 10 | import java.time.format.DateTimeFormatter; 11 | 12 | /** 13 | * Class to hold constants 14 | */ 15 | final public class Constants { 16 | public static final int ROLE_MAPPING_DEFAULT_MAX_THREADS = 30; 17 | public static final int ROLE_MAPPING_DEFAULT_MIN_THREADS = 10; 18 | public static final int ROLE_MAPPING_DEFAULT_IDLE_TIMEOUT_MS = 300 * 1000; // 5 min 19 | public static final int JETTY_PORT = 9944; 20 | 21 | /** 22 | * Class name for mapper class. 23 | */ 24 | public static final String ROLE_MAPPER_CLASS = "rolemapper.class.name"; 25 | /** 26 | * S3 Bucket for the role mapping file. 27 | */ 28 | public static final String ROLE_MAPPING_S3_BUCKET = "rolemapper.s3.bucket"; 29 | /** 30 | * AWS Role to be used for role mapping. This is used in {@link ManagedPolicyBasedUserRoleMapperImpl} 31 | */ 32 | public static final String ROLE_MAPPING_ROLE_ARN = "rolemapper.role.arn"; 33 | /** 34 | * S3 Bucket for the role mapping file. 35 | */ 36 | public static final String ROLE_MAPPING_MAX_THREADS = "rolemapper.max.threads"; 37 | /** 38 | * S3 Bucket for the role mapping file. 39 | */ 40 | public static final String ROLE_MAPPING_MIN_THREADS = "rolemapper.min.threads"; 41 | /** 42 | * Key for the role mapping file. 43 | */ 44 | public static final String ROLE_MAPPING_S3_KEY = "rolemapper.s3.key"; 45 | 46 | /** 47 | * Duration in mins to check for new mapping. 48 | */ 49 | public static final String ROLE_MAPPPING_REFRESH_INTERVAL_MIN = "rolemapper.refresh.interval.minutes"; 50 | 51 | public static final String ROLE_MAPPPING_DEFAULT_REFRESH_INTERVAL_MIN = "5"; 52 | 53 | public static final int ROLE_MAPPING_MIN_REFRESH_INTERVAL_MIN = 1; 54 | 55 | /** 56 | * Default S3 Mapper Impl for JSON format. 57 | */ 58 | public static final String ROLE_MAPPING_DEFAULT_CLASSNAME = DefaultUserRoleMapperImpl.class.getName(); 59 | 60 | /** 61 | * Default S3 Mapper Impl for JSON format. 62 | */ 63 | public static final String ROLE_MAPPING_MANAGED_POLICY_CLASSNAME = ManagedPolicyBasedUserRoleMapperImpl 64 | .class.getName(); 65 | 66 | /** 67 | * Constants related with joda DateTime and JSON 68 | */ 69 | // This format is to match the result from EC2 metadata service call 70 | public static final String DATE_TIME_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SS'Z'"; 71 | public static final DateTimeFormatter DATE_TIME_FORMATTER = 72 | DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_PATTERN).withZone(ZoneOffset.UTC); 73 | 74 | /** 75 | * Strategy for resolving principal i.e. user or group. Set the below to "command" if default strategy is not 76 | * working 77 | */ 78 | public static final String PRINCIPAL_RESOLVER_STRATEGY_KEY = "principal.resolver.strategy"; 79 | 80 | // Default Principal resolver implementation using native system calls 81 | public static final String DEFAULT_PRINCIPAL_RESOLVER_STRATEGY = "command"; 82 | 83 | public static final String IMPERSONATION_ALLOWED_USERS = "rolemapper.impersonation.allowed.users"; 84 | 85 | // Set the source identity in the Assume Role calls 86 | public static final String SET_SOURCE_IDENTITY_ENABLED = "rolemapper.sourceidentity.enabled"; 87 | 88 | // Determines if regional STS endpoint is used. Setting to "false" uses global endpoint. 89 | // Default value is true. 90 | public static final String REGIONAL_STS_ENDPOINT_ENABLED = "rolemapper.regional.sts.endpoint.enabled"; 91 | 92 | private Constants() { 93 | } 94 | 95 | /** 96 | * Networking related constants 97 | */ 98 | public static class Network { 99 | /** 100 | * Hex value of 127.0.0.1 in /proc/net/tcp file in reverse byte order 101 | */ 102 | public static final String IPV4_LOCALHOST_ADDR_IN_HEX_REVERSED_BYTE_ORDER = "0100007F"; 103 | 104 | /** 105 | * Hex value of ::1 in /proc/net/tcp6 file in reverse byte order 106 | */ 107 | public static final String IPV6_LOCALHOST_ADDR_IN_HEX_REVERSED_BYTE_ORDER = "00000000000000000000000001000000"; 108 | 109 | /** 110 | * Hex value of IPv4 127.0.0.1 in /proc/net/tcp6 file in reverse byte order 111 | */ 112 | public static final String IPV4_MAPPED_IPV6_LOCALHOST_ADDR_IN_HEX_REVERSED_BYTE_ORDER = "0000000000000000FFFF00000100007F"; 113 | 114 | /** 115 | * Hex value of 160 in /proc/net/tcp file in reverse byte order 116 | */ 117 | public static final String IPV4_IMDS_ADDR_IN_HEX_REVERSED_BYTE_ORDER = "FEA9FEA9"; 118 | 119 | /** 120 | * Hex value of 160 in /proc/net/tcp file in reverse byte order 121 | */ 122 | public static final String IPV6_IMDS_ADDR_IN_HEX_REVERSED_BYTE_ORDER = "0000000000000000FFFF0000FEA9FEA9"; 123 | 124 | /** 125 | * /proc/net/tcp filepath. Make it result of function call so compiler does not inline the value. 126 | * This way we can override during unit testing. 127 | */ 128 | public static final String MODULE_PROC_NET_TCP_PATH = String.valueOf("/proc/net/tcp"); 129 | 130 | /** 131 | * /proc/net/tcp6 filepath. Make it result of function call so compiler does not inline the value. 132 | * This way we can override during unit testing. 133 | */ 134 | public static final String MODULE_PROC_NET_TCP6_PATH = String.valueOf("/proc/net/tcp6"); 135 | } 136 | 137 | /** 138 | * Linux related constants 139 | */ 140 | public static class Linux { 141 | 142 | // /proc related constants. 143 | public static class Proc { 144 | // Connection state used in /proc/net/tcp and /proc/net/tcp6 files. 145 | public static final int TCP_STATE_ESTABLISHED = 1; 146 | } 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/PrincipalResolver.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | public interface PrincipalResolver { 10 | /** 11 | * Gets the username associated with a user id. 12 | * 13 | * @param uid the user id whose mapping needs to be found. 14 | * @return an {@link Optional} containing username if mapping found, else {@link Optional#empty()} 15 | */ 16 | Optional getUsername(int uid); 17 | 18 | /** 19 | * Get the group names a username belongs to. 20 | * 21 | * @param username 22 | * @return list of group names 23 | */ 24 | Optional> getGroups(String username); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/factory/PrincipalResolverFactory.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.factory; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.Constants; 8 | import com.amazon.aws.emr.common.system.PrincipalResolver; 9 | import com.amazon.aws.emr.common.system.impl.CommandBasedPrincipalResolver; 10 | import com.amazon.aws.emr.common.system.impl.JniBasedPrincipalResolver; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.glassfish.hk2.api.Factory; 13 | 14 | import javax.inject.Inject; 15 | 16 | /** 17 | * Factory to return principal resolver implementation depending on {@link Constants#PRINCIPAL_RESOLVER_STRATEGY_KEY} 18 | * value. 19 | * By default, it uses the JNI implementation to retrieve user/ groups. 20 | */ 21 | @Slf4j 22 | public class PrincipalResolverFactory implements Factory { 23 | @Inject 24 | private ApplicationConfiguration appConfig; 25 | 26 | @Override 27 | public PrincipalResolver provide() { 28 | String principalResolverStrategy = appConfig 29 | .getProperty(Constants.PRINCIPAL_RESOLVER_STRATEGY_KEY, Constants.DEFAULT_PRINCIPAL_RESOLVER_STRATEGY); 30 | 31 | log.info("Using principal resolver strategy: {}", principalResolverStrategy); 32 | if (Constants.DEFAULT_PRINCIPAL_RESOLVER_STRATEGY.equalsIgnoreCase(principalResolverStrategy)) 33 | return new CommandBasedPrincipalResolver(); 34 | 35 | return new JniBasedPrincipalResolver(); 36 | } 37 | 38 | @Override 39 | public void dispose(PrincipalResolver instance) { 40 | // noop 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/impl/AbstractPrincipalResolver.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.impl; 5 | 6 | import com.amazon.aws.emr.common.system.PrincipalResolver; 7 | import com.amazon.aws.emr.model.User; 8 | import com.google.common.annotations.VisibleForTesting; 9 | import com.google.common.cache.CacheBuilder; 10 | import com.google.common.cache.CacheLoader; 11 | import com.google.common.cache.LoadingCache; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.io.IOException; 16 | import java.nio.file.Files; 17 | import java.nio.file.Path; 18 | import java.nio.file.Paths; 19 | import java.util.List; 20 | import java.util.Optional; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.stream.Stream; 23 | 24 | /** 25 | * Provides shared caching functionality for concrete implementations. 26 | */ 27 | @Slf4j 28 | public abstract class AbstractPrincipalResolver implements PrincipalResolver { 29 | private static final int USER_MAP_MAX_SIZE = 10000; 30 | private static final String LINUX_USERS_FILE = "/etc/passwd"; 31 | 32 | private static final int GROUP_MAP_MAX_SIZE = 10000; 33 | private static final int DEFAULT_GROUP_MAP_EXPIRATION_MINS = 15; 34 | 35 | private final LoadingCache> userMap; 36 | private final LoadingCache> groupMap; 37 | 38 | AbstractPrincipalResolver() { 39 | this(DEFAULT_GROUP_MAP_EXPIRATION_MINS, TimeUnit.MINUTES); 40 | } 41 | 42 | AbstractPrincipalResolver(Integer groupMapTtl, TimeUnit timeUnit) { 43 | CacheLoader> userLoader = new CacheLoader>() { 44 | @Override 45 | public Optional load(Integer uid) { 46 | return getLinuxUsername(uid); 47 | } 48 | }; 49 | 50 | CacheLoader> groupLoader = new CacheLoader>() { 51 | @Override 52 | public List load(String username) { 53 | return getLinuxGroups(username); 54 | } 55 | }; 56 | 57 | this.userMap = CacheBuilder.newBuilder() 58 | .maximumSize(USER_MAP_MAX_SIZE) 59 | .build(userLoader); 60 | 61 | this.groupMap = CacheBuilder.newBuilder() 62 | .maximumSize(GROUP_MAP_MAX_SIZE) 63 | .expireAfterWrite(groupMapTtl, timeUnit) 64 | .build(groupLoader); 65 | } 66 | 67 | @PostConstruct 68 | void init() { 69 | log.info("Reading all OS users"); 70 | readOSUsers(); 71 | } 72 | 73 | /** 74 | * We don't employ locks here as we never clear the mapping. 75 | * User id once assigned a username by Linux is not reused even if the same username is created again. 76 | *

77 | * Note the map might contain more entries if users get deleted but that should be an infrequent operation. 78 | */ 79 | private synchronized void readOSUsers() { 80 | try (Stream stream = Files.lines(getSystemUsersFileName())) { 81 | stream.filter(s -> s.charAt(0) != '#').map(User::createFromPasswdEntry) 82 | .filter(u -> !u.getShell().equals("/usr/sbin/nologin")) 83 | .forEach(user -> userMap.put(user.getUid(), Optional.ofNullable(user.getName()))); 84 | } catch (IOException ioe) { 85 | log.error("Couldn't parse system users file", ioe); 86 | } 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | @Override 93 | public Optional getUsername(int uid) { 94 | return userMap.getUnchecked(uid); 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | *

100 | * Uses linux command: id -Gn {username} to get groups 101 | */ 102 | @Override 103 | public Optional> getGroups(String username) { 104 | return Optional.ofNullable(groupMap.getUnchecked(username)); 105 | } 106 | 107 | /** 108 | * Get linux username corresponding to POSIX userId 109 | * 110 | * @param userId 111 | * @return Username wrapped in an {@code Optional} 112 | */ 113 | protected abstract Optional getLinuxUsername(int userId); 114 | 115 | /** 116 | * @param username 117 | * @return List containing groups if mapping is found else empty 118 | */ 119 | protected abstract List getLinuxGroups(String username); 120 | 121 | @VisibleForTesting 122 | protected Path getSystemUsersFileName() { 123 | return Paths.get(LINUX_USERS_FILE); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/impl/CommandBasedPrincipalResolver.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.impl; 5 | 6 | import com.google.common.annotations.VisibleForTesting; 7 | import lombok.NoArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Optional; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.regex.Pattern; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * Uses linux commands to gather user and group information. 23 | */ 24 | @Slf4j 25 | @NoArgsConstructor 26 | public class CommandBasedPrincipalResolver extends AbstractPrincipalResolver { 27 | 28 | @VisibleForTesting 29 | CommandBasedPrincipalResolver(Integer groupMapTtl, TimeUnit timeUnit) { 30 | super(groupMapTtl, timeUnit); 31 | } 32 | 33 | @Override 34 | @VisibleForTesting 35 | protected Optional getLinuxUsername(int uid) { 36 | List getUsernameCommand = Arrays.asList("id", "-nu", String.valueOf(uid)); 37 | 38 | log.debug("Finding username for uid: {}", uid); 39 | List getUsernameOutput = runCommand(getUsernameCommand); 40 | return getUsernameOutput.stream().findFirst(); 41 | } 42 | 43 | @Override 44 | @VisibleForTesting 45 | protected List getLinuxGroups(String username) { 46 | List getGroupsCommand = Arrays.asList("id", "-Gn", username); 47 | 48 | log.debug("Finding groups for user: {}", username); 49 | return runCommand(getGroupsCommand); 50 | } 51 | 52 | /** 53 | * Returns the command output delimited by space 54 | * In case of any error such as a non zero return code from subprocess, exception etc 55 | * returns an empty list. 56 | */ 57 | public List runCommand(List command) { 58 | List commandOutput = new ArrayList<>(); 59 | 60 | try { 61 | Process process = new ProcessBuilder(command).start(); 62 | 63 | if (!process.waitFor(3, TimeUnit.SECONDS)) { 64 | log.error("Command didn't finish: {}", command); 65 | process.destroyForcibly(); 66 | return commandOutput; 67 | } 68 | 69 | try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { 70 | return br.lines().flatMap(Pattern.compile("\\s+")::splitAsStream).collect(Collectors.toList()); 71 | } 72 | } catch (IOException | InterruptedException ie) { 73 | log.error("Couldn't run command to retrieve user/ groups: {}", command, ie); 74 | } 75 | 76 | return commandOutput; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/impl/JniBasedPrincipalResolver.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.impl; 5 | 6 | import com.google.common.annotations.VisibleForTesting; 7 | import lombok.NoArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.bytedeco.systems.global.linux; 10 | import org.bytedeco.systems.linux.group; 11 | import org.bytedeco.systems.linux.passwd; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * Uses linux native calls to gather user and group information. 20 | * 21 | * In order to make native calls, it uses the {@link linux} library 22 | * which acts as an interface to invoke APIs provided by glibc. 23 | * @see javacpp-presets 24 | */ 25 | @Slf4j 26 | @NoArgsConstructor 27 | public class JniBasedPrincipalResolver extends AbstractPrincipalResolver { 28 | private static final int MAX_NUM_GROUPS_FETCH = 100; 29 | 30 | @VisibleForTesting 31 | JniBasedPrincipalResolver(Integer groupMapTtl, TimeUnit timeUnit) { 32 | super(groupMapTtl, timeUnit); 33 | } 34 | 35 | @Override 36 | @VisibleForTesting 37 | protected Optional getLinuxUsername(int uid) { 38 | log.debug("Finding username for uid: {}", uid); 39 | 40 | passwd passwdEntry = linux.getpwuid(uid); 41 | if (passwdEntry == null) { 42 | log.error("Couldn't fetch record from password database for uid: {}", uid); 43 | return Optional.empty(); 44 | } 45 | return Optional.ofNullable(passwdEntry.pw_name().getString()); 46 | } 47 | 48 | @Override 49 | @VisibleForTesting 50 | protected List getLinuxGroups(String username) { 51 | List groups = new ArrayList<>(); 52 | 53 | log.debug("Finding groups for user: {}", username); 54 | passwd passwdEntry = linux.getpwnam(username); 55 | 56 | if (passwdEntry == null) { 57 | log.error("Couldn't fetch record from password database for user: {}", username); 58 | return groups; 59 | } 60 | 61 | int gid = passwdEntry.pw_gid(); 62 | log.debug("Got group id: {} for username: {}", gid, username); 63 | 64 | int[] numGroups = new int[] { MAX_NUM_GROUPS_FETCH }; 65 | int[] allGroupIds = new int[MAX_NUM_GROUPS_FETCH]; 66 | 67 | /* If the number of groups of which user is a member is less than or equal 68 | * to numGroups, then the value numGroups is returned. 69 | * 70 | * If the user is a member of more than numGroups groups, then 71 | * getgrouplist() returns -1. In this case, the value returned in 72 | * numGroups can be used to resize the buffer passed to a further call 73 | * getgrouplist(). 74 | */ 75 | int getGroupsExitCode = linux.getgrouplist(username, gid, allGroupIds, numGroups); 76 | 77 | if (getGroupsExitCode == -1) { 78 | log.warn("Some groups may not be fetched, {} has more than {} groups", username, MAX_NUM_GROUPS_FETCH); 79 | } 80 | 81 | /* nGroups[0] is always set to actual number of groups a user is part of. 82 | * To avoid spending too much time/ putting memory pressure, we will only 83 | * fetch minimum of {numGroups[0], MAX_NUM_GROUPS_FETCH}. 84 | * As a follow up, we can make this limit configurable. 85 | */ 86 | int numGroupsToFetch = Math.min(numGroups[0], MAX_NUM_GROUPS_FETCH); 87 | log.debug("Retrieving {} groups for username: {}", numGroupsToFetch, username); 88 | for (int i = 0; i < numGroupsToFetch; i++) { 89 | group grp = linux.getgrgid(allGroupIds[i]); 90 | 91 | if (grp == null || grp.gr_name() == null) { 92 | log.debug("No group entry found for gid: {} username: {}", allGroupIds[i], username); 93 | continue; 94 | } 95 | groups.add(grp.gr_name().getString()); 96 | } 97 | return groups; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/user/LinuxUserIdService.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.user; 5 | 6 | import com.amazon.aws.emr.common.Constants; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.FileReader; 11 | import java.io.IOException; 12 | import java.net.InetAddress; 13 | import java.net.UnknownHostException; 14 | import java.util.OptionalInt; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | /** 19 | * Authenticates users via /proc/net/tpc(6) by searching for matching Linux UID in the file. 20 | * 21 | *

22 | * The requests are received here for IMDS due to iptables routing. The remote addr/port in /proc/net/tpc(6) 23 | * for TCP socket will be the IMDS server and port. The local addr/port will be the callers address and port. 24 | * Note that the local entry will match the HTTP socket remote addr/port. For such an entry we check for established 25 | * TCP state and return back UID. 26 | */ 27 | @Slf4j 28 | public class LinuxUserIdService implements UserIdService { 29 | 30 | public static final int TCP_ESTABLISHED = 1; 31 | /** 32 | * Details on /proc/net/tpc(6) format: http://lkml.iu.edu/hypermail/linux/kernel/0409.1/2166.html 33 | */ 34 | private static Pattern pattern = Pattern.compile("\\s*\\d+: ([0-9A-Fa-f]+):([0-9A-Fa-f]+) ([0-9A-Fa-f]+):([0-9A-Fa-f]+) ([0-9A-Fa-f]{2}) [0-9A-Fa-f]+:[0-9A-Fa-f]+ [0-9A-Fa-f]+:[0-9A-Fa-f]+ [0-9A-Fa-f]+\\s+([0-9]+).+"); 35 | 36 | private final String ipV4Path; 37 | private final String ipV6Path; 38 | 39 | public LinuxUserIdService() { 40 | this.ipV4Path = Constants.Network.MODULE_PROC_NET_TCP_PATH; 41 | this.ipV6Path = Constants.Network.MODULE_PROC_NET_TCP6_PATH; 42 | } 43 | 44 | public LinuxUserIdService(String ipV4Path, String ipV6Path) { 45 | this.ipV4Path = ipV4Path; 46 | this.ipV6Path = ipV6Path; 47 | } 48 | 49 | /** 50 | * Check is ip address is loopback address. Method name uses localhost since it usually means 127.0.0.1 51 | * 52 | * @param ipAddr 53 | * @return 54 | */ 55 | public static boolean isLocalhost(String ipAddr) { 56 | try { 57 | InetAddress remoteInetAddress = InetAddress.getByName(ipAddr); 58 | return remoteInetAddress.isLoopbackAddress(); 59 | } catch (UnknownHostException ex) { 60 | throw new RuntimeException(String.format("Unexpected IP address (%s)", ipAddr)); 61 | } 62 | } 63 | 64 | /** 65 | * Resolve linux user id via linux /proc/net/tcp(6) 66 | * 67 | * For intercepted IMDS call, a validation is performed to ensure that the target is indeed IMDS, 68 | * and the call is made locally. 69 | * 70 | * For non-IMDS call, only ensure that the call is made locally 71 | * 72 | * @param localAddr 73 | * @param localPort 74 | * @param remoteAddr 75 | * @param remotePort 76 | * @param isNativeIMDSApi 77 | * @return 78 | */ 79 | public OptionalInt resolveSystemUID(String localAddr, int localPort, 80 | String remoteAddr, int remotePort, 81 | boolean isNativeIMDSApi) { 82 | 83 | if (!isLocalhost(localAddr)) { 84 | log.debug("Local address is not localhost on the HTTP socket!"); 85 | return OptionalInt.empty(); 86 | } 87 | 88 | OptionalInt uid; 89 | try (BufferedReader br = new BufferedReader(new FileReader(ipV4Path))) { 90 | String line; 91 | while ((line = br.readLine()) != null) { 92 | uid = getUID(line, localPort, remotePort, remoteAddr, isNativeIMDSApi); 93 | if (uid.isPresent()) { 94 | return uid; 95 | } 96 | } 97 | } catch (IOException e) { 98 | log.error("Exception reading {} file. ", ipV4Path, e); 99 | // May be this succeeds with TCP6 socket! 100 | } 101 | 102 | try (BufferedReader br = new BufferedReader(new FileReader(ipV6Path))) { 103 | String line; 104 | while ((line = br.readLine()) != null) { 105 | uid = getUID(line, localPort, remotePort, remoteAddr, isNativeIMDSApi); 106 | if (uid.isPresent()) { 107 | return uid; 108 | } 109 | } 110 | } catch (IOException e) { 111 | log.error("Exception reading {} file. ", ipV6Path, e); 112 | // TODO: re-throw this 113 | } 114 | 115 | return OptionalInt.empty(); 116 | } 117 | 118 | private OptionalInt getUID(String line, int reqLocalPort, 119 | int reqRemotePort, String procRemoteAddress, 120 | boolean isNativeIMDSApi) { 121 | return getUID( 122 | line, 123 | procRemoteAddress, 124 | reqLocalPort, reqRemotePort, isNativeIMDSApi); 125 | } 126 | 127 | private OptionalInt getUID(String line, String remoteAddr, 128 | int reqLocalPort, int reqRemotePort, 129 | boolean isNativeIMDSApi) { 130 | Matcher matcher = pattern.matcher(line); 131 | if (!matcher.matches()) { 132 | return OptionalInt.empty(); 133 | } 134 | 135 | int groupCount = matcher.groupCount(); 136 | if (groupCount <= 5) { 137 | return OptionalInt.empty(); 138 | } 139 | 140 | long procLocalPort = Long.parseLong(matcher.group(2), 16); 141 | String procRemoteAddress = matcher.group(3); 142 | long procRemotePort = Long.parseLong(matcher.group(4), 16); 143 | long state = Long.parseLong(matcher.group(5), 16); 144 | int uid = Integer.parseInt(matcher.group(6)); 145 | 146 | if (isNativeIMDSApi) { 147 | if ((procRemoteAddress.equals(Constants.Network.IPV4_IMDS_ADDR_IN_HEX_REVERSED_BYTE_ORDER) || 148 | procRemoteAddress.equals(Constants.Network.IPV6_IMDS_ADDR_IN_HEX_REVERSED_BYTE_ORDER)) 149 | && procLocalPort == reqRemotePort 150 | && procRemotePort == 80 151 | && state == TCP_ESTABLISHED 152 | ) { 153 | return OptionalInt.of(uid); 154 | } 155 | } else { 156 | // Socket established directly from caller process to the server for below use cases: 157 | // 1/ impersonation request from EMR-FS 158 | if ((procLocalPort == reqRemotePort && procRemotePort == Constants.JETTY_PORT) 159 | && (procRemoteAddress.equals(Constants.Network.IPV4_LOCALHOST_ADDR_IN_HEX_REVERSED_BYTE_ORDER) 160 | || procRemoteAddress.equals(Constants.Network.IPV6_LOCALHOST_ADDR_IN_HEX_REVERSED_BYTE_ORDER) 161 | || procRemoteAddress.equals(Constants.Network.IPV4_MAPPED_IPV6_LOCALHOST_ADDR_IN_HEX_REVERSED_BYTE_ORDER))) { 162 | return OptionalInt.of(uid); 163 | } 164 | } 165 | 166 | return OptionalInt.empty(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/common/system/user/UserIdService.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.user; 5 | 6 | import java.util.OptionalInt; 7 | 8 | public interface UserIdService { 9 | OptionalInt resolveSystemUID(String localAddr, int localPort, 10 | String remoteAddr, int remotePort, 11 | boolean isNativeIMDSApi); 12 | } 13 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/credentials/MetadataCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.credentials; 5 | 6 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 7 | import com.amazonaws.util.EC2MetadataUtils; 8 | 9 | import java.util.Optional; 10 | 11 | /** 12 | * Provider for IMDS credentials. 13 | */ 14 | public interface MetadataCredentialsProvider { 15 | /** 16 | * Gets credentials for {@code AssumeRoleRequest}. 17 | * 18 | * @param assumeRoleRequest the request to assume 19 | * @return credentials in the {@link EC2MetadataUtils.IAMSecurityCredential} format 20 | */ 21 | Optional getUserCredentials(AssumeRoleRequest assumeRoleRequest); 22 | } 23 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/credentials/STSClient.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.credentials; 2 | 3 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 4 | import com.amazonaws.services.securitytoken.model.AssumeRoleResult; 5 | 6 | /** 7 | * Interface representing the AWS STS client. 8 | */ 9 | public interface STSClient { 10 | 11 | AssumeRoleResult assumeRole(AssumeRoleRequest assumeRoleRequest); 12 | } 13 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/credentials/STSClientImpl.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.credentials; 2 | 3 | import com.amazon.aws.emr.ApplicationConfiguration; 4 | import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; 5 | import com.amazonaws.regions.Region; 6 | import com.amazonaws.regions.Regions; 7 | import com.amazonaws.services.securitytoken.AWSSecurityTokenService; 8 | import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; 9 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 10 | import com.amazonaws.services.securitytoken.model.AssumeRoleResult; 11 | import javax.annotation.PostConstruct; 12 | import javax.inject.Inject; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.glassfish.hk2.api.Immediate; 15 | 16 | /** 17 | * Creates a custom AWS STS client based on {@link ApplicationConfiguration} 18 | */ 19 | @Slf4j 20 | @Immediate 21 | public class STSClientImpl implements STSClient { 22 | // If using regional configurations we will use us-west-2 as default 23 | // This is primarily used in integration tests 24 | private String regionString = "us-west-2"; 25 | 26 | @Inject 27 | ApplicationConfiguration applicationConfiguration; 28 | 29 | AWSSecurityTokenService stsClient; 30 | 31 | @PostConstruct 32 | void init() { 33 | if (applicationConfiguration.isRegionalStsEnabled()) { 34 | Region region = null; 35 | try { 36 | region = Regions.getCurrentRegion(); 37 | regionString = region.getName(); 38 | String endpoint = String.format("https://sts.%s.amazonaws.com", regionString); 39 | log.info("Running the application with regional STS endpoint " + endpoint); 40 | stsClient = AWSSecurityTokenServiceClientBuilder 41 | .standard() 42 | .withEndpointConfiguration(new EndpointConfiguration(endpoint, regionString)) 43 | .build(); 44 | } catch (Exception e) { 45 | log.error("Cannot determine the AWS region. Defaulting to global endpoint."); 46 | createGlobalEndpointClient(); 47 | } 48 | } else { 49 | createGlobalEndpointClient(); 50 | } 51 | } 52 | 53 | private void createGlobalEndpointClient() { 54 | log.info("Running the application with global STS endpoint."); 55 | stsClient = AWSSecurityTokenServiceClientBuilder 56 | .standard() 57 | .build(); 58 | } 59 | 60 | @Override 61 | public AssumeRoleResult assumeRole(AssumeRoleRequest assumeRoleRequest) { 62 | return stsClient.assumeRole(assumeRoleRequest); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/credentials/STSCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.credentials; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazonaws.AmazonClientException; 8 | import com.amazonaws.AmazonServiceException; 9 | import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; 10 | import com.amazonaws.regions.Region; 11 | import com.amazonaws.regions.Regions; 12 | import com.amazonaws.services.securitytoken.AWSSecurityTokenService; 13 | import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; 14 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 15 | import com.amazonaws.services.securitytoken.model.AssumeRoleResult; 16 | import com.amazonaws.services.securitytoken.model.Credentials; 17 | import com.amazonaws.util.EC2MetadataUtils; 18 | import com.google.common.annotations.VisibleForTesting; 19 | import com.google.common.cache.CacheBuilder; 20 | import com.google.common.cache.CacheLoader; 21 | import com.google.common.cache.LoadingCache; 22 | import java.text.ParseException; 23 | import java.text.SimpleDateFormat; 24 | import java.time.Duration; 25 | import java.util.Date; 26 | import java.util.Optional; 27 | import java.util.TimeZone; 28 | import java.util.concurrent.ThreadLocalRandom; 29 | import javax.inject.Inject; 30 | import javax.inject.Singleton; 31 | import lombok.extern.slf4j.Slf4j; 32 | 33 | /** 34 | * Fetches credentials for {@code AssumeRoleRequest} from STS. 35 | */ 36 | @Slf4j 37 | @Singleton 38 | public class STSCredentialsProvider implements MetadataCredentialsProvider { 39 | 40 | @Inject 41 | STSClient stsClient; 42 | 43 | public static final Duration MIN_REMAINING_TIME_TO_REFRESH_CREDENTIALS = Duration.ofMinutes(10); 44 | public static final Duration MAX_RANDOM_TIME_TO_REFRESH_CREDENTIALS = Duration.ofMinutes(5); 45 | private static final int CREDENTIALS_MAP_MAX_SIZE = 20000; 46 | 47 | private final LoadingCache> credentialsCache = CacheBuilder 48 | .newBuilder().maximumSize(CREDENTIALS_MAP_MAX_SIZE) 49 | .build(new CacheLoader>() { 50 | @Override 51 | public Optional load(AssumeRoleRequest assumeRoleRequest) { 52 | return assumeRole(assumeRoleRequest); 53 | } 54 | }); 55 | 56 | /** 57 | * Create an instance of SimpleDataFormat. 58 | * SimpleDateFormat is not thread safe, so we create an instance when needed instead of using a shared one 59 | * 60 | * @return 61 | */ 62 | static SimpleDateFormat createInterceptorDateTimeFormat() { 63 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 64 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 65 | return dateFormat; 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | */ 71 | @Override 72 | public Optional getUserCredentials(AssumeRoleRequest assumeRoleRequest) { 73 | log.debug("Request to assume role {} with STS", assumeRoleRequest); 74 | Optional credentials = credentialsCache.getUnchecked(assumeRoleRequest); 75 | 76 | if (credentials.isPresent() && shouldRefresh(credentials.get())) { 77 | // TODO: we should consider using Caffeine which provides ttl at item level 78 | log.debug("Invalidating the cache for assume role {}", assumeRoleRequest); 79 | /* 80 | * In case of multiple threads reaching here, we should be alright as locking is at 81 | * segment level for both invalidate() and get() calls. 82 | */ 83 | credentialsCache.invalidate(assumeRoleRequest); 84 | credentials = credentialsCache.getUnchecked(assumeRoleRequest); 85 | } 86 | return credentials; 87 | } 88 | 89 | /** 90 | * Makes actual call to STS. 91 | * 92 | * @param assumeRoleRequest the request to assume 93 | * @return an {@code Optional} containing {@link EC2MetadataUtils.IAMSecurityCredential} 94 | */ 95 | private Optional assumeRole(AssumeRoleRequest assumeRoleRequest) { 96 | log.info("Need to assume role {} with STS", assumeRoleRequest); 97 | try { 98 | AssumeRoleResult assumeRoleResult = stsClient.assumeRole(assumeRoleRequest); 99 | EC2MetadataUtils.IAMSecurityCredential credentials = createIAMSecurityCredential(assumeRoleResult.getCredentials()); 100 | log.debug("Procured credentials from STS for assume role {}", assumeRoleRequest); 101 | return Optional.of(credentials); 102 | } catch (AmazonServiceException ase) { 103 | // This is an internal server error. 104 | log.error("AWS Service exception {}", ase.getErrorMessage(), ase); 105 | throw ase; 106 | } catch (AmazonClientException ace) { 107 | log.error("AWS Client exception {}", ace.getMessage(), ace); 108 | } 109 | return Optional.empty(); 110 | } 111 | 112 | private EC2MetadataUtils.IAMSecurityCredential createIAMSecurityCredential(Credentials credentials) { 113 | EC2MetadataUtils.IAMSecurityCredential iamCredential = new EC2MetadataUtils.IAMSecurityCredential(); 114 | iamCredential.accessKeyId = credentials.getAccessKeyId(); 115 | iamCredential.secretAccessKey = credentials.getSecretAccessKey(); 116 | iamCredential.token = credentials.getSessionToken(); 117 | iamCredential.code = "Success"; 118 | iamCredential.type = "AWS-HMAC"; 119 | iamCredential.expiration = createInterceptorDateTimeFormat().format(credentials.getExpiration()); 120 | 121 | long nowTs = System.currentTimeMillis(); 122 | Date now = new Date(nowTs); 123 | iamCredential.lastUpdated = createInterceptorDateTimeFormat().format(now); 124 | return iamCredential; 125 | } 126 | 127 | /** 128 | * Determines if we need to refresh the cached credentials. 129 | *

130 | * The credentials are refreshed if we don't have any cached credentials, or if the 131 | * current time + 132 | * {@link STSCredentialsProvider#MIN_REMAINING_TIME_TO_REFRESH_CREDENTIALS} + some random time in range 133 | * [0, {@link STSCredentialsProvider#MAX_RANDOM_TIME_TO_REFRESH_CREDENTIALS}) is 134 | * greater than the expiration of cached credentials. 135 | * 136 | * @param credentials the cached credentials 137 | * @return {@code true} if we need to assume role with STS, else {@code false} 138 | */ 139 | private boolean shouldRefresh(EC2MetadataUtils.IAMSecurityCredential credentials) { 140 | try { 141 | Date expirationDate = createInterceptorDateTimeFormat().parse(credentials.expiration); 142 | return getRandomTimeInRange() + System.currentTimeMillis() > expirationDate.getTime(); 143 | } catch (ParseException ex) { 144 | log.error("Unable to parse the expiration in the cached assume role credentials. Refreshing credentials anyway.", ex); 145 | return true; 146 | } 147 | } 148 | 149 | @VisibleForTesting 150 | public long getRandomTimeInRange() { 151 | long minTimeMs = MIN_REMAINING_TIME_TO_REFRESH_CREDENTIALS.toMillis(); 152 | long maxRandomTimeMs = MAX_RANDOM_TIME_TO_REFRESH_CREDENTIALS.toMillis(); 153 | 154 | return minTimeMs + ThreadLocalRandom.current().nextLong(maxRandomTimeMs); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/mapping/DefaultUserRoleMapperImpl.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.mapping; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.system.PrincipalResolver; 8 | import com.amazon.aws.emr.common.system.factory.PrincipalResolverFactory; 9 | import com.amazon.aws.emr.model.PrincipalRoleMapping; 10 | import com.amazon.aws.emr.model.PrincipalRoleMappings; 11 | import com.amazon.aws.emr.rolemapper.UserRoleMapperProvider; 12 | import com.amazonaws.AmazonClientException; 13 | import com.amazonaws.services.s3.AmazonS3; 14 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 15 | import com.amazonaws.services.s3.model.GetObjectRequest; 16 | import com.amazonaws.services.s3.model.ObjectMetadata; 17 | import com.amazonaws.services.s3.model.S3Object; 18 | import com.amazonaws.services.s3.model.S3ObjectInputStream; 19 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 20 | import com.google.gson.FieldNamingPolicy; 21 | import com.google.gson.Gson; 22 | import com.google.gson.GsonBuilder; 23 | import java.util.Properties; 24 | import lombok.NoArgsConstructor; 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | import java.io.BufferedReader; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.nio.charset.StandardCharsets; 32 | import java.util.Collections; 33 | import java.util.HashMap; 34 | import java.util.List; 35 | import java.util.Map; 36 | import java.util.Objects; 37 | import java.util.Optional; 38 | 39 | /** 40 | * Default implementation to read mapping from S3 in JSON format. 41 | * The format for the JSON can be found in {@code PrincipalRoleMappings}. 42 | */ 43 | @NoArgsConstructor 44 | @Slf4j 45 | public class DefaultUserRoleMapperImpl extends S3BasedUserMappingImplBase 46 | implements UserRoleMapperProvider { 47 | 48 | static final AmazonS3 s3Client = AmazonS3ClientBuilder.standard().build(); 49 | private static final Gson GSON = new GsonBuilder() 50 | .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) 51 | .setPrettyPrinting() 52 | .create(); 53 | 54 | 55 | private final Map userRoleMapping = new HashMap<>(); 56 | private final Map groupRoleMapping = new HashMap<>(); 57 | 58 | private PrincipalResolver principalResolver; 59 | 60 | public DefaultUserRoleMapperImpl(String bucketName, String key, PrincipalResolver principalResolver) { 61 | this.bucketName = Objects.requireNonNull(bucketName); 62 | 63 | // TODO: We may relax this to allow null value. In case of null value, parse all keys under above bucket 64 | this.key = Objects.requireNonNull(key); 65 | this.etag = null; 66 | this.principalResolver = Objects.requireNonNull(principalResolver); 67 | } 68 | 69 | /** 70 | * Inits the mapper. 71 | */ 72 | public void init(Map configMap) { 73 | } 74 | 75 | /** 76 | * @param username the user whose mapping we want. 77 | * Username mapping takes precedence over group name mapping. 78 | * If multiple group name mappings exist, then the first one is returned. 79 | * @return an {@code Optional} of {@code AssumeRoleRequest} 80 | */ 81 | public Optional getMapping(String username) { 82 | // Consult if we have a mapping with username 83 | AssumeRoleRequest assumeRoleRequest = userRoleMapping.get(username); 84 | if (assumeRoleRequest != null) { 85 | log.debug("Usermapping found for {} as {}", username, assumeRoleRequest); 86 | return Optional.of(assumeRoleRequest); 87 | } 88 | log.debug("No user mapping found for {}. Checking with group mapping.", username); 89 | Optional> groups = principalResolver.getGroups(username); 90 | 91 | return groups.orElse(Collections.emptyList()).stream() 92 | .filter(group -> groupRoleMapping.get(group) != null) 93 | .map(group -> { 94 | log.debug("Mapped {} with group membership of {}", username, group); 95 | return groupRoleMapping.get(group); 96 | }) 97 | .findFirst(); 98 | } 99 | 100 | /** 101 | * Populates the internal maps with the mapping in S3. 102 | * The format for the JSON can be found in {@code PrincipalRoleMappings}. 103 | * 104 | * @param jsonString the S3 JSON represented as a String. 105 | */ 106 | void processFile(String jsonString) { 107 | log.info("Received the following JSON {}", jsonString); 108 | PrincipalRoleMappings principalRoleMappings = GSON.fromJson(jsonString, PrincipalRoleMappings.class); 109 | // Clear the old mapping now since we found a new valid mapping! 110 | userRoleMapping.clear(); 111 | groupRoleMapping.clear(); 112 | 113 | for (PrincipalRoleMapping principalRoleMapping : principalRoleMappings.getPrincipalRoleMappings()) { 114 | if (principalRoleMapping == null) { 115 | log.info("Invalid record!"); 116 | continue; 117 | } 118 | String principal = principalRoleMapping.getUsername() != null ? principalRoleMapping.getUsername() : 119 | principalRoleMapping.getGroupname(); 120 | if (principal == null) { 121 | log.info("Invalid record containing no username or groupname"); 122 | continue; 123 | } 124 | String roleArn = principalRoleMapping.getRoleArn(); 125 | if (roleArn == null) { 126 | log.info("Invalid record containing no role ARN"); 127 | continue; 128 | } 129 | AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest() 130 | .withRoleArn(principalRoleMapping.getRoleArn()) 131 | .withRoleSessionName(principal) // Use principal as session name 132 | .withDurationSeconds(principalRoleMapping.getDurationSeconds()) 133 | .withPolicy(principalRoleMapping.getPolicy()) 134 | .withSerialNumber(principalRoleMapping.getSerialNumber()) 135 | .withExternalId(principalRoleMapping.getExternalId()); 136 | if (principalRoleMapping.getUsername() != null) { 137 | userRoleMapping.put(principal, assumeRoleRequest); 138 | } else { 139 | groupRoleMapping.put(principal, assumeRoleRequest); 140 | } 141 | log.info("Mapped {} to {}", principal, assumeRoleRequest); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/mapping/ManagedPolicyBasedUserRoleMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.mapping; 2 | 3 | import com.amazon.aws.emr.common.Constants; 4 | import com.amazon.aws.emr.common.system.PrincipalResolver; 5 | import com.amazon.aws.emr.model.PrincipalPolicyMapping; 6 | import com.amazon.aws.emr.model.PrincipalPolicyMappings; 7 | import com.amazon.aws.emr.rolemapper.UserRoleMapperProvider; 8 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 9 | import com.amazonaws.services.securitytoken.model.PolicyDescriptorType; 10 | import com.google.common.annotations.VisibleForTesting; 11 | import com.google.gson.FieldNamingPolicy; 12 | import com.google.gson.Gson; 13 | import com.google.gson.GsonBuilder; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.Optional; 21 | import lombok.NoArgsConstructor; 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | /** 25 | * Mapping impl based on union of policies after principal resolution. 26 | */ 27 | @Slf4j 28 | @NoArgsConstructor 29 | public class ManagedPolicyBasedUserRoleMapperImpl extends S3BasedUserMappingImplBase 30 | implements UserRoleMapperProvider { 31 | 32 | @VisibleForTesting 33 | static String DEFAULT_NO_MATCH_POLICY_ARN = "arn:aws:iam::aws:policy/AWSDenyAll"; 34 | 35 | private PrincipalResolver principalResolver; 36 | private String roleArn; 37 | private String noMatchPolicyArn = DEFAULT_NO_MATCH_POLICY_ARN; 38 | 39 | private final Map> principalRoleMapping = new HashMap<>(); 40 | 41 | private static final Gson GSON = new GsonBuilder() 42 | .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) 43 | .setPrettyPrinting() 44 | .create(); 45 | 46 | public ManagedPolicyBasedUserRoleMapperImpl(String bucketName, String key, 47 | PrincipalResolver principalResolver) { 48 | this.bucketName = Objects.requireNonNull(bucketName); 49 | 50 | // TODO: We may relax this to allow null value. In case of null value, parse all keys under above bucket 51 | this.key = Objects.requireNonNull(key); 52 | this.etag = null; 53 | this.principalResolver = Objects.requireNonNull(principalResolver); 54 | } 55 | 56 | @Override 57 | public void init(Map configMap) { 58 | roleArn = Objects.requireNonNull(configMap.get(Constants.ROLE_MAPPING_ROLE_ARN)); 59 | } 60 | 61 | @Override 62 | public Optional getMapping(String username) { 63 | log.debug("Got request to map user {}", username); 64 | 65 | List principals = new ArrayList<>(); 66 | List policyDescriptorTypes = new ArrayList<>(); 67 | 68 | principals.add(username); 69 | Optional> groups = principalResolver.getGroups(username); 70 | if (groups.isPresent()) { 71 | principals.addAll(groups.get()); 72 | } 73 | 74 | log.debug("Groups user belongs to is {}", groups.orElse(Collections.EMPTY_LIST)); 75 | 76 | principals.stream() 77 | .map(principal -> principalRoleMapping.getOrDefault(principal, Collections.emptyList())) 78 | .filter(policies -> !policies.isEmpty()) 79 | .flatMap(List::stream) 80 | .distinct() 81 | .forEach(policyDescriptorTypes::add); 82 | 83 | if (policyDescriptorTypes.isEmpty()) { 84 | if (noMatchPolicyArn != null && noMatchPolicyArn.length() > 0) { 85 | log.debug("Found no mappings for this user. Returning credentials with default policy arn"); 86 | policyDescriptorTypes.add(new PolicyDescriptorType().withArn(noMatchPolicyArn)); 87 | } else { 88 | return Optional.empty(); 89 | } 90 | } 91 | 92 | log.debug("Policies mapped for user: {}", policyDescriptorTypes); 93 | 94 | AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest() 95 | .withRoleArn(roleArn) 96 | .withRoleSessionName(username) 97 | .withPolicyArns(policyDescriptorTypes); 98 | return Optional.of(assumeRoleRequest); 99 | } 100 | 101 | @Override 102 | void processFile(String jsonString) { 103 | log.info("Received the following JSON {}", jsonString); 104 | PrincipalPolicyMappings principalPolicyMappings = GSON 105 | .fromJson(jsonString, PrincipalPolicyMappings.class); 106 | // Clear the old mapping now since we found a new valid mapping! 107 | principalRoleMapping.clear(); 108 | noMatchPolicyArn = DEFAULT_NO_MATCH_POLICY_ARN; 109 | 110 | if (principalPolicyMappings.getNoMatchPolicyArn() != null) { 111 | noMatchPolicyArn = principalPolicyMappings.getNoMatchPolicyArn(); 112 | } 113 | log.info("No-Match Policy ARN is : " + noMatchPolicyArn); 114 | 115 | for (PrincipalPolicyMapping principalPolicyMapping : principalPolicyMappings 116 | .getPrincipalPolicyMappings()) { 117 | if (!isValidMapping(principalPolicyMapping)) { 118 | log.info("Invalid record!"); 119 | continue; 120 | } 121 | 122 | String principal = 123 | principalPolicyMapping.getUsername() != null ? principalPolicyMapping.getUsername() : 124 | principalPolicyMapping.getGroupname(); 125 | 126 | List policyDescriptorTypes = new ArrayList<>(); 127 | principalPolicyMapping.getPolicyArns().stream() 128 | .map(p -> new PolicyDescriptorType().withArn(p)) 129 | .forEach(policyDescriptorTypes::add); 130 | 131 | principalRoleMapping.put(principal, policyDescriptorTypes); 132 | 133 | log.info("Mapped {} to {}", principal, principalPolicyMapping.getPolicyArns()); 134 | } 135 | } 136 | 137 | boolean isValidMapping(PrincipalPolicyMapping principalPolicyMapping) { 138 | if (principalPolicyMapping == null) { 139 | log.info("Invalid record!"); 140 | return false; 141 | } 142 | String principal = 143 | principalPolicyMapping.getUsername() != null ? principalPolicyMapping.getUsername() : 144 | principalPolicyMapping.getGroupname(); 145 | if (principal == null) { 146 | log.info("Invalid record containing no username or groupname {}", principalPolicyMapping); 147 | return false; 148 | } 149 | 150 | if (principalPolicyMapping.getPolicyArns() == null || principalPolicyMapping.getPolicyArns().isEmpty()) { 151 | log.info("Invalid record containing no policy {}", principalPolicyMapping); 152 | return false; 153 | } 154 | return true; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/mapping/MappingInvoker.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.mapping; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.Constants; 8 | import com.amazon.aws.emr.common.system.PrincipalResolver; 9 | import com.amazon.aws.emr.rolemapper.UserRoleMapperProvider; 10 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 11 | import com.google.common.annotations.VisibleForTesting; 12 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 13 | import java.lang.reflect.Constructor; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.util.Optional; 16 | import java.util.concurrent.Executors; 17 | import java.util.concurrent.ScheduledExecutorService; 18 | import java.util.concurrent.ThreadFactory; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.concurrent.locks.Lock; 21 | import java.util.concurrent.locks.ReentrantReadWriteLock; 22 | import javax.annotation.PostConstruct; 23 | import javax.inject.Inject; 24 | import lombok.extern.slf4j.Slf4j; 25 | import org.glassfish.hk2.api.Immediate; 26 | 27 | /** 28 | * Maps username to the {@code AssumeRoleRequest}. 29 | */ 30 | @Slf4j 31 | @Immediate 32 | public class MappingInvoker { 33 | // The mapping would be read many times, but changed quite infrequently! 34 | // Hence we don't block readers if there is no change in mapping. 35 | private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); 36 | private final Lock readLockInRwLock = rwLock.readLock(); 37 | private final Lock writeLockInRwLock = rwLock.writeLock(); 38 | 39 | UserRoleMapperProvider roleMapperProvider; 40 | 41 | @Inject 42 | ApplicationConfiguration applicationConfiguration; 43 | 44 | @Inject 45 | PrincipalResolver principalResolver; 46 | 47 | /** 48 | * Constructs a mapper object via reflection and delegates calls to it. 49 | * Also creates a thread to refresh mappings. 50 | */ 51 | @PostConstruct 52 | void init() { 53 | try { 54 | String className = applicationConfiguration.getProperty(Constants.ROLE_MAPPER_CLASS, 55 | Constants.ROLE_MAPPING_DEFAULT_CLASSNAME); 56 | log.info("Trying to load {}", className); 57 | if (isS3BasedProviderImpl(className)) { 58 | // For our default mapper implementation we need at least the S3 bucket name and key 59 | Constructor c = Class.forName(className) 60 | .getConstructor(String.class, String.class, PrincipalResolver.class); 61 | String bucketName = applicationConfiguration 62 | .getProperty(Constants.ROLE_MAPPING_S3_BUCKET, null); 63 | String key = applicationConfiguration 64 | .getProperty(Constants.ROLE_MAPPING_S3_KEY, null); 65 | roleMapperProvider = (UserRoleMapperProvider) c 66 | .newInstance(bucketName, key, principalResolver); 67 | log.info("Successfully created the mapper using {}/{}", bucketName, key); 68 | } else { 69 | Class clazz = Class.forName(className); 70 | roleMapperProvider = (UserRoleMapperProvider) clazz.newInstance(); 71 | } 72 | roleMapperProvider.init(applicationConfiguration.asMap()); 73 | log.info("Initialized the mapper."); 74 | } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 75 | log.error("Could not load the mapper " + e.getMessage()); 76 | throw new RuntimeException("Could not load the mapper class", e); 77 | } catch (Throwable t) { 78 | log.error("Could not load the mapper " + t.getMessage()); 79 | throw new RuntimeException("Could not load the mapper class", t); 80 | } 81 | int refreshIntervalMins = Integer.parseInt(applicationConfiguration.getProperty 82 | (Constants.ROLE_MAPPPING_REFRESH_INTERVAL_MIN, Constants.ROLE_MAPPPING_DEFAULT_REFRESH_INTERVAL_MIN)); 83 | createRefreshTask(Math.max(Constants.ROLE_MAPPING_MIN_REFRESH_INTERVAL_MIN, refreshIntervalMins)); 84 | } 85 | 86 | @VisibleForTesting 87 | public boolean isS3BasedProviderImpl(String className) { 88 | return className.equals(Constants.ROLE_MAPPING_DEFAULT_CLASSNAME) || 89 | className.equals(Constants.ROLE_MAPPING_MANAGED_POLICY_CLASSNAME); 90 | } 91 | 92 | /** 93 | * Maps a user to an {@code Optional} of {@link AssumeRoleRequest}. 94 | * This is invoked by many threads and we employ a reentrant read lock 95 | * to stay unblocked as long as there is no need to refresh mapping. 96 | * 97 | * @param username 98 | * @return 99 | */ 100 | public Optional map(String username) { 101 | readLockInRwLock.lock(); 102 | try { 103 | Optional assumeRoleRequest = roleMapperProvider.getMapping(username); 104 | if (assumeRoleRequest.isPresent() && applicationConfiguration.isSetSourceIdentityEnabled()) { 105 | assumeRoleRequest.get().setSourceIdentity(username); 106 | } 107 | log.debug("Found mapping for {} as {}", username, assumeRoleRequest); 108 | return assumeRoleRequest; 109 | } catch (Throwable t) { 110 | // We are running some custom code that could throw anything. 111 | log.error("Got exception in getting mapping for {}", username, t); 112 | return Optional.empty(); 113 | } finally { 114 | readLockInRwLock.unlock(); 115 | } 116 | } 117 | 118 | /** 119 | * Creates a thread that runs the user provided refresh method periodically. 120 | * It acquires a write lock and reloads the mapping. 121 | * 122 | * @param refreshIntervalMins 123 | */ 124 | private void createRefreshTask(int refreshIntervalMins) { 125 | ThreadFactory threadFactory = new ThreadFactoryBuilder() 126 | .setNameFormat("refresh-mapping-%d") 127 | .setDaemon(true) 128 | .build(); 129 | ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(threadFactory); 130 | exec.scheduleAtFixedRate(() -> { 131 | writeLockInRwLock.lock(); 132 | try { 133 | log.debug("Refreshing the user role mapping."); 134 | roleMapperProvider.refresh(); 135 | } catch (Throwable t) { 136 | // We are running some custom code that could throw anything. 137 | log.error("Got an error while refreshing", t); 138 | } finally { 139 | writeLockInRwLock.unlock(); 140 | } 141 | }, 0, refreshIntervalMins, TimeUnit.MINUTES); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/mapping/S3BasedUserMappingImplBase.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.mapping; 2 | 3 | import com.amazonaws.AmazonClientException; 4 | import com.amazonaws.services.s3.AmazonS3; 5 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 6 | import com.amazonaws.services.s3.model.GetObjectRequest; 7 | import com.amazonaws.services.s3.model.ObjectMetadata; 8 | import com.amazonaws.services.s3.model.S3Object; 9 | import com.amazonaws.services.s3.model.S3ObjectInputStream; 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | import java.nio.charset.StandardCharsets; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | /** 18 | * Common functionality to fetch and retrieve mappings stored in S3. 19 | */ 20 | @Slf4j 21 | public abstract class S3BasedUserMappingImplBase { 22 | 23 | protected String bucketName; 24 | protected String key; 25 | protected String etag; 26 | protected static AmazonS3 s3Client = null; 27 | 28 | public void refresh() { 29 | log.debug("Checking if need to load mapping again from S3 from {}/{}", bucketName, key); 30 | ObjectMetadata objectMetadata = getS3Client().getObjectMetadata(bucketName, key); 31 | if (objectMetadata.getETag().equals(etag)) { 32 | log.debug("Nothing to do as current etag {} matches the last one.", objectMetadata.getETag()); 33 | } else { 34 | log.info("Seems we have new mapping - reload it."); 35 | readMapping(); 36 | log.info("Done with the reload."); 37 | } 38 | } 39 | 40 | /** 41 | * Process the contents of S3 mapping file. 42 | * 43 | * @param json the contents of the S3 mapping. 44 | */ 45 | abstract void processFile(String json); 46 | 47 | private void readMapping() { 48 | log.info("Load the mapping from S3 from {}/{}", bucketName, key); 49 | try (S3Object s3object = getS3Client().getObject(new GetObjectRequest( 50 | bucketName, key))) { 51 | S3ObjectInputStream s3InputStream = s3object.getObjectContent(); 52 | String jsonString = null; 53 | try { 54 | jsonString = getS3FileAsString(s3InputStream); 55 | } catch (IOException e) { 56 | throw new RuntimeException("Could not fetch the mapping file from S3.", e); 57 | } 58 | // Update the ETag 59 | etag = s3object.getObjectMetadata().getETag(); 60 | processFile(jsonString); 61 | } catch (AmazonClientException ace) { 62 | log.error("AWS exception {}", ace.getMessage(), ace); 63 | } catch (IOException e) { 64 | log.error("Could not load mapping from S3", e); 65 | } 66 | } 67 | 68 | private static String getS3FileAsString(InputStream is) throws IOException { 69 | if (is == null) { 70 | return null; 71 | } 72 | StringBuilder sb = new StringBuilder(); 73 | try (BufferedReader reader = new BufferedReader( 74 | new InputStreamReader(is, StandardCharsets.UTF_8))) { 75 | String line; 76 | while ((line = reader.readLine()) != null) { 77 | sb.append(line); 78 | } 79 | return sb.toString(); 80 | } 81 | } 82 | 83 | synchronized static AmazonS3 getS3Client() { 84 | if (s3Client == null) { 85 | s3Client = AmazonS3ClientBuilder 86 | .standard() 87 | .build(); 88 | } 89 | return s3Client; 90 | } 91 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/model/Group.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Value; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * Models a POSIX group. 17 | */ 18 | @AllArgsConstructor 19 | @Builder 20 | @Value 21 | public class Group { 22 | 23 | String name; 24 | Integer gid; 25 | List users; 26 | 27 | public static Group createFromGroupEntry(String line) { 28 | String[] items = line.split(":"); 29 | 30 | if (items.length < 3) { 31 | throw new IllegalArgumentException("Need at least 3 items from file and there's only: " + items.length); 32 | } 33 | 34 | String name = items[0]; 35 | int gid = Integer.parseInt(items[2]); 36 | // Some groups may not have any members 37 | List users = (items.length == 4) ? Arrays.stream(items[3].split(",")).collect(Collectors.toList()) : 38 | new ArrayList<>(); 39 | return new Group(name, gid, users); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/model/PrincipalPolicyMapping.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Data 16 | public class PrincipalPolicyMapping { 17 | 18 | @SerializedName("username") 19 | private String username; 20 | 21 | @SerializedName("groupname") 22 | private String groupname; 23 | 24 | @SerializedName("policies") 25 | private java.util.List policyArns; 26 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/model/PrincipalPolicyMappings.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | import lombok.Data; 8 | 9 | @Data 10 | public class PrincipalPolicyMappings { 11 | @SerializedName("NoMatchPolicyArn") 12 | String noMatchPolicyArn; 13 | 14 | @SerializedName("PrincipalPolicyMappings") 15 | PrincipalPolicyMapping[] principalPolicyMappings; 16 | } 17 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/model/PrincipalRoleMapping.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Data 16 | public class PrincipalRoleMapping { 17 | 18 | @SerializedName("username") 19 | private String username; 20 | 21 | @SerializedName("groupname") 22 | private String groupname; 23 | 24 | @SerializedName("rolearn") 25 | private String roleArn; 26 | 27 | @SerializedName("session") 28 | private String roleSessionName; 29 | 30 | @SerializedName("policies") 31 | private java.util.List policyArns; 32 | 33 | @SerializedName("textpolicy") 34 | private String policy; 35 | 36 | @SerializedName("duration") 37 | private Integer durationSeconds; 38 | 39 | @SerializedName("externalid") 40 | private String externalId; 41 | 42 | @SerializedName("serialnumber") 43 | private String serialNumber; 44 | 45 | @SerializedName("tokencode") 46 | private String tokenCode; 47 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/model/PrincipalRoleMappings.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | import lombok.Data; 8 | 9 | @Data 10 | public class PrincipalRoleMappings { 11 | @SerializedName("PrincipalRoleMappings") 12 | PrincipalRoleMapping[] principalRoleMappings; 13 | } 14 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/model/User.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Value; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * Model for a user. 13 | */ 14 | @AllArgsConstructor 15 | @Builder 16 | @Value 17 | @Slf4j 18 | public class User { 19 | String name; 20 | Integer uid; 21 | Integer gid; 22 | String comment; 23 | String home; 24 | String shell; 25 | 26 | public static User createFromPasswdEntry(String line) { 27 | String[] items = line.split(":"); 28 | 29 | if (items.length != 7) { 30 | log.error("Incorrect number of fields found in passwd file {}. Please check man 5 passwd", items.length); 31 | throw new IllegalArgumentException("Need 7 items from file and there's only: " + items.length); 32 | } 33 | 34 | String name = items[0]; 35 | // items[1] is encrypted password. x character indicates that encrypted password is stored in /etc/shadow file. 36 | int uid = Integer.parseInt(items[2]); 37 | int gid = Integer.parseInt(items[3]); 38 | String comment = items[4]; 39 | String home = items[5]; 40 | String shell = items[6]; 41 | 42 | return new User(name, uid, gid, comment, home, shell); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/ws/ImmediateFeature.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.ws; 5 | 6 | import org.glassfish.hk2.api.ServiceLocator; 7 | import org.glassfish.hk2.utilities.ServiceLocatorUtilities; 8 | 9 | import javax.inject.Inject; 10 | import javax.ws.rs.core.Feature; 11 | import javax.ws.rs.core.FeatureContext; 12 | 13 | /** 14 | * Enables {@link ImmediateFeature} in this Jersey application. 15 | */ 16 | public class ImmediateFeature implements Feature { 17 | 18 | @Inject 19 | public ImmediateFeature(ServiceLocator locator) { 20 | ServiceLocatorUtilities.enableImmediateScope(locator); 21 | } 22 | 23 | @Override 24 | public boolean configure(FeatureContext context) { 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/ws/UserRoleMapperApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.ws; 5 | 6 | import org.glassfish.jersey.server.ResourceConfig; 7 | 8 | import javax.inject.Inject; 9 | 10 | /** 11 | * The Jersey servlet application. 12 | */ 13 | public class UserRoleMapperApplication extends ResourceConfig { 14 | 15 | @Inject 16 | public UserRoleMapperApplication() { 17 | String[] pkgs = new String[]{"com.amazon.aws.emr.api", "com.amazon.aws.emr.mapping"}; 18 | packages(pkgs); 19 | register(ImmediateFeature.class); 20 | register(new UserRoleMapperBinder()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/java/com/amazon/aws/emr/ws/UserRoleMapperBinder.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.ws; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.system.PrincipalResolver; 8 | import com.amazon.aws.emr.common.system.factory.PrincipalResolverFactory; 9 | import com.amazon.aws.emr.credentials.MetadataCredentialsProvider; 10 | import com.amazon.aws.emr.credentials.STSClient; 11 | import com.amazon.aws.emr.credentials.STSClientImpl; 12 | import com.amazon.aws.emr.credentials.STSCredentialsProvider; 13 | import com.amazon.aws.emr.mapping.MappingInvoker; 14 | import com.amazon.aws.emr.common.system.user.LinuxUserIdService; 15 | import com.amazon.aws.emr.common.system.user.UserIdService; 16 | import org.glassfish.hk2.api.Immediate; 17 | import org.glassfish.hk2.utilities.binding.AbstractBinder; 18 | 19 | import javax.inject.Singleton; 20 | 21 | /** 22 | * Bindings used in {@link UserRoleMapperApplication}. 23 | */ 24 | public class UserRoleMapperBinder extends AbstractBinder { 25 | 26 | @Override 27 | protected void configure() { 28 | bind(LinuxUserIdService.class).to(UserIdService.class); 29 | bind(MappingInvoker.class).to(MappingInvoker.class).in(Immediate.class); 30 | bind(STSCredentialsProvider.class).to(MetadataCredentialsProvider.class).in(Singleton.class); 31 | bind(ApplicationConfiguration.class).to(ApplicationConfiguration.class).in(Immediate.class); 32 | bind(STSClientImpl.class).to(STSClient.class).in(Immediate.class); 33 | bindFactory(PrincipalResolverFactory.class).to(PrincipalResolver.class).in(Singleton.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/main/resources/jar-with-deps-with-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | jar-with-dependencies-and-exclude-classes 6 | 7 | jar 8 | 9 | false 10 | 11 | 12 | / 13 | false 14 | true 15 | runtime 16 | 17 | org.bytedeco:systems:jar:macosx-x86_64:${javacpp.version} 18 | org.bytedeco:systems:jar:windows-x86:${javacpp.version} 19 | org.bytedeco:systems:jar:windows-x86_64:${javacpp.version} 20 | org.bytedeco:systems:jar:linux-ppc64le:${javacpp.version} 21 | org.bytedeco:systems:jar:linux-arm64:${javacpp.version} 22 | org.bytedeco:systems:jar:linux-armhf:${javacpp.version} 23 | 24 | 25 | 26 | 27 | 28 | / 29 | ${project.build.outputDirectory} 30 | 31 | 32 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test-data/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootCategory=INFO,console 2 | log4j.additivity.com.amazon.emr=false 3 | log4j.appender.console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.console.target=System.out 5 | log4j.appender.console.immediateFlush=true 6 | log4j.appender.console.encoding=UTF-8 7 | #log4j.appender.console.threshold=warn 8 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.console.layout.conversionPattern=%d [%t] %-5p %c - %m%n 10 | 11 | log4j.logger.com.amazon.emr=DEBUG,console 12 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test-data/tcp: -------------------------------------------------------------------------------- 1 | 62: B5061EAC:9BF0 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 505 0 113374014 1 ffff88815175e000 20 4 28 10 -1 2 | 63: B5061EAC:9BA4 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 509 0 113373986 1 ffff88815175d000 20 4 28 10 -1 3 | 64: B5061EAC:9BB4 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 504 0 113373996 1 ffff888151759800 20 4 28 10 -1 4 | 65: B5061EAC:9BA2 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 506 0 113372115 1 ffff88812489a800 20 4 28 10 -1 5 | 66: B5061EAC:B7D7 B5061EAC:1F54 01 00000000:00000000 02:000AFADD 00000000 495 0 113373069 2 ffff8883f76a6000 20 4 20 10 -1 6 | 67: B5061EAC:9B8E FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 509 0 113372103 1 ffff88812489e800 20 4 28 10 -1 7 | 68: B5061EAC:CD58 469D2E34:01BB 06 00000000:00000000 03:00000AB0 00000000 0 0 0 3 ffff888220ec81f0 8 | 69: B5061EAC:9BF8 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 506 0 113373162 1 ffff888111511000 20 4 28 10 -1 9 | 70: B5061EAC:9BF4 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 505 0 113375452 1 ffff88814de53800 20 4 28 10 -1 10 | 71: B5061EAC:9BBE FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 506 0 113373140 1 ffff8883f76a5000 20 4 28 10 -1 11 | 72: B5061EAC:0016 3482CC47:CFFA 01 00000000:00000000 02:000236B3 00000000 0 0 111280469 2 ffff8882e4942800 32 4 23 10 52 12 | 73: B5061EAC:9BC4 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 506 0 113373144 1 ffff8883f76a3800 20 4 28 10 -1 13 | 74: B5061EAC:9BAA FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 509 0 113372119 1 ffff88812489f000 20 4 28 10 -1 14 | 75: B5061EAC:9BD4 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 506 0 113375441 1 ffff88814de54000 20 4 28 10 -1 15 | 76: B5061EAC:9BB8 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 509 0 113373999 1 ffff88815175c800 20 4 28 10 -1 16 | 77: B5061EAC:1F54 B5061EAC:E7E9 01 00000000:00000000 02:000AFB2C 00000000 493 0 113372058 2 ffff88812489b000 21 4 25 10 -1 17 | 78: B5061EAC:9BD6 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 505 0 113375443 1 ffff88814de57000 20 4 28 10 -1 18 | 79: B5061EAC:9BEE FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 505 0 113375448 1 ffff88814de55000 20 4 28 10 -1 19 | 80: B5061EAC:9BF2 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 506 0 113375450 1 ffff88814de53000 20 4 28 10 -1 20 | 81: B5061EAC:1F54 B5061EAC:B7D7 01 00000000:00000000 02:000AFADD 00000000 493 0 113372051 2 ffff88812489b800 20 4 25 10 -1 21 | 82: B5061EAC:9B88 FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 509 0 113373975 1 ffff88815175b800 20 4 28 10 -1 22 | 83: 0100007F:A468 0100007F:26D8 01 00000000:00000000 00:00000000 00000000 510 0 113373975 1 ffff88815175b800 20 4 28 10 -1 23 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test-data/tcp6: -------------------------------------------------------------------------------- 1 | 37: 0000000000000000FFFF00000100007F:26AD 0000000000000000FFFF00000100007F:DD72 01 00000000:00000000 00:00000000 00000000 485 0 105461962 1 ffff8881fef7cc80 20 4 31 10 -1 2 | 38: 0000000000000000FFFF0000B5061EAC:1F98 0000000000000000FFFF0000B5061EAC:A260 01 00000000:00000000 00:00000000 00000000 496 0 113378447 1 ffff8881fefcee80 20 4 29 10 -1 3 | 39: 0000000000000000FFFF0000B5061EAC:A466 0000000000000000FFFF0000FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 485 0 113697252 1 ffff8881561eaa80 20 4 30 10 -1 4 | 40: 0000000000000000FFFF0000B5061EAC:A466 0000000000000000FFFF0000FEA9FEA9:0050 01 00000000:00000000 00:00000000 00000000 485 0 113697252 1 ffff8881561eaa80 20 4 30 10 -1 5 | 41: 00000000000000000000000001000000:A469 00000000000000000000000001000000:26D8 01 00000000:00000000 00:00000000 00000000 470 0 113697252 1 ffff8881561eaa80 20 4 30 10 -1 6 | 41: 0000000000000000FFFF00000100007F:A46A 0000000000000000FFFF00000100007F:26D8 01 00000000:00000000 00:00000000 00000000 470 0 113697252 1 ffff8881561eaa80 20 4 30 10 -1 -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test-data/test-users: -------------------------------------------------------------------------------- 1 | u1:x:503:504::/home/u1:/bin/bash 2 | u2:x:504:505::/home/u2:/bin/bash 3 | u3:x:505:506::/home/u3:/bin/bash 4 | u4:x:506:507::/home/u4:/bin/bash 5 | hive:x:1001:491:Hive:/var/lib/hive:/sbin/nologin 6 | hive:x:1002:492:Presto:/var/lib/hive:/sbin/nologin -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test-data/user-role-mapper.properties: -------------------------------------------------------------------------------- 1 | rolemapper.class.name=com.amazon.aws.emr.mapping.TestUserRoleMapperImpl 2 | rolemapper.impersonation.allowed.users=hive,presto -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/common/TestConstants.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common; 5 | 6 | public class TestConstants { 7 | public static final int UNKNOWN_USERNAME_UID = 1000; 8 | public static final String EMPTY_ROLE_NAME = ""; 9 | public static final String EMPTY_ROLE_CREDENTIALS = ""; 10 | public static final int USER1_UID = 503; 11 | public static final String USER1_ROLE_NAME = "u1"; 12 | public static final String USER2_ROLE_NAME = "u2"; 13 | public static final int UNMAPPED_UID = 505; 14 | public static final String UNMAPPED_USER_NAME = "u3"; 15 | public static final int GROUP_MAPPED_UID = 506; 16 | public static final String GROUP_ROLE_NAME = "g1"; 17 | public static final int HIVE_USER_UID = 1001; 18 | public static final int PRESTO_USER_UID = 1002; 19 | } 20 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/common/system/impl/CommandBasedPrincipalResolverTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.impl; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.powermock.api.mockito.PowerMockito; 10 | import org.powermock.core.classloader.annotations.PowerMockIgnore; 11 | import org.powermock.core.classloader.annotations.PrepareForTest; 12 | import org.powermock.modules.junit4.PowerMockRunner; 13 | 14 | import java.io.ByteArrayInputStream; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.hamcrest.core.Is.is; 21 | import static org.mockito.Answers.CALLS_REAL_METHODS; 22 | import static org.mockito.ArgumentMatchers.any; 23 | import static org.mockito.ArgumentMatchers.anyList; 24 | import static org.mockito.ArgumentMatchers.anyLong; 25 | import static org.mockito.Mockito.mock; 26 | import static org.mockito.Mockito.verify; 27 | import static org.mockito.Mockito.when; 28 | import static org.mockito.Mockito.withSettings; 29 | 30 | @RunWith(PowerMockRunner.class) 31 | @PrepareForTest({ CommandBasedPrincipalResolver.class }) 32 | @PowerMockIgnore({ "javax.management.*", "javax.net.ssl.*" }) 33 | public class CommandBasedPrincipalResolverTest extends PrincipalResolverTestBase { 34 | private static final List USER_SINGLE_GRP_GET_GRPS_CMD = Arrays.asList("id", "-Gn", USER_SINGLE_GRP); 35 | private static final List USER_MULTI_GRPS_GET_GRPS_CMD = Arrays.asList("id", "-Gn", USER_MULTI_GRPS); 36 | private static final List GET_USERNAME_CMD = Arrays.asList("id", "-nu", String.valueOf(VALID_UID)); 37 | 38 | @Before 39 | public void setup() throws Exception { 40 | principalResolver = mock(CommandBasedPrincipalResolver.class, 41 | withSettings().useConstructor(TTL_SECS, TimeUnit.SECONDS).defaultAnswer(CALLS_REAL_METHODS)); 42 | setupGetUsernameMock(); 43 | setupGetGrpsMocks(); 44 | } 45 | 46 | @Test 47 | public void linuxCommandDidNotFinish_returnsNoGroups() throws Exception { 48 | ProcessBuilder mockProcessBuilder = PowerMockito.mock(ProcessBuilder.class); 49 | PowerMockito.whenNew(ProcessBuilder.class).withArguments(anyList()).thenReturn(mockProcessBuilder); 50 | 51 | Process mockProcess = mock(Process.class); 52 | when(mockProcessBuilder.start()).thenReturn(mockProcess); 53 | 54 | when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(false); 55 | assertThat(principalResolver.getLinuxGroups(USER_MULTI_GRPS).isEmpty(), is(true)); 56 | verify(mockProcess).waitFor(anyLong(), any()); 57 | } 58 | 59 | private void setupGetGrpsMocks() throws Exception { 60 | ProcessBuilder mockProcessBuilderGetSingleGrp = PowerMockito.mock(ProcessBuilder.class); 61 | ProcessBuilder mockProcessBuilderGetMultiGrp = PowerMockito.mock(ProcessBuilder.class); 62 | PowerMockito.whenNew(ProcessBuilder.class).withArguments(USER_SINGLE_GRP_GET_GRPS_CMD) 63 | .thenReturn(mockProcessBuilderGetSingleGrp); 64 | PowerMockito.whenNew(ProcessBuilder.class).withArguments(USER_MULTI_GRPS_GET_GRPS_CMD) 65 | .thenReturn(mockProcessBuilderGetMultiGrp); 66 | 67 | Process subProcessSingleGrp = mock(Process.class); 68 | Process subProcessMultiGrp = mock(Process.class); 69 | when(mockProcessBuilderGetSingleGrp.start()).thenReturn(subProcessSingleGrp); 70 | when(mockProcessBuilderGetMultiGrp.start()).thenReturn(subProcessMultiGrp); 71 | when(subProcessSingleGrp.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); 72 | when(subProcessMultiGrp.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); 73 | when(subProcessSingleGrp.getInputStream()) 74 | .thenReturn(new ByteArrayInputStream(String.join(" ", SINGLE_GRP).getBytes())); 75 | when(subProcessMultiGrp.getInputStream()) 76 | .thenReturn(new ByteArrayInputStream(String.join(" ", MULTI_GRPS).getBytes())); 77 | } 78 | 79 | private void setupGetUsernameMock() throws Exception { 80 | ProcessBuilder mockProcessBuilder = PowerMockito.mock(ProcessBuilder.class); 81 | PowerMockito.whenNew(ProcessBuilder.class).withArguments(GET_USERNAME_CMD).thenReturn(mockProcessBuilder); 82 | 83 | Process subprocess = mock(Process.class); 84 | when(mockProcessBuilder.start()).thenReturn(subprocess); 85 | when(subprocess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); 86 | when(subprocess.getInputStream()).thenReturn(new ByteArrayInputStream(VALID_USERNAME.getBytes())); 87 | } 88 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/common/system/impl/JniBasedPrincipalResolverTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.impl; 5 | 6 | import org.junit.Before; 7 | 8 | import java.util.Optional; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import static org.mockito.Answers.CALLS_REAL_METHODS; 12 | import static org.mockito.ArgumentMatchers.eq; 13 | import static org.mockito.Mockito.doReturn; 14 | import static org.mockito.Mockito.mock; 15 | import static org.mockito.Mockito.withSettings; 16 | 17 | public class JniBasedPrincipalResolverTest extends PrincipalResolverTestBase { 18 | 19 | @Before 20 | public void setup() { 21 | principalResolver = mock(JniBasedPrincipalResolver.class, 22 | withSettings().useConstructor(TTL_SECS, TimeUnit.SECONDS).defaultAnswer(CALLS_REAL_METHODS)); 23 | setupMocks(); 24 | } 25 | 26 | private void setupMocks() { 27 | doReturn(Optional.of(VALID_USERNAME)).when(principalResolver).getLinuxUsername(eq(VALID_UID)); 28 | doReturn(SINGLE_GRP).when(principalResolver).getLinuxGroups(eq(USER_SINGLE_GRP)); 29 | doReturn(MULTI_GRPS).when(principalResolver).getLinuxGroups(eq(USER_MULTI_GRPS)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/common/system/impl/PrincipalResolverTestBase.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.impl; 5 | 6 | import org.junit.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.core.Is.is; 13 | import static org.mockito.ArgumentMatchers.eq; 14 | import static org.mockito.Mockito.doReturn; 15 | import static org.mockito.Mockito.times; 16 | import static org.mockito.Mockito.verify; 17 | 18 | public class PrincipalResolverTestBase { 19 | protected static final Integer TTL_SECS = 3; 20 | 21 | protected static final int VALID_UID = 123; 22 | protected static final String VALID_USERNAME = "validUsername"; 23 | 24 | protected static final String USER_SINGLE_GRP = "userSingleGrp"; 25 | protected static final List SINGLE_GRP = Arrays.asList("grp1"); 26 | 27 | protected static final String USER_MULTI_GRPS = "userMultiGrps"; 28 | protected static final List MULTI_GRPS = Arrays.asList("grp1", "grp2", "grp3"); 29 | 30 | protected AbstractPrincipalResolver principalResolver; 31 | 32 | @Test 33 | public void validUid_returnsCorrespondingUsername() { 34 | assertThat(principalResolver.getUsername(VALID_UID).get(), is(VALID_USERNAME)); 35 | verify(principalResolver).getLinuxUsername(VALID_UID); 36 | 37 | // ensure username is returned from cache 38 | assertThat(principalResolver.getUsername(VALID_UID).get(), is(VALID_USERNAME)); 39 | assertThat(principalResolver.getUsername(VALID_UID).get(), is(VALID_USERNAME)); 40 | 41 | // both the above calls retrieve mapping from cache so there should not be any additional invocations 42 | verify(principalResolver, times(1)).getLinuxUsername(VALID_UID); 43 | } 44 | 45 | @Test 46 | public void validUsername_returnsCorrespondingGroups() { 47 | assertThat(principalResolver.getGroups(USER_SINGLE_GRP).get(), is(SINGLE_GRP)); 48 | verify(principalResolver).getLinuxGroups(USER_SINGLE_GRP); 49 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 50 | verify(principalResolver).getLinuxGroups(USER_MULTI_GRPS); 51 | } 52 | 53 | @Test 54 | public void subsequentRetrieveGroupsCall_returnGroupsFromCache() { 55 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 56 | verify(principalResolver).getLinuxGroups(USER_MULTI_GRPS); 57 | 58 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 59 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 60 | // both the above calls retrieve mapping from cache so there should not be any additional invocations 61 | verify(principalResolver, times(1)).getLinuxGroups(USER_MULTI_GRPS); 62 | } 63 | 64 | @Test 65 | public void staleGroupMapping_shouldGetRefreshed() throws InterruptedException { 66 | // populate the cache with groups 67 | doReturn(MULTI_GRPS).when(principalResolver).getLinuxGroups(eq(USER_MULTI_GRPS)); 68 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 69 | verify(principalResolver).getLinuxGroups(USER_MULTI_GRPS); 70 | 71 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 72 | // above call retrieves mapping from cache so there should not be any additional invocation 73 | verify(principalResolver, times(1)).getLinuxGroups((USER_MULTI_GRPS)); 74 | 75 | // sleep for more than ttl value for cache 76 | Thread.sleep(TTL_SECS * 1000 + 5); 77 | assertThat(principalResolver.getGroups(USER_MULTI_GRPS).get(), is(MULTI_GRPS)); 78 | verify(principalResolver, times(2)).getLinuxGroups(USER_MULTI_GRPS); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/common/system/user/LinuxUserIdServiceTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.user; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.net.URISyntaxException; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.OptionalInt; 14 | 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.core.Is.is; 17 | 18 | @Slf4j 19 | public class LinuxUserIdServiceTest { 20 | 21 | private static final int LOCAL_SERVER_PORT = 9901; 22 | 23 | private UserIdService userIdService; 24 | 25 | @Before 26 | public void setup() throws Exception { 27 | Path tcp = null; 28 | Path tcp6 = null; 29 | try { 30 | java.net.URL url = getClass().getResource("/tcp"); 31 | tcp = Paths.get(url.toURI()); 32 | } catch (URISyntaxException e) { 33 | throw new RuntimeException("Can't get tcp file"); 34 | } 35 | try { 36 | java.net.URL url = this.getClass().getResource("/tcp6"); 37 | tcp6 = Paths.get(url.toURI()); 38 | } catch (URISyntaxException e) { 39 | throw new RuntimeException("Can't get tcp6 file"); 40 | } 41 | userIdService = new LinuxUserIdService( 42 | tcp.toString(), 43 | tcp6.toString()); 44 | } 45 | 46 | @Test 47 | public void resolveUID_tcp_IMDS() { 48 | OptionalInt uid = userIdService.resolveSystemUID( 49 | "127.0.0.1", 50 | LOCAL_SERVER_PORT, 51 | "172.30.6.181", 52 | 39844, true); 53 | assertThat(uid.getAsInt(), is(509)); 54 | uid = userIdService.resolveSystemUID( 55 | "127.0.0.1", 56 | LOCAL_SERVER_PORT, 57 | "172.30.6.181", 58 | 39860, true); 59 | assertThat(uid.getAsInt(), is(504)); 60 | uid = userIdService.resolveSystemUID( 61 | "127.0.0.1", 62 | LOCAL_SERVER_PORT, 63 | "172.30.6.181", 64 | 39842, true); 65 | assertThat(uid.getAsInt(), is(506)); 66 | } 67 | 68 | @Test 69 | public void resolveUID_tcp6_IMDS() throws NoSuchFieldException, IllegalAccessException { 70 | OptionalInt uid = userIdService.resolveSystemUID( 71 | "127.0.0.1", 72 | LOCAL_SERVER_PORT, 73 | "172.30.6.181", 74 | 42086, true); 75 | assertThat(uid.getAsInt(), is(485)); 76 | } 77 | 78 | @Test 79 | public void resolveUID_unmatched_IMDS() throws NoSuchFieldException, IllegalAccessException { 80 | int nonExistingPort = 999999; 81 | OptionalInt uid = userIdService.resolveSystemUID( 82 | "127.0.0.1", 83 | LOCAL_SERVER_PORT, 84 | "127.0.0.1", 85 | nonExistingPort, true); 86 | assertThat(uid.isPresent(), is(false)); 87 | } 88 | 89 | @Test 90 | public void resolveUID_tcp_nonIMDS() { 91 | OptionalInt uid = userIdService.resolveSystemUID( 92 | "127.0.0.1", 93 | LOCAL_SERVER_PORT, 94 | "127.0.0.1", 95 | 42088, false); 96 | assertThat(uid.getAsInt(), is(510)); 97 | } 98 | 99 | @Test 100 | public void resolveUID_tcp6_nonIMDS() { 101 | OptionalInt uid = userIdService.resolveSystemUID( 102 | "127.0.0.1", 103 | LOCAL_SERVER_PORT, 104 | "127.0.0.1", 105 | 42089, false); 106 | assertThat(uid.getAsInt(), is(470)); 107 | uid = userIdService.resolveSystemUID( 108 | "127.0.0.1", 109 | LOCAL_SERVER_PORT, 110 | "127.0.0.1", 111 | 42090, false); 112 | assertThat(uid.getAsInt(), is(470)); 113 | } 114 | 115 | @Test 116 | public void resolveUID_unmatched_nonIMDS() throws NoSuchFieldException, IllegalAccessException { 117 | int nonExistingPort = 999999; 118 | OptionalInt uid = userIdService.resolveSystemUID( 119 | "127.0.0.1", 120 | LOCAL_SERVER_PORT, 121 | "127.0.0.1", 122 | nonExistingPort, false); 123 | assertThat(uid.isPresent(), is(false)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/common/system/user/TestCommandBasedPrincipalResolver.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.common.system.user; 5 | 6 | import com.amazon.aws.emr.common.system.PrincipalResolver; 7 | import com.amazon.aws.emr.common.system.impl.CommandBasedPrincipalResolver; 8 | 9 | import java.net.URISyntaxException; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | 13 | public class TestCommandBasedPrincipalResolver extends CommandBasedPrincipalResolver implements PrincipalResolver { 14 | private static final String TEST_USERS_FILE = "/test-users"; 15 | 16 | protected Path getSystemUsersFileName() { 17 | java.net.URL url = this.getClass().getResource(TEST_USERS_FILE); 18 | try { 19 | return Paths.get(url.toURI()); 20 | } catch (URISyntaxException e) { 21 | return null; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/credentials/STSCredentialsProviderTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.credentials; 5 | 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.Matchers.allOf; 8 | import static org.hamcrest.Matchers.greaterThan; 9 | import static org.hamcrest.Matchers.lessThan; 10 | import static org.hamcrest.core.Is.is; 11 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 12 | 13 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 14 | import com.amazonaws.services.securitytoken.model.AssumeRoleResult; 15 | import com.amazonaws.services.securitytoken.model.Credentials; 16 | import com.amazonaws.util.EC2MetadataUtils; 17 | import java.util.Date; 18 | import java.util.Optional; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.mockito.InjectMocks; 23 | import org.mockito.Mock; 24 | import org.mockito.Mockito; 25 | import org.powermock.api.mockito.PowerMockito; 26 | import org.powermock.core.classloader.annotations.PowerMockIgnore; 27 | import org.powermock.core.classloader.annotations.PrepareForTest; 28 | import org.powermock.modules.junit4.PowerMockRunner; 29 | 30 | @RunWith(PowerMockRunner.class) 31 | @PrepareForTest({STSCredentialsProvider.class}) 32 | @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"}) 33 | public class STSCredentialsProviderTest { 34 | 35 | private static final long ONE_HR_MS = 60 * 60 * 1000; 36 | 37 | private static final long TWO_MIN_MS = 2 * 60 * 1000; 38 | 39 | @Mock 40 | STSClient stsClient; 41 | 42 | @InjectMocks 43 | STSCredentialsProvider stsCredentialsProvider; 44 | 45 | AssumeRoleRequest assumeRoleRequest; 46 | 47 | @Before 48 | public void setup() { 49 | mockStatic(STSCredentialsProvider.class); 50 | Mockito.when(STSCredentialsProvider.createInterceptorDateTimeFormat()).thenCallRealMethod(); 51 | assumeRoleRequest = createTestAssumeRoleRequest(); 52 | } 53 | 54 | @Test 55 | public void get_credentials() { 56 | Credentials longLivedCredentials = createTestCredentials(ONE_HR_MS); 57 | Mockito.when(stsClient.assumeRole(assumeRoleRequest)).thenReturn( 58 | new AssumeRoleResult() 59 | .withCredentials(longLivedCredentials)); 60 | Optional optionalIAMSecurityCredentials = stsCredentialsProvider 61 | .getUserCredentials(assumeRoleRequest); 62 | assertThat(optionalIAMSecurityCredentials.isPresent(), is(true)); 63 | EC2MetadataUtils.IAMSecurityCredential iamSecurityCredential = optionalIAMSecurityCredentials 64 | .get(); 65 | assertThat(iamSecurityCredential.accessKeyId, is("test-access")); 66 | assertThat(iamSecurityCredential.secretAccessKey, is("test-secret")); 67 | assertThat(iamSecurityCredential.code, is("Success")); 68 | assertThat(iamSecurityCredential.code, is("Success")); 69 | assertThat(iamSecurityCredential.expiration, is( 70 | STSCredentialsProvider.createInterceptorDateTimeFormat() 71 | .format(longLivedCredentials.getExpiration()))); 72 | } 73 | 74 | @Test 75 | public void get_cached_credentials() { 76 | Credentials longLivedCredentials = createTestCredentials(ONE_HR_MS); 77 | Mockito.when(stsClient.assumeRole(assumeRoleRequest)).thenReturn( 78 | new AssumeRoleResult() 79 | .withCredentials(longLivedCredentials)); 80 | 81 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 82 | Mockito.verify(stsClient, Mockito.times(1)).assumeRole(assumeRoleRequest); 83 | // Make the second call and there should no additional Mock invocation 84 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 85 | Mockito.verify(stsClient, Mockito.times(1)).assumeRole(assumeRoleRequest); 86 | } 87 | 88 | @Test 89 | public void expired_credentials() { 90 | Credentials shortLivedTestCredentials = createTestCredentials(-1); 91 | Mockito.when(stsClient.assumeRole(assumeRoleRequest)).thenReturn( 92 | new AssumeRoleResult() 93 | .withCredentials(shortLivedTestCredentials)); 94 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 95 | /* 96 | * Why 2? 97 | * First call gets the credentials using sts as the cache is empty. 98 | * Second call is made to STS as the retrieved credentials are expired. 99 | */ 100 | Mockito.verify(stsClient, Mockito.times(2)).assumeRole(assumeRoleRequest); 101 | 102 | // Make the second call and there should no another Mock invocation 103 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 104 | Mockito.verify(stsClient, Mockito.times(3)).assumeRole(assumeRoleRequest); 105 | 106 | // Make second call, should invoke STS client again 107 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 108 | PowerMockito.verifyStatic(STSCredentialsProvider.class, Mockito.times(3)); 109 | } 110 | 111 | @Test 112 | public void about_to_expire_credentials() { 113 | Credentials shortLivedTestCredentials = createTestCredentials(TWO_MIN_MS); 114 | Mockito.when(stsClient.assumeRole(assumeRoleRequest)).thenReturn( 115 | new AssumeRoleResult() 116 | .withCredentials(shortLivedTestCredentials)); 117 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 118 | Mockito.verify(stsClient, Mockito.times(2)).assumeRole(assumeRoleRequest); 119 | 120 | // Make the second call and there should no another Mock invocation 121 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 122 | Mockito.verify(stsClient, Mockito.times(3)).assumeRole(assumeRoleRequest); 123 | 124 | // Make second call, should invoke STS client again 125 | stsCredentialsProvider.getUserCredentials(assumeRoleRequest); 126 | PowerMockito.verifyStatic(STSCredentialsProvider.class, Mockito.times(3)); 127 | } 128 | 129 | @Test 130 | public void random_refresh_time() { 131 | assertThat(stsCredentialsProvider.getRandomTimeInRange(), allOf( 132 | greaterThan(STSCredentialsProvider.MIN_REMAINING_TIME_TO_REFRESH_CREDENTIALS.toMillis()), 133 | lessThan(STSCredentialsProvider.MIN_REMAINING_TIME_TO_REFRESH_CREDENTIALS.toMillis() + 134 | STSCredentialsProvider.MAX_RANDOM_TIME_TO_REFRESH_CREDENTIALS.toMillis()))); 135 | } 136 | 137 | private Credentials createTestCredentials(long xp) { 138 | return new Credentials().withAccessKeyId("test-access") 139 | .withSecretAccessKey("test-secret") 140 | .withSessionToken("test-session") 141 | .withExpiration(new Date(System.currentTimeMillis() + xp)); 142 | } 143 | 144 | 145 | private AssumeRoleRequest createTestAssumeRoleRequest() { 146 | return new AssumeRoleRequest() 147 | .withRoleArn("test-arn") 148 | .withRoleSessionName("test-session"); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/credentials/TestMetadataCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.credentials; 5 | 6 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 7 | import com.amazonaws.util.EC2MetadataUtils; 8 | 9 | import java.util.Date; 10 | import java.util.Optional; 11 | 12 | public class TestMetadataCredentialsProvider implements MetadataCredentialsProvider { 13 | 14 | public static final String TEST_ACCESSKEY_ID = "test-accesskey-id"; 15 | public static final String TEST_SECRETKEY = "test-secret"; 16 | public static final String TEST_SESSION_TOKEN = "test-token"; 17 | 18 | @Override 19 | public Optional getUserCredentials(AssumeRoleRequest assumeRoleRequest) { 20 | EC2MetadataUtils.IAMSecurityCredential iamCredential = new EC2MetadataUtils.IAMSecurityCredential(); 21 | iamCredential.accessKeyId = TEST_ACCESSKEY_ID; 22 | iamCredential.secretAccessKey = TEST_SECRETKEY; 23 | iamCredential.token = TEST_SESSION_TOKEN; 24 | iamCredential.code = "Success"; 25 | iamCredential.type = "AWS-HMAC"; 26 | iamCredential.expiration = new Date().toString(); 27 | 28 | return Optional.of(iamCredential); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/IntegrationTestBase.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration; 2 | 3 | import com.google.gson.FieldNamingPolicy; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | 7 | /** 8 | * Common functionality and constants for integration tests. 9 | */ 10 | public class IntegrationTestBase { 11 | 12 | protected static final Gson GSON = new GsonBuilder() 13 | .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) 14 | .setPrettyPrinting() 15 | .create(); 16 | public static int RELOAD_CFG_TIME_MIN = 1; 17 | protected static String user = System.getProperty("user.name"); 18 | public static String DEFAULT_MAPPER_IMPL_BUCKET = "urm-integ-test-default-mapper-" + user; 19 | public static String DEFAULT_MAPPER_IMPL_MAPPING = "default-impl.json"; 20 | public static String POLICY_UNION_MAPPER_IMPL_BUCKET = "urm-integ-test-policy-union--mapper-" + user; 21 | public static String POLICY_UNION_MAPPER_IMPL_MAPPING = "policies.json"; 22 | public static String UNION_POLICIES_MAPPER_IMPL_MAPPING = "union-mapper-impl.json"; 23 | protected static String LOCALHOST_SERVER = "http://localhost"; 24 | protected static String IMDS_CREDENTIALS_URI = "/latest/meta-data/iam/security-credentials/"; 25 | protected static String USER_TO_CHANGE = "#USER_TO_CHANGE#"; 26 | protected static String GROUP_TO_CHANGE = "#GROUP_TO_CHANGE#"; 27 | protected static String USER_ROLE_TO_CHANGE = "#USER_ROLE_TO_CHANGE#"; 28 | protected static String USER_POLICY_TO_CHANGE = "#USER_POLICY_TO_CHANGE#"; 29 | protected static String GROUP_POLICY_TO_CHANGE = "#GROUP_ROLE_TO_CHANGE#"; 30 | protected static String AWS_ACCOUNT_TO_CHANGE = "#AWS_ACCOUNT_TO_CHANGE#"; 31 | protected static String BUCKET_NAME_TO_CHANGE = "#BUCKET_NAME_TO_CHANGE#"; 32 | protected static String TEST_CFG_BUCKET = "test-urm-bucket-" + user; 33 | protected static String TEST_CFG_OBJECT = "test-urm-object-" + user; 34 | protected static String TEST_ROLE_PREFIX = "test-integ-urm-role-" + user; 35 | protected static String testPolicyArn; 36 | protected static String testRoleArn; 37 | protected static String testRoleName; 38 | 39 | protected static String rolePolicyDocumentTemplate = "{\n" 40 | + " \"Version\": \"2012-10-17\",\n" 41 | + " \"Statement\": [\n" 42 | + " {\n" 43 | + " \"Effect\": \"Allow\",\n" 44 | + " \"Principal\": {\n" 45 | + " \"AWS\": \"" + AWS_ACCOUNT_TO_CHANGE + "\"\n" 46 | + " },\n" 47 | + " \"Action\": \"sts:AssumeRole\"\n" 48 | + " }\n" 49 | + " ]\n" 50 | + "}"; 51 | protected static String limitedS3AccessJsonPolicyDocument = "{" + 52 | " \"Version\": \"2012-10-17\"," + 53 | " \"Statement\": [" + 54 | " {" + 55 | " \"Effect\": \"Allow\"," + 56 | " \"Action\": [" + 57 | " \"s3:Put*\"," + 58 | " \"s3:List*\"," + 59 | " \"s3:Get*\"" + 60 | " ]," + 61 | " \"Resource\": \"arn:aws:s3:::" + BUCKET_NAME_TO_CHANGE + "/*\"" + 62 | " }" + 63 | " ]" + 64 | "}"; 65 | protected static String fullS3AccessJsonPolicyDocument = "{" + 66 | " \"Version\": \"2012-10-17\"," + 67 | " \"Statement\": [" + 68 | " {" + 69 | " \"Effect\": \"Allow\"," + 70 | " \"Action\": [" + 71 | " \"s3:Put*\"," + 72 | " \"s3:List*\"," + 73 | " \"s3:Get*\"" + 74 | " ]," + 75 | " \"Resource\": \"arn:aws:s3:::*/*\"" + 76 | " }" + 77 | " ]" + 78 | "}"; 79 | protected static String defaultImplMappingJsonTemplate = "{\n" 80 | + " \"PrincipalRoleMappings\": [\n" 81 | + " {\n" 82 | + " \"username\": \"" + USER_TO_CHANGE + "\",\n" 83 | + " \"rolearn\": \"" + USER_ROLE_TO_CHANGE + "\"\n" 84 | + " },\n" 85 | + " {\n" 86 | + " \"groupname\": \"GROUP\",\n" 87 | + " \"rolearn\": \"GROUP-ROLE\",\n" 88 | + " \"duration\": 1800\n" 89 | + " }\n" 90 | + " ]\n" 91 | + "}"; 92 | protected static String managedPoliciesImplMappingJsonTemplate = "{\n" 93 | + " \"PrincipalPolicyMappings\": [\n" 94 | + " {\n" 95 | + " \"username\": \"" + USER_TO_CHANGE + "\",\n" 96 | + " \"policies\": [\"" + USER_POLICY_TO_CHANGE + "\"]\n" 97 | + " },\n" 98 | + " {\n" 99 | + " \"groupname\": \"" + GROUP_TO_CHANGE + "\",\n" 100 | + " \"policies\": [\"" + GROUP_POLICY_TO_CHANGE + "\"]\n" 101 | + " }\n" 102 | + " ]\n" 103 | + "}"; 104 | 105 | 106 | /** 107 | * Integration tests only run on OSX and Unix as the core application needs Unix style OS. 108 | * @return {@code true} if running OS is OSX, or a Unix falvor, else {@code false}. 109 | */ 110 | protected static boolean isOsSupported() { 111 | String osString = System.getProperty("os.name", "generic").toLowerCase(); 112 | if ((osString.indexOf("mac") >= 0) || (osString.indexOf("darwin") >= 0) ||(osString.indexOf("nux") >= 0)) { 113 | return true; 114 | } 115 | return false; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/IntegrationTestsUserService.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration; 2 | 3 | import com.amazon.aws.emr.common.system.impl.CommandBasedPrincipalResolver; 4 | import com.amazon.aws.emr.common.system.user.UserIdService; 5 | import java.util.Arrays; 6 | import java.util.Optional; 7 | import java.util.OptionalInt; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | @Slf4j 11 | public class IntegrationTestsUserService implements UserIdService { 12 | 13 | @Override 14 | public OptionalInt resolveSystemUID(String localAddr, int localPort, String remoteAddr, 15 | int remotePort, boolean isNativeIMDSApi) { 16 | Optional uid = new CommandBasedPrincipalResolver() 17 | .runCommand(Arrays.asList("id", "-u")).stream().findFirst(); 18 | return uid.map(u -> OptionalInt.of(Integer.parseInt(u))) 19 | .orElse(OptionalInt.empty()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/defaultmapper/DefaultMapperImplApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration.defaultmapper; 2 | 3 | import com.amazon.aws.emr.ApplicationConfiguration; 4 | import com.amazon.aws.emr.common.Constants; 5 | import com.amazon.aws.emr.integration.IntegrationTestBase; 6 | import javax.annotation.PostConstruct; 7 | 8 | /** 9 | * Application config used to test {@link com.amazon.aws.emr.mapping.DefaultUserRoleMapperImpl} 10 | */ 11 | public class DefaultMapperImplApplicationConfig extends ApplicationConfiguration { 12 | 13 | @PostConstruct 14 | public void init() { 15 | properties.setProperty(Constants.ROLE_MAPPING_S3_BUCKET, 16 | IntegrationTestBase.DEFAULT_MAPPER_IMPL_BUCKET); 17 | properties.setProperty(Constants.ROLE_MAPPING_S3_KEY, 18 | IntegrationTestBase.DEFAULT_MAPPER_IMPL_MAPPING); 19 | properties.setProperty(Constants.ROLE_MAPPPING_REFRESH_INTERVAL_MIN, 20 | String.valueOf(IntegrationTestBase.RELOAD_CFG_TIME_MIN)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/defaultmapper/DefaultMapperIntegrationBinder.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.integration.defaultmapper; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.system.PrincipalResolver; 8 | import com.amazon.aws.emr.common.system.impl.CommandBasedPrincipalResolver; 9 | import com.amazon.aws.emr.common.system.user.UserIdService; 10 | import com.amazon.aws.emr.credentials.MetadataCredentialsProvider; 11 | import com.amazon.aws.emr.credentials.STSClient; 12 | import com.amazon.aws.emr.credentials.STSClientImpl; 13 | import com.amazon.aws.emr.credentials.STSCredentialsProvider; 14 | import com.amazon.aws.emr.integration.IntegrationTestsUserService; 15 | import com.amazon.aws.emr.mapping.MappingInvoker; 16 | import javax.inject.Singleton; 17 | import org.glassfish.hk2.api.Immediate; 18 | import org.glassfish.hk2.utilities.binding.AbstractBinder; 19 | 20 | /** 21 | * Bindings used to test {@link com.amazon.aws.emr.mapping.DefaultUserRoleMapperImpl} 22 | */ 23 | public class DefaultMapperIntegrationBinder extends AbstractBinder { 24 | 25 | @Override 26 | protected void configure() { 27 | bind(IntegrationTestsUserService.class).to(UserIdService.class); 28 | bind(MappingInvoker.class).to(MappingInvoker.class).in(Immediate.class); 29 | bind(STSClientImpl.class).to(STSClient.class).in(Immediate.class); 30 | bind(STSCredentialsProvider.class).to(MetadataCredentialsProvider.class).in(Singleton.class); 31 | bind(DefaultMapperImplApplicationConfig.class).to(ApplicationConfiguration.class) 32 | .in(Immediate.class); 33 | bind(CommandBasedPrincipalResolver.class).to(PrincipalResolver.class).in(Singleton.class); 34 | } 35 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/defaultmapper/DefaultProviderImplIntegrationApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.integration.defaultmapper; 5 | 6 | import com.amazon.aws.emr.ws.ImmediateFeature; 7 | import javax.inject.Inject; 8 | import org.glassfish.jersey.server.ResourceConfig; 9 | 10 | /** 11 | * The Jersey servlet application. 12 | */ 13 | public class DefaultProviderImplIntegrationApplication extends ResourceConfig { 14 | 15 | @Inject 16 | public DefaultProviderImplIntegrationApplication() { 17 | String[] pkgs = new String[]{"com.amazon.aws.emr.api", "com.amazon.aws.emr.mapping"}; 18 | packages(pkgs); 19 | register(ImmediateFeature.class); 20 | register(new DefaultMapperIntegrationBinder()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/policyunionmapper/PoliciesUnionMapperImplApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration.policyunionmapper; 2 | 3 | import com.amazon.aws.emr.ApplicationConfiguration; 4 | import com.amazon.aws.emr.common.Constants; 5 | import com.amazon.aws.emr.integration.IntegrationTestBase; 6 | import com.amazon.aws.emr.mapping.ManagedPolicyBasedUserRoleMapperImpl; 7 | import javax.annotation.PostConstruct; 8 | import lombok.Setter; 9 | 10 | /** 11 | * Application config used to test {@link com.amazon.aws.emr.mapping.ManagedPolicyBasedUserRoleMapperImpl} 12 | */ 13 | public class PoliciesUnionMapperImplApplicationConfig extends ApplicationConfiguration { 14 | 15 | @Setter 16 | static String roleArn; 17 | 18 | @PostConstruct 19 | public void init() { 20 | properties.setProperty(Constants.ROLE_MAPPING_S3_BUCKET, 21 | IntegrationTestBase.POLICY_UNION_MAPPER_IMPL_BUCKET); 22 | properties.setProperty(Constants.ROLE_MAPPING_S3_KEY, 23 | IntegrationTestBase.POLICY_UNION_MAPPER_IMPL_MAPPING); 24 | properties.setProperty(Constants.ROLE_MAPPPING_REFRESH_INTERVAL_MIN, 25 | String.valueOf(IntegrationTestBase.RELOAD_CFG_TIME_MIN)); 26 | properties.setProperty(Constants.ROLE_MAPPING_ROLE_ARN, roleArn); 27 | properties.setProperty(Constants.ROLE_MAPPER_CLASS, 28 | ManagedPolicyBasedUserRoleMapperImpl.class.getName()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/policyunionmapper/PoliciesUnionMapperIntegrationBinder.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.integration.policyunionmapper; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.system.PrincipalResolver; 8 | import com.amazon.aws.emr.common.system.impl.CommandBasedPrincipalResolver; 9 | import com.amazon.aws.emr.common.system.user.UserIdService; 10 | import com.amazon.aws.emr.credentials.MetadataCredentialsProvider; 11 | import com.amazon.aws.emr.credentials.STSClient; 12 | import com.amazon.aws.emr.credentials.STSClientImpl; 13 | import com.amazon.aws.emr.credentials.STSCredentialsProvider; 14 | import com.amazon.aws.emr.integration.IntegrationTestsUserService; 15 | import com.amazon.aws.emr.mapping.MappingInvoker; 16 | import javax.inject.Singleton; 17 | import org.glassfish.hk2.api.Immediate; 18 | import org.glassfish.hk2.utilities.binding.AbstractBinder; 19 | 20 | /** 21 | * Bindings used to test {@link com.amazon.aws.emr.mapping.ManagedPolicyBasedUserRoleMapperImpl} 22 | */ 23 | public class PoliciesUnionMapperIntegrationBinder extends AbstractBinder { 24 | 25 | @Override 26 | protected void configure() { 27 | bind(IntegrationTestsUserService.class).to(UserIdService.class); 28 | bind(MappingInvoker.class).to(MappingInvoker.class).in(Immediate.class); 29 | bind(STSClientImpl.class).to(STSClient.class).in(Immediate.class); 30 | bind(STSCredentialsProvider.class).to(MetadataCredentialsProvider.class).in(Singleton.class); 31 | bind(PoliciesUnionMapperImplApplicationConfig.class).to(ApplicationConfiguration.class) 32 | .in(Immediate.class); 33 | bind(CommandBasedPrincipalResolver.class).to(PrincipalResolver.class).in(Singleton.class); 34 | } 35 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/policyunionmapper/PoliciesUnionProviderImplIntegrationApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.integration.policyunionmapper; 5 | 6 | import com.amazon.aws.emr.ws.ImmediateFeature; 7 | import javax.inject.Inject; 8 | import org.glassfish.jersey.server.ResourceConfig; 9 | 10 | public class PoliciesUnionProviderImplIntegrationApplication extends ResourceConfig { 11 | 12 | @Inject 13 | public PoliciesUnionProviderImplIntegrationApplication() { 14 | String[] pkgs = new String[]{"com.amazon.aws.emr.api", "com.amazon.aws.emr.mapping"}; 15 | packages(pkgs); 16 | register(ImmediateFeature.class); 17 | register(new PoliciesUnionMapperIntegrationBinder()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/utils/IAMUtils.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration.utils; 2 | 3 | import com.amazonaws.services.identitymanagement.AmazonIdentityManagement; 4 | import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClientBuilder; 5 | import com.amazonaws.services.identitymanagement.model.AttachRolePolicyRequest; 6 | import com.amazonaws.services.identitymanagement.model.CreatePolicyRequest; 7 | import com.amazonaws.services.identitymanagement.model.CreatePolicyResult; 8 | import com.amazonaws.services.identitymanagement.model.CreateRoleRequest; 9 | import com.amazonaws.services.identitymanagement.model.CreateRoleResult; 10 | import com.amazonaws.services.identitymanagement.model.DeletePolicyRequest; 11 | import com.amazonaws.services.identitymanagement.model.DeleteRoleRequest; 12 | import com.amazonaws.services.identitymanagement.model.DetachRolePolicyRequest; 13 | import com.amazonaws.services.identitymanagement.model.EntityAlreadyExistsException; 14 | import com.amazonaws.services.identitymanagement.model.GetPolicyRequest; 15 | import com.amazonaws.services.identitymanagement.model.GetRoleRequest; 16 | import com.amazonaws.services.identitymanagement.model.Policy; 17 | import com.amazonaws.services.identitymanagement.model.Role; 18 | 19 | /** 20 | * Utility methods for AWS IAM. 21 | */ 22 | public class IAMUtils { 23 | 24 | final static AmazonIdentityManagement iam = 25 | AmazonIdentityManagementClientBuilder.defaultClient(); 26 | 27 | 28 | public static Policy createPolicy(String account, String policy) { 29 | String policyName = "urm-policy" + policy.hashCode(); 30 | CreatePolicyResult response; 31 | try { 32 | CreatePolicyRequest request = new CreatePolicyRequest() 33 | .withPolicyName(policyName) 34 | .withPolicyDocument(policy); 35 | response = iam.createPolicy(request); 36 | } catch (EntityAlreadyExistsException ex) { 37 | String policyArn = getIamPolicyArn(account, policyName); 38 | return iam.getPolicy(new GetPolicyRequest().withPolicyArn(policyArn)).getPolicy(); 39 | } 40 | return response.getPolicy(); 41 | } 42 | 43 | private static String getIamPolicyArn(String account, String policyName) { 44 | return String.format("arn:aws:iam::%s:policy/%s", account, policyName); 45 | } 46 | 47 | public static void attachPolicyToRole(String roleArn, String PolicyArn) { 48 | AttachRolePolicyRequest attach_request = 49 | new AttachRolePolicyRequest() 50 | .withRoleName(roleArn) 51 | .withPolicyArn(PolicyArn); 52 | 53 | iam.attachRolePolicy(attach_request); 54 | } 55 | 56 | public static void detachPolicyFromRole(String role, String policy) { 57 | DetachRolePolicyRequest detachRolePolicyRequest = 58 | new DetachRolePolicyRequest() 59 | .withRoleName(role) 60 | .withPolicyArn(policy); 61 | 62 | iam.detachRolePolicy(detachRolePolicyRequest); 63 | } 64 | 65 | public static Role createRole(String name, String policy) { 66 | CreateRoleResult createRoleResult; 67 | try { 68 | createRoleResult = iam.createRole(new CreateRoleRequest().withRoleName(name) 69 | .withAssumeRolePolicyDocument(policy)); 70 | } catch (EntityAlreadyExistsException ex) { 71 | return iam.getRole(new GetRoleRequest().withRoleName(name)).getRole(); 72 | } 73 | return createRoleResult.getRole(); 74 | } 75 | 76 | public static void deletePolicy(String policyArn) { 77 | DeletePolicyRequest deletePolicyRequest = new DeletePolicyRequest(); 78 | deletePolicyRequest.setPolicyArn(policyArn); 79 | iam.deletePolicy(deletePolicyRequest); 80 | } 81 | 82 | public static void deleteRole(String roleName) { 83 | DeleteRoleRequest deleteRoleRequest = new DeleteRoleRequest(); 84 | deleteRoleRequest.setRoleName(roleName); 85 | iam.deleteRole(deleteRoleRequest); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/utils/S3Utils.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration.utils; 2 | 3 | import com.amazonaws.AmazonServiceException; 4 | import com.amazonaws.services.s3.AmazonS3; 5 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 6 | import com.amazonaws.services.s3.model.Bucket; 7 | import com.amazonaws.services.s3.model.S3Object; 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.List; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Slf4j 17 | public class S3Utils { 18 | 19 | final static AmazonS3 s3 = AmazonS3ClientBuilder.standard() 20 | .build(); 21 | 22 | public static Bucket createBucket(String bucket_name) { 23 | Bucket b = null; 24 | if (s3.doesBucketExistV2(bucket_name)) { 25 | System.out.format("Bucket %s already exists.\n", bucket_name); 26 | b = getBucket(bucket_name); 27 | } else { 28 | b = s3.createBucket(bucket_name); 29 | } 30 | return b; 31 | } 32 | 33 | public static Bucket getBucket(String bucket_name) { 34 | Bucket named_bucket = null; 35 | List buckets = s3.listBuckets(); 36 | for (Bucket b : buckets) { 37 | if (b.getName().equals(bucket_name)) { 38 | named_bucket = b; 39 | } 40 | } 41 | return named_bucket; 42 | } 43 | 44 | public static void uploadObject(String bucket, String key, String fileContents) { 45 | s3.putObject(bucket, key, fileContents); 46 | } 47 | 48 | public static void deleteObject(String bucket, String key) { 49 | try { 50 | s3.deleteObject(bucket, key); 51 | } catch (AmazonServiceException e) { 52 | log.warn("Could not delete the object {}/{}", bucket, key); 53 | } 54 | } 55 | 56 | public static void deleteBucket(String bucket) { 57 | try { 58 | s3.deleteBucket(bucket); 59 | } catch (AmazonServiceException e) { 60 | log.warn("Could not delete the bucket {}", bucket); 61 | } 62 | } 63 | 64 | public static String getS3FileAsString(S3Object s3Object) throws IOException { 65 | InputStream is = s3Object.getObjectContent(); 66 | if (is == null) { 67 | return null; 68 | } 69 | StringBuilder sb = new StringBuilder(); 70 | try (BufferedReader reader = new BufferedReader( 71 | new InputStreamReader(is, StandardCharsets.UTF_8))) { 72 | String line; 73 | while ((line = reader.readLine()) != null) { 74 | sb.append(line); 75 | } 76 | return sb.toString(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/integration/utils/STSUtils.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.integration.utils; 2 | 3 | import com.amazonaws.services.securitytoken.AWSSecurityTokenService; 4 | import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; 5 | import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; 6 | import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; 7 | 8 | public class STSUtils { 9 | final static AWSSecurityTokenService sts = 10 | AWSSecurityTokenServiceClientBuilder.defaultClient(); 11 | 12 | public static String getLoggedUserAccount() { 13 | GetCallerIdentityRequest callerIdentityRequest = new GetCallerIdentityRequest(); 14 | GetCallerIdentityResult getCallerIdentityResult = sts.getCallerIdentity(callerIdentityRequest); 15 | return getCallerIdentityResult.getAccount(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/mapping/MappingInvokerTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.aws.emr.mapping; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.core.Is.is; 5 | 6 | import com.amazon.aws.emr.ApplicationConfiguration; 7 | import com.amazon.aws.emr.common.Constants; 8 | import com.amazon.aws.emr.rolemapper.UserRoleMapperProvider; 9 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 10 | import java.util.Optional; 11 | import javax.inject.Inject; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.junit.Assume; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.InjectMocks; 17 | import org.mockito.Mock; 18 | import org.mockito.junit.MockitoJUnitRunner; 19 | import static org.mockito.Mockito.when; 20 | 21 | @Slf4j 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class MappingInvokerTest { 24 | 25 | private static final String TEST_USER = "test-user"; 26 | @Mock 27 | UserRoleMapperProvider roleMapperProvider; 28 | @Mock 29 | ApplicationConfiguration applicationConfiguration; 30 | @InjectMocks 31 | MappingInvoker mappingInvoker; 32 | 33 | @Test 34 | public void is_s3_based_impl() { 35 | assertThat(mappingInvoker.isS3BasedProviderImpl("my.class"), is(false)); 36 | assertThat(mappingInvoker.isS3BasedProviderImpl(Constants.ROLE_MAPPING_DEFAULT_CLASSNAME), 37 | is(true)); 38 | assertThat( 39 | mappingInvoker.isS3BasedProviderImpl(Constants.ROLE_MAPPING_MANAGED_POLICY_CLASSNAME), 40 | is(true)); 41 | } 42 | 43 | @Test 44 | public void source_identity_enabled_then_injected() { 45 | AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().withRoleArn("test-arn"); 46 | when(roleMapperProvider.getMapping(TEST_USER)).thenReturn(Optional.of(assumeRoleRequest)); 47 | when(applicationConfiguration.isSetSourceIdentityEnabled()).thenReturn(true); 48 | Optional actual = mappingInvoker.map(TEST_USER); 49 | assertThat(actual.isPresent(), is(true)); 50 | assertThat(actual.get(), is(assumeRoleRequest.withSourceIdentity(TEST_USER))); 51 | } 52 | 53 | @Test 54 | public void source_identity_disabled_then_not_injected() { 55 | AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().withRoleArn("test-arn"); 56 | when(roleMapperProvider.getMapping(TEST_USER)).thenReturn(Optional.of(assumeRoleRequest)); 57 | when(applicationConfiguration.isSetSourceIdentityEnabled()).thenReturn(false); 58 | Optional actual = mappingInvoker.map(TEST_USER); 59 | assertThat(actual.isPresent(), is(true)); 60 | assertThat(actual.get(), is(assumeRoleRequest)); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/mapping/TestUserRoleMapperImpl.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.mapping; 5 | 6 | import com.amazon.aws.emr.common.TestConstants; 7 | import com.amazon.aws.emr.common.system.PrincipalResolver; 8 | import com.amazon.aws.emr.rolemapper.UserRoleMapperProvider; 9 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.powermock.api.mockito.PowerMockito; 12 | 13 | import java.util.Collections; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | 19 | import static org.mockito.ArgumentMatchers.eq; 20 | import static org.mockito.Mockito.when; 21 | 22 | @Slf4j 23 | public class TestUserRoleMapperImpl implements UserRoleMapperProvider { 24 | private Map userRoleMapping = new HashMap<>(); 25 | private Map groupRoleMapping = new HashMap<>(); 26 | 27 | private PrincipalResolver principalResolver; 28 | 29 | @Override 30 | public void init(Map configMap) { 31 | AssumeRoleRequest assumeRoleRequestU1 = new AssumeRoleRequest() 32 | .withRoleArn("arn:aws:iam::123456789:role/u1") 33 | .withRoleSessionName("u1"); 34 | AssumeRoleRequest assumeRoleRequestU2 = new AssumeRoleRequest() 35 | .withRoleArn("arn:aws:iam::123456789:role/u2") 36 | .withRoleSessionName("u2"); 37 | AssumeRoleRequest assumeRoleRequestU3 = new AssumeRoleRequest() 38 | .withRoleArn("arn:aws:iam::123456789:role/g1") 39 | .withRoleSessionName("g2"); 40 | userRoleMapping.put(TestConstants.USER1_ROLE_NAME, assumeRoleRequestU1); 41 | userRoleMapping.put(TestConstants.USER2_ROLE_NAME, assumeRoleRequestU2); 42 | groupRoleMapping.put(TestConstants.GROUP_ROLE_NAME, assumeRoleRequestU3); 43 | 44 | principalResolver = PowerMockito.mock(PrincipalResolver.class); 45 | when(principalResolver.getGroups(eq("u2"))).thenReturn(Optional.of(Collections.singletonList("g1"))); 46 | when(principalResolver.getGroups(eq("u4"))).thenReturn(Optional.of(Collections.singletonList("g1"))); 47 | when(principalResolver.getGroups(eq("u3"))).thenReturn(Optional.of(Collections.singletonList("g2"))); 48 | } 49 | 50 | @Override 51 | public Optional getMapping(String username) { 52 | AssumeRoleRequest assumeRoleRequest; 53 | assumeRoleRequest = userRoleMapping.get(username); 54 | if (assumeRoleRequest != null) { 55 | return Optional.of(userRoleMapping.get(username)); 56 | } 57 | Optional> groups = principalResolver.getGroups(username); 58 | return groups.orElse(Collections.emptyList()).stream() 59 | .filter(g -> groupRoleMapping.get(g) != null) 60 | .map(g -> groupRoleMapping.get(g)) 61 | .findFirst(); 62 | } 63 | 64 | @Override 65 | public void refresh() { 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/model/GroupTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import org.junit.Test; 7 | 8 | import java.util.Arrays; 9 | 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.hamcrest.core.Is.is; 12 | 13 | public class GroupTest { 14 | 15 | @Test 16 | public void valid_creation() { 17 | String line = "g1:x:508:u2,u4"; 18 | Group g = Group.createFromGroupEntry(line); 19 | assertThat(g, is(Group.builder(). 20 | name("g1") 21 | .gid(508) 22 | .users(Arrays.asList("u2", "u4")).build())); 23 | } 24 | 25 | @Test(expected = IllegalArgumentException.class) 26 | public void invalid_creation() { 27 | String line = "g1:x"; 28 | Group.createFromGroupEntry(line); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/model/PrincipalPolicyMappingsTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.core.Is.is; 8 | 9 | import com.google.common.collect.Lists; 10 | import com.google.gson.FieldNamingPolicy; 11 | import com.google.gson.Gson; 12 | import com.google.gson.GsonBuilder; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import org.junit.Test; 17 | 18 | public class PrincipalPolicyMappingsTest { 19 | final Gson GSON = new GsonBuilder() 20 | .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) 21 | .setPrettyPrinting() 22 | .create(); 23 | 24 | @Test 25 | public void serialization() { 26 | PrincipalPolicyMappings mappings = new PrincipalPolicyMappings(); 27 | 28 | PrincipalPolicyMapping e1 = new PrincipalPolicyMapping(); 29 | e1.setUsername("u1"); 30 | List policies = new ArrayList<>(); 31 | policies.add("arn:aws:s3:::NewHireOrientation1"); 32 | policies.add("arn:aws:s3:::NewHireOrientation2"); 33 | policies.add("arn:aws:s3:::NewHireOrientation3"); 34 | policies.add("arn:aws:s3:::NewHireOrientation4"); 35 | e1.setPolicyArns(policies); 36 | 37 | PrincipalPolicyMapping e2 = new PrincipalPolicyMapping(); 38 | e2.setGroupname("g1"); 39 | List gpPolicies = new ArrayList<>(); 40 | gpPolicies.add("arn:aws:s3:::MyBucket"); 41 | e2.setPolicyArns(gpPolicies); 42 | 43 | mappings.principalPolicyMappings = new PrincipalPolicyMapping[2]; 44 | mappings.principalPolicyMappings[0] = e1; 45 | mappings.principalPolicyMappings[1] = e2; 46 | 47 | String expected = "{\n" + 48 | " \"PrincipalPolicyMappings\": [\n" + 49 | " {\n" + 50 | " \"username\": \"u1\",\n" + 51 | " \"policies\": [\n" + 52 | " \"arn:aws:s3:::NewHireOrientation1\",\n" + 53 | " \"arn:aws:s3:::NewHireOrientation2\",\n" + 54 | " \"arn:aws:s3:::NewHireOrientation3\",\n" + 55 | " \"arn:aws:s3:::NewHireOrientation4\"\n" + 56 | " ]\n" + 57 | " },\n" + 58 | " {\n" + 59 | " \"groupname\": \"g1\",\n" + 60 | " \"policies\": [\n" + 61 | " \"arn:aws:s3:::MyBucket\"\n" + 62 | " ]\n" + 63 | " }\n" + 64 | " ]\n" + 65 | "}"; 66 | assertThat(expected, is(GSON.toJson(mappings))); 67 | } 68 | 69 | @Test 70 | public void deserialization() { 71 | String json = "{\n" + 72 | " \"PrincipalPolicyMappings\": [\n" + 73 | " {\n" + 74 | " \"username\": \"u1\",\n" + 75 | " \"policies\": [\n" + 76 | " \"arn:aws:s3:::NewHireOrientation1\",\n" + 77 | " \"arn:aws:s3:::NewHireOrientation2\",\n" + 78 | " \"arn:aws:s3:::NewHireOrientation3\",\n" + 79 | " \"arn:aws:s3:::NewHireOrientation4\"\n" + 80 | " ]\n" + 81 | " },\n" + 82 | " {\n" + 83 | " \"groupname\": \"g1\",\n" + 84 | " \"policies\": [\n" + 85 | " \"arn:aws:s3:::MyBucket\"\n" + 86 | " ]\n" + 87 | " }\n" + 88 | " ]\n" + 89 | "}"; 90 | PrincipalPolicyMappings principalPolicyMappings = GSON 91 | .fromJson(json, PrincipalPolicyMappings.class); 92 | assertThat(principalPolicyMappings.getPrincipalPolicyMappings().length, is(2)); 93 | 94 | PrincipalPolicyMapping mapping1 = principalPolicyMappings.getPrincipalPolicyMappings()[0]; 95 | assertThat(mapping1, is(PrincipalPolicyMapping.builder() 96 | .username("u1") 97 | .policyArns(Arrays.asList("arn:aws:s3:::NewHireOrientation1", 98 | "arn:aws:s3:::NewHireOrientation2", "arn:aws:s3:::NewHireOrientation3", 99 | "arn:aws:s3:::NewHireOrientation4")) 100 | .build())); 101 | 102 | PrincipalPolicyMapping mapping2 = principalPolicyMappings.getPrincipalPolicyMappings()[1]; 103 | assertThat(mapping2, is(PrincipalPolicyMapping.builder() 104 | .groupname("g1") 105 | .policyArns(Arrays.asList("arn:aws:s3:::MyBucket")) 106 | .build())); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/model/PrincipalRoleMappingsTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import com.google.gson.FieldNamingPolicy; 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import org.junit.Test; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.core.Is.is; 16 | 17 | public class PrincipalRoleMappingsTest { 18 | final Gson GSON = new GsonBuilder() 19 | .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) 20 | .setPrettyPrinting() 21 | .create(); 22 | 23 | @Test 24 | public void serialization() { 25 | PrincipalRoleMappings mappings = new PrincipalRoleMappings(); 26 | 27 | PrincipalRoleMapping e1 = new PrincipalRoleMapping(); 28 | e1.setUsername("u1"); 29 | e1.setRoleArn("arn:aws:s3:::u1"); 30 | List policies = new ArrayList<>(); 31 | policies.add("arn:aws:s3:::NewHireOrientation1"); 32 | policies.add("arn:aws:s3:::NewHireOrientation2"); 33 | policies.add("arn:aws:s3:::NewHireOrientation3"); 34 | policies.add("arn:aws:s3:::NewHireOrientation4"); 35 | e1.setPolicyArns(policies); 36 | e1.setDurationSeconds(900); 37 | e1.setExternalId("test-id"); 38 | e1.setTokenCode("test-code"); 39 | 40 | PrincipalRoleMapping e2 = new PrincipalRoleMapping(); 41 | String inline = "{\n" + 42 | "\"Version\":\"2012-10-17\"," + 43 | "\"Statement\":[{\n" + 44 | " \"Sid\":\"Statement1\"," + 45 | " \"Effect\":\"Allow\"," + 46 | " \"Action\":[\"s3:GetBucket\", \"s3:GetObject\"]," + 47 | " \"Resource\": [\"arn:aws:s3:::NewHireOrientation\", \"arn:aws:s3:::NewHireOrientation/*\"]" + 48 | " }]" + 49 | "} "; 50 | e2.setGroupname("u1"); 51 | e2.setRoleArn("arn:aws:s3:::u1"); 52 | e2.setDurationSeconds(20); 53 | e2.setPolicy(inline); 54 | mappings.principalRoleMappings = new PrincipalRoleMapping[2]; 55 | mappings.principalRoleMappings[0] = e1; 56 | mappings.principalRoleMappings[1] = e2; 57 | 58 | String expected = "{\n" + 59 | " \"PrincipalRoleMappings\": [\n" + 60 | " {\n" + 61 | " \"username\": \"u1\",\n" + 62 | " \"rolearn\": \"arn:aws:s3:::u1\",\n" + 63 | " \"policies\": [\n" + 64 | " \"arn:aws:s3:::NewHireOrientation1\",\n" + 65 | " \"arn:aws:s3:::NewHireOrientation2\",\n" + 66 | " \"arn:aws:s3:::NewHireOrientation3\",\n" + 67 | " \"arn:aws:s3:::NewHireOrientation4\"\n" + 68 | " ],\n" + 69 | " \"duration\": 900,\n" + 70 | " \"externalid\": \"test-id\",\n" + 71 | " \"tokencode\": \"test-code\"\n" + 72 | " },\n" + 73 | " {\n" + 74 | " \"groupname\": \"u1\",\n" + 75 | " \"rolearn\": \"arn:aws:s3:::u1\",\n" + 76 | " \"textpolicy\": \"{\\n\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\n \\\"Sid\\\":\\\"Statement1\\\", \\\"Effect\\\":\\\"Allow\\\", \\\"Action\\\":[\\\"s3:GetBucket\\\", \\\"s3:GetObject\\\"], \\\"Resource\\\": [\\\"arn:aws:s3:::NewHireOrientation\\\", \\\"arn:aws:s3:::NewHireOrientation/*\\\"] }]} \",\n" + 77 | " \"duration\": 20\n" + 78 | " }\n" + 79 | " ]\n" + 80 | "}"; 81 | assertThat(expected, is(GSON.toJson(mappings))); 82 | } 83 | 84 | @Test 85 | public void deserialization() { 86 | String json = "{\n" + 87 | " \"PrincipalRoleMappings\": [\n" + 88 | " {\n" + 89 | " \"username\": \"u1\",\n" + 90 | " \"rolearn\": \"arn:aws:iam::176430881729:role/u1\",\n" + 91 | " \"duration\": 900,\n" + 92 | " \"externalid\": \"test-id\",\n" + 93 | " \"tokencode\": \"test-code\"\n" + 94 | " },\n" + 95 | " {\n" + 96 | " \"username\": \"u2\",\n" + 97 | " \"rolearn\": \"arn:aws:iam::176430881729:role/u2\"\n" + 98 | " },\n" + 99 | " {\n" + 100 | " \"groupname\": \"g1\",\n" + 101 | " \"rolearn\": \"arn:aws:iam::176430881729:role/g1\",\n" + 102 | " \"duration\": 900\n" + 103 | " }\n" + 104 | " ]\n" + 105 | "}"; 106 | PrincipalRoleMappings principalRoleMappings = GSON.fromJson(json, PrincipalRoleMappings.class); 107 | assertThat(principalRoleMappings.getPrincipalRoleMappings().length, is(3)); 108 | 109 | PrincipalRoleMapping mapping1 = principalRoleMappings.getPrincipalRoleMappings()[0]; 110 | assertThat(mapping1, is(PrincipalRoleMapping.builder() 111 | .username("u1") 112 | .roleArn("arn:aws:iam::176430881729:role/u1") 113 | .durationSeconds(900) 114 | .externalId("test-id") 115 | .tokenCode("test-code") 116 | .build())); 117 | PrincipalRoleMapping mapping2 = principalRoleMappings.getPrincipalRoleMappings()[1]; 118 | assertThat(mapping2, is(PrincipalRoleMapping.builder() 119 | .username("u2") 120 | .roleArn("arn:aws:iam::176430881729:role/u2") 121 | .build())); 122 | PrincipalRoleMapping mapping3 = principalRoleMappings.getPrincipalRoleMappings()[2]; 123 | assertThat(mapping3, is(PrincipalRoleMapping.builder() 124 | .groupname("g1") 125 | .roleArn("arn:aws:iam::176430881729:role/g1") 126 | .durationSeconds(900) 127 | .build())); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/src/test/com/amazon/aws/emr/model/UserTest.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.model; 5 | 6 | import org.junit.Test; 7 | 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.core.Is.is; 10 | 11 | public class UserTest { 12 | 13 | @Test 14 | public void valid_format_succeeds() { 15 | String line = "u4:x:506:507::/home/u4:/bin/bash"; 16 | User user = User.createFromPasswdEntry(line); 17 | assertThat(user, is(User.builder(). 18 | name("u4") 19 | .uid(506) 20 | .gid(507) 21 | .comment("") 22 | .home("/home/u4") 23 | .shell("/bin/bash") 24 | .build())); 25 | } 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void invalid_creation_throws() { 29 | String line = "u1:x"; 30 | User.createFromPasswdEntry(line); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/usr/install/al1/emr-user-role-mapper.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | description "Starts EMR Record Server" 18 | 19 | start on runlevel [2345] 20 | stop on runlevel [016] 21 | 22 | start on started netfs 23 | start on started rsyslog 24 | 25 | stop on stopping netfs 26 | stop on stopping rsyslog 27 | 28 | respawn 29 | 30 | # respawn unlimited times with 5 seconds time interval 31 | respawn limit 0 5 32 | 33 | env SLEEP_TIME=10 34 | 35 | env DAEMON="emr-user-role-mapper" 36 | env DESC="Starts EMR Record Server" 37 | env EXEC_PATH="/usr/share/aws/emr/user-role-mapper/lib" 38 | env SVC_USER="userrolemapper" 39 | env DAEMON_FLAGS="" 40 | env CONF_DIR="/emr/user-role-mapper/conf/" 41 | env PIDFILE="/var/run/emr-user-role-mapper/emr-record-server.pid" 42 | env WORKING_DIR="/var/lib/emr-user-role-mapper" 43 | env EMRUSERROLEMAPPER_HOME="/usr/share/aws/emr/user-role-mapper" 44 | env CLASSPATH="/usr/share/aws/emr/user-role-mapper/lib/*:/emr/user-role-mapper/conf/" 45 | env CONF="${EMRUSERROLEMAPPER_HOME}/conf/rolemapper.properties" 46 | 47 | pre-start script 48 | #install -d -m 0755 -o $SVC_USER -g $SVC_USER $(dirname $PIDFILE) 1>/dev/null 2>&1 || : 49 | touch /tmp/1 50 | 51 | if [ ! -d $CONF_DIR ]; then 52 | echo "$CONF_DIR is not a directory" 53 | exit 1 54 | fi 55 | 56 | run_prestart() { 57 | LOG_FILE=/tmp/${DAEMON}.out 58 | JAVA_HEAPSIZE="-Xms200m -Xmx1024m" 59 | JAVA_OPTS="${JAVA_HEAPSIZE} -XX:+UseGCOverheadLimit -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError=\"kill -9 %p\" -XX:ReservedCodeCacheSize=150M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -Xloggc:/tmp/$DAEMON-garbage-collection.log -XX:+PrintFlagsFinal -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=128M" 60 | su -s /bin/bash $SVC_USER -c "nohup nice -n 0 \ 61 | /usr/bin/java ${JAVA_OPTS} -cp ${CLASSPATH} \ 62 | com.amazon.aws.emr.UserRoleMappingServer \ 63 | > $LOG_FILE 2>&1 & "'echo $!' > "$PIDFILE" 64 | } 65 | 66 | export -f run_prestart 67 | $EXEC_LAUNCHER run_prestart 68 | end script 69 | 70 | script 71 | 72 | # sleep for sometime for the daemon to start running 73 | sleep $SLEEP_TIME 74 | if [ ! -f $PIDFILE ]; then 75 | echo "$PIDFILE not found" 76 | exit 1 77 | fi 78 | pid=$(<"$PIDFILE") 79 | while ps -p $pid > /dev/null; do 80 | sleep $SLEEP_TIME 81 | done 82 | echo "$pid stopped running..." 83 | 84 | end script 85 | 86 | pre-stop script 87 | 88 | # do nothing 89 | 90 | end script 91 | 92 | post-stop script 93 | if [ ! -f $PIDFILE ]; then 94 | echo "$PIDFILE not found" 95 | exit 96 | fi 97 | pid=$(<"$PIDFILE") 98 | if kill $pid > /dev/null 2>&1; then 99 | echo "process $pid is killed" 100 | fi 101 | rm -rf $PIDFILE 102 | end script 103 | 104 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/usr/install/al2/emr-user-role-mapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Amazon EMR 5 | # 6 | # Copyright 2020, Amazon.com, Inc. or its affiliates. All Rights Reserved. 7 | # 8 | # Licensed under the Amazon Software License (the "License"). 9 | # You may not use this file except in compliance with the License. 10 | # A copy of the License is located at 11 | # 12 | # http://aws.amazon.com/asl/ 13 | # 14 | # or in the "license" file accompanying this file. This file is distributed 15 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 16 | # express or implied. See the License for the specific language governing 17 | # permissions and limitations under the License. 18 | # 19 | 20 | set -e -x 21 | 22 | LOG_FILE=/emr/user-role-mapper/log/emr-user-role-mapper.out 23 | PID_FILE=/emr/user-role-mapper/run/emr-user-role-mapper.pid 24 | 25 | function start { 26 | set -x 27 | 28 | EMR_USER_ROLE_MAPPER_HOME="/usr/share/aws/emr/user-role-mapper" 29 | CLASSPATH="${EMR_USER_ROLE_MAPPER_HOME}/lib/*:/emr/user-role-mapper/conf/:" 30 | GC_OPTIONS="-XX:+UseGCOverheadLimit -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError=\"kill -9 %p\" \ 31 | -XX:ReservedCodeCacheSize=150M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails \ 32 | -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 \ 33 | -Xloggc:/tmp/emr-user-role-mapper-garbage-collection.log -XX:+PrintFlagsFinal -XX:+UseGCLogFileRotation \ 34 | -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=128M" 35 | 36 | sudo echo "(console) $(date '+%Y-%m-%d %H:%M:%S') EMR UserRoleMapper start called!" > /dev/console 37 | 38 | LAUNCH_CMD='/usr/bin/java -Xms200m -Xmx1024m '$GC_OPTIONS' -cp '$CLASSPATH' \ 39 | com.amazon.aws.emr.UserRoleMappingServer >> '$LOG_FILE' 2>&1 &' 40 | 41 | sudo -u userrolemapper -H sh -c "$LAUNCH_CMD" 42 | 43 | userrolemapper_pid=$(ps -eo uname:20,pid,cmd | grep "userrolemapper.*[/]usr/bin/java.*emr.UserRoleMappingServer" | awk '{print $2}') 44 | echo "EMR User Role Mapper process id is $userrolemapper_pid" 45 | 46 | sudo echo $userrolemapper_pid > $PID_FILE 47 | 48 | sleep 5 49 | 50 | echo "(console) $(date '+%Y-%m-%d %H:%M:%S') Listing currently running userrolemapper: " > /dev/console 51 | echo `ps -efww | grep -i userrolemapper` 52 | } 53 | 54 | function stop { 55 | echo "(console) $(date '+%Y-%m-%d %H:%M:%S') EMR UserRoleMapper stop called!" > /dev/console 56 | 57 | if [[ -f $PID_FILE ]]; then 58 | userrolemapper_pid=$(cat $PID_FILE) 59 | kill -9 $userrolemapper_pid 60 | sudo rm $PID_FILE 61 | else 62 | echo "User Role Mapper PID file does not exist. " 63 | fi 64 | } 65 | 66 | function status { 67 | if [ -e $PID_FILE ] && ps --pid $(cat $PID_FILE) > /dev/null 2>&1 ; then 68 | echo "EMR User Role Mapper: Running" 69 | exit 0 70 | else 71 | echo "EMR User Role Mapper: Not Running" 72 | exit 3 73 | fi 74 | } 75 | 76 | if [[ $# -eq 0 ]]; then 77 | start 78 | else 79 | case $1 in 80 | 'start' ) 81 | start 82 | ;; 83 | 'stop' ) 84 | stop 85 | ;; 86 | 'restart' ) 87 | stop 88 | start 89 | ;; 90 | 'status' ) 91 | status 92 | ;; 93 | *) 94 | echo "usage: `basename $0` {start|stop|status}" 95 | esac 96 | fi 97 | 98 | exit 0 -------------------------------------------------------------------------------- /emr-user-role-mapper-application/usr/install/al2/emr-user-role-mapper.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=EMR process to map users to AWS IAM Roles. 3 | After=libinstance-controller-java.service 4 | 5 | [Service] 6 | Type=forking 7 | ExecStart=/usr/bin/emr-user-role-mapper 8 | ExecStop=/usr/bin/emr-user-role-mapper stop 9 | Restart=always 10 | PIDFile=/emr/user-role-mapper/run/emr-user-role-mapper.pid 11 | 12 | [Install] 13 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /emr-user-role-mapper-application/usr/install/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | #log4j.rootLogger=WARN, file 3 | log4j.logger.com.amazon.aws.emr=INFO, file 4 | 5 | # Direct log messages to a log file 6 | log4j.appender.file=org.apache.log4j.RollingFileAppender 7 | 8 | log4j.appender.file.File=/emr/user-role-mapper/log/emr-user-role-mapper.log 9 | log4j.appender.file.MaxFileSize=2MB 10 | log4j.appender.file.MaxBackupIndex=10 11 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %t %-5p %c{1}:%L - %m%n 13 | log4j.logger.httpclient.wire.header=OFF 14 | log4j.logger.httpclient.wire.content=OFF 15 | log4j.logger.org.apache.http.wire=OFF 16 | -------------------------------------------------------------------------------- /emr-user-role-mapper-application/usr/install/user-role-mapper.properties: -------------------------------------------------------------------------------- 1 | #rolemapper.class.name=com.mycompany.mapper.RoleMapperImpl 2 | #principal.resolver.strategy=command 3 | rolemapper.s3.bucket=my-urm-bucket 4 | rolemapper.s3.key=mappings.json 5 | rolemapper.refresh.interval.minutes=1 6 | rolemapper.max.threads=35 7 | rolemapper.min.threads=15 8 | 9 | -------------------------------------------------------------------------------- /emr-user-role-mapper-credentials-provider/README.md: -------------------------------------------------------------------------------- 1 | # URM Credentials Provider 2 | 3 | This module is necessary if you are going to use URM with Apache PrestoDB/SQL and/or Hive (using impersonation). This credentials provider works by looking at who the current user is using Hadoops UserGroupInformation and retrieves credentials for that user. 4 | 5 | # Setup instructions 6 | 7 | ## Pre-requistites 8 | 9 | - Your cluster must be kerberized and URM must be installed according to the instructions for URM. 10 | 11 | ## Instructions 12 | Run: 13 | ```sh 14 | mvn clean install 15 | ``` 16 | 17 | and copy target/urm-credentials-provider-0.x-SNAPSHOT-with-dependencies.jar to a location in S3. 18 | 19 | Then create a Bootstrap action that does the following: 20 | 21 | ```bash 22 | sudo aws s3 cp s3:// /usr/share/aws/emr/emrfs/auxlib/ 23 | ``` 24 | 25 | ## EMR Cluster setup 26 | 27 | Set the following configuration: 28 | 29 | ```json 30 | { 31 | "Classification":"emrfs-site", 32 | "Properties":{ 33 | "fs.s3.customAWSCredentialsProvider":"com.amazonaws.emr.urm.credentialsprovider.URMCredentialsProviderChain" 34 | }, 35 | "Configurations":[ 36 | ] 37 | } 38 | ``` 39 | 40 | By default, the hive and presto users will use this credentials provider, and everyone else will use the existing DefaultCredentialsProviderChain. If you wish to change this list, add the "urm.credentialsprovider.impersonation.users" property to emrfs-site.xml, like below: 41 | 42 | ```json 43 | { 44 | "Classification":"emrfs-site", 45 | "Properties":{ 46 | "fs.s3.customAWSCredentialsProvider":"com.amazonaws.emr.urm.credentialsprovider.URMCredentialsProviderChain", 47 | "urm.credentialsprovider.impersonation.users":"hive,presto,admin" 48 | }, 49 | "Configurations":[ 50 | ] 51 | } 52 | ``` 53 | 54 | ## Presto setup 55 | 56 | ### General Instructions 57 | 58 | * Set the following configuration: 59 | 60 | ```json 61 | { 62 | "Classification":"presto-connector-hive", 63 | "Properties":{ 64 | "hive.hdfs.impersonation.enabled":"true" 65 | }, 66 | "Configurations":[ 67 | ] 68 | } 69 | ``` 70 | 71 | * "rolemapper.impersonation.allowed.users" property must include "presto" user in your user-role-mapper.properties file to allow presto to impersonate other users. 72 | 73 | * When your cluster comes up, ensure that the jar is available as a symlink in /usr/lib/presto/plugin/hive-hadoop2/ 74 | ```bash 75 | ls -lrt /usr/lib/presto/plugin/hive-hadoop2/urm-credentials-provider*.jar 76 | lrwxrwxrwx 1 root root 73 Dec 12 02:24 /usr/lib/presto/plugin/hive-hadoop2/urm-credentials-provider-0.1-SNAPSHOT.jar -> /usr/share/aws/emr/emrfs/auxlib/urm-credentials-provider-0.1-SNAPSHOT.jar 77 | ``` 78 | 79 | * **IMPORTANT: Presto does not support impersonation when interacting with Metadata services like Glue Data Catalog or Hive Metastore. Trino does and support for it is coming.** 80 | 81 | * **IMPORTANT: Impersonation is not supported when S3 Select Predicate Push down is enabled. Ensure that its not enabled.** 82 | 83 | ### Using Glue Data Catalog as your meta store. 84 | 85 | If using Glue Data Catalog, there must be a single policy in mappings.json to allow the presto user to access Glue Data Catalog. 86 | 87 | For example: 88 | 89 | ```json 90 | { 91 | "PrincipalPolicyMappings": [ 92 | { 93 | "username": "presto", 94 | "policies": ["arn:aws:iam:::policy/"] 95 | } 96 | ] 97 | } 98 | ``` 99 | 100 | where POLICY_X is a policy that has access to Glue Data Catalog and s3:ListBucket for any s3 buckets that you wish Presto to access. 101 | 102 | Note: As of PrestoDB 0.232, it does not perform the necessary impersonation in rare cases which is why s3:ListBucket permissions is needed. 103 | 104 | ### Using Hive Metastore 105 | 106 | Presto does not support impersonation when interacting with Hive Metastore. For this reason, the presto user will need to have a mapping to a role that has access to S3. 107 | 108 | ```json 109 | { 110 | "PrincipalPolicyMappings": [ 111 | { 112 | "username": "hive", 113 | "policies": ["arn:aws:iam:::policy/"] 114 | } 115 | ] 116 | } 117 | ``` 118 | 119 | where POLICY_X is a policy that has access to Glue Data Catalog and s3:ListBucket for any s3 buckets that you wish Presto to access. 120 | 121 | Also, if you are using StorageBasedAuthorizer as the authorization layer for Hive Metastore, you will need to provide S3 privileges to the presto user as well. This is so that the authorizer can access S3 during it's authorization checks. 122 | 123 | ## Hive setup 124 | 125 | * "rolemapper.impersonation.allowed.users" property must include "hive" user in your user-role-mapper.properties file to allow hive to impersonate other users. 126 | -------------------------------------------------------------------------------- /emr-user-role-mapper-credentials-provider/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.amazonaws.emr 7 | amazon-emr-user-role-mapper 8 | 1.2.0-SNAPSHOT 9 | 10 | 11 | urm-credentials-provider 12 | 13 | Amazon EMR User Role Mapper URM Credentials Provider 14 | URM Credentials Provider 15 | https://github.com/awslabs/amazon-emr-user-role-mapper/ 16 | 17 | 18 | 19 | Apache License 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | repo 22 | 23 | 24 | 25 | 26 | 1.8 27 | 1.8 28 | 29 | 2.8.5 30 | 1.11.890 31 | 4.5.13 32 | 2.2.4 33 | 34 | 4.12 35 | 2.8.9 36 | 1.7.4 37 | 38 | 39 | 40 | 41 | com.amazonaws 42 | aws-java-sdk-core 43 | ${dep.aws-sdk.version} 44 | provided 45 | 46 | 47 | 48 | org.apache.hadoop 49 | hadoop-common 50 | ${dep.hadoop.version} 51 | provided 52 | 53 | 54 | 55 | org.apache.httpcomponents 56 | httpclient 57 | ${dep.httpclient.version} 58 | provided 59 | 60 | 61 | 62 | com.google.code.gson 63 | gson 64 | ${dep.gson.version} 65 | 66 | 67 | 68 | 69 | org.mockito 70 | mockito-core 71 | ${dep.mockito.version} 72 | test 73 | 74 | 75 | 76 | junit 77 | junit 78 | ${dep.junit.version} 79 | test 80 | 81 | 82 | 83 | org.powermock 84 | powermock-module-junit4 85 | ${dep.powermock.version} 86 | test 87 | 88 | 89 | org.powermock 90 | powermock-api-mockito2 91 | ${dep.powermock.version} 92 | test 93 | 94 | 95 | 96 | 97 | 98 | ${basedir}/src/main/java 99 | ${basedir}/src/test/java 100 | 101 | 102 | org.jacoco 103 | jacoco-maven-plugin 104 | 0.8.5 105 | 106 | 107 | 108 | prepare-agent 109 | 110 | 111 | 112 | 113 | report 114 | test 115 | 116 | report 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-shade-plugin 124 | 3.2.1 125 | 126 | false 127 | 128 | 129 | org.slf4j:* 130 | com.fasterxml.* 131 | 132 | 133 | 134 | 135 | com.google.code.gson 136 | com.amazonaws.emr.urm.shaded.com.google.code.gson 137 | 138 | 139 | urm-credentials-provider-${project.version}-with-dependencies 140 | 141 | 142 | 143 | package 144 | 145 | shade 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /emr-user-role-mapper-credentials-provider/src/main/java/com/amazonaws/auth/URMCredentialsFetcher.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.auth; 2 | 3 | import com.amazonaws.internal.EC2ResourceFetcher; 4 | import com.amazonaws.internal.InstanceMetadataServiceResourceFetcher; 5 | import com.amazonaws.retry.internal.CredentialsEndpointRetryParameters; 6 | import com.amazonaws.retry.internal.CredentialsEndpointRetryPolicy; 7 | import com.google.common.annotations.VisibleForTesting; 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | 11 | import java.net.URI; 12 | 13 | /** 14 | * This class reuses the {@link BaseCredentialsFetcher} from AWS CLI because it takes care for us: 15 | * 1) refreshing credentials automatically when they are close to expiration 16 | * 2) parses of the output for credentials and returns the appropriate credentials 17 | * 3) makes sure that we conform to the behaviour of the existing instance profile credentials provider. 18 | *

19 | * This class needed to be in this namespace as the class is package private. 20 | */ 21 | public class URMCredentialsFetcher 22 | extends BaseCredentialsFetcher 23 | implements CredentialsEndpointRetryPolicy 24 | { 25 | private static final Log LOG = LogFactory.getLog(URMCredentialsFetcher.class); 26 | 27 | final private static String IMPERSONATION_PATH = "http://localhost:9944/latest/meta-data/iam/security-credentials/impersonation/"; 28 | private URI impersonationURI; 29 | private String currentUser; 30 | 31 | private final EC2ResourceFetcher resourceFetcher; 32 | 33 | public URMCredentialsFetcher(String user) 34 | { 35 | this(user, InstanceMetadataServiceResourceFetcher.getInstance()); 36 | } 37 | 38 | @VisibleForTesting 39 | URMCredentialsFetcher(String user, EC2ResourceFetcher ec2ResourceFetcher) 40 | { 41 | this.currentUser = user; 42 | this.resourceFetcher = ec2ResourceFetcher; 43 | this.impersonationURI = URI.create(IMPERSONATION_PATH + user); 44 | setImpersonationUser(user); 45 | } 46 | 47 | public void setImpersonationUser(String user) { 48 | if (!currentUser.equalsIgnoreCase(user)) { 49 | this.currentUser = user; 50 | this.impersonationURI = URI.create(IMPERSONATION_PATH + user); 51 | this.refresh(); 52 | } 53 | } 54 | 55 | @Override 56 | protected String getCredentialsResponse() 57 | { 58 | if (LOG.isDebugEnabled()) { 59 | LOG.debug("URMCredentialsFetcher: Calling URI: " + impersonationURI.toASCIIString()); 60 | } 61 | return resourceFetcher.readResource(impersonationURI, this); 62 | } 63 | 64 | @Override 65 | public String toString() 66 | { 67 | return "URMCredentialsFetcher"; 68 | } 69 | 70 | @Override 71 | public boolean shouldRetry(int retriesAttempted, CredentialsEndpointRetryParameters retryParams) 72 | { 73 | return false; 74 | } 75 | } -------------------------------------------------------------------------------- /emr-user-role-mapper-credentials-provider/src/main/java/com/amazonaws/emr/urm/credentialsprovider/URMCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.credentialsprovider; 2 | 3 | import com.amazonaws.auth.AWSCredentials; 4 | import com.amazonaws.auth.AWSCredentialsProvider; 5 | import com.amazonaws.auth.URMCredentialsFetcher; 6 | import com.google.common.annotations.VisibleForTesting; 7 | import com.google.common.base.Preconditions; 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | import org.apache.hadoop.conf.Configuration; 11 | import org.apache.hadoop.security.UserGroupInformation; 12 | 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | /** 19 | * This class will get credentials from the URM process if the current user is configured 20 | * to impersonate the user in the UGI session. This is useful for applications like Presto and 21 | * Hive. 22 | */ 23 | public class URMCredentialsProvider 24 | implements AWSCredentialsProvider 25 | { 26 | static final String EMRFS_SITE_CONF_ALLOWED_USERS = "urm.credentialsprovider.impersonation.users"; 27 | private static final Log LOG = LogFactory.getLog(URMCredentialsProvider.class); 28 | private static final Set DEFAULT_ALLOWED_USERS = new HashSet<>(Arrays.asList("presto", "hive")); 29 | private final Set usersAllowedToImpersonate; 30 | 31 | private final String createdBy; 32 | private final String createdFor; 33 | 34 | final private URMCredentialsFetcher urmCredentialsFetcher; 35 | 36 | /** 37 | * Default constructor. This constructor will get the current user from the {@link UserGroupInformation} and 38 | * uses the default allowed users list. 39 | */ 40 | public URMCredentialsProvider() 41 | { 42 | this(new URMCredentialsFetcher(getUgi().getShortUserName()), 43 | DEFAULT_ALLOWED_USERS, 44 | getUgi().getShortUserName(), 45 | getRealUser() 46 | ); 47 | } 48 | 49 | /** 50 | * This constructor takes a {@link Configuration} and gets the list of users that can impersonate other users. 51 | * It will also use the {@link UserGroupInformation} to get the current user. 52 | * @param configuration The configuration object that can be passed in to read from. 53 | */ 54 | public URMCredentialsProvider(Configuration configuration) 55 | { 56 | this(new URMCredentialsFetcher(getUgi().getShortUserName()), 57 | getUsersAllowedToImpersonate(configuration), 58 | getUgi().getShortUserName(), 59 | getRealUser()); 60 | } 61 | 62 | /** 63 | * This constructor takes all members as an argument for testing purposes. 64 | * @param urmCredentialsFetcher 65 | * @param usersAllowedToImpersonate 66 | * @param realUser 67 | */ 68 | @VisibleForTesting 69 | URMCredentialsProvider(URMCredentialsFetcher urmCredentialsFetcher, Set usersAllowedToImpersonate, 70 | String currentUser, String realUser) 71 | { 72 | Preconditions.checkNotNull(urmCredentialsFetcher); 73 | Preconditions.checkNotNull(usersAllowedToImpersonate); 74 | 75 | LOG.debug("Building URMCredentials Provider."); 76 | this.urmCredentialsFetcher = urmCredentialsFetcher; 77 | this.usersAllowedToImpersonate = usersAllowedToImpersonate; 78 | this.createdFor = currentUser; 79 | this.createdBy = realUser; 80 | } 81 | 82 | /** 83 | * Gets credentials if the real user is allowed to get credentials for an impersonated user. 84 | * 85 | * @return AWSCredentials if user is allowed to impersonate, null otherwise. 86 | */ 87 | @Override 88 | public AWSCredentials getCredentials() 89 | { 90 | //Check to make sure I am allowed to impersonate. 91 | if (!usersAllowedToImpersonate.contains(createdBy)) { 92 | //not going to use impersonation for this. 93 | if (LOG.isDebugEnabled()) { 94 | LOG.debug("Not Impersonating as I am: " + createdBy); 95 | } 96 | //Return null which will then let the existing credentials provider chain to get credentials 97 | return null; 98 | } 99 | 100 | String callingUser = this.createdFor; 101 | 102 | if (LOG.isDebugEnabled()) { 103 | LOG.debug("I am impersonating user: " + callingUser); 104 | } 105 | 106 | try { 107 | urmCredentialsFetcher.setImpersonationUser(callingUser); 108 | return urmCredentialsFetcher.getCredentials(); 109 | } catch (Exception e) { 110 | LOG.error("Failed to get credentials for user: " + callingUser + " from URM", e); 111 | throw e; 112 | } 113 | } 114 | 115 | @Override 116 | public void refresh() 117 | { 118 | LOG.debug("Starting to Refreshing Credentials."); 119 | urmCredentialsFetcher.refresh(); 120 | LOG.debug("Finished to Refreshing Credentials."); 121 | } 122 | 123 | @VisibleForTesting 124 | static Set getUsersAllowedToImpersonate(Configuration configuration) 125 | { 126 | String[] allowedUsers = configuration.getTrimmedStrings(EMRFS_SITE_CONF_ALLOWED_USERS); 127 | if (allowedUsers != null) { 128 | return new HashSet<>(Arrays.asList(allowedUsers)); 129 | } 130 | else { 131 | return DEFAULT_ALLOWED_USERS; 132 | } 133 | } 134 | 135 | private static UserGroupInformation getUgi() 136 | { 137 | try { 138 | UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); 139 | if (LOG.isDebugEnabled()) { 140 | LOG.debug("UGI Returned user : " + ugi.getShortUserName() + " Real User: " + ugi.getRealUser() + " AuthN Method: " + ugi.getAuthenticationMethod()); 141 | } 142 | return ugi; 143 | } 144 | catch (IOException e) { 145 | throw new RuntimeException("Failed to get UGI of the current user.", e); 146 | } 147 | } 148 | 149 | @VisibleForTesting 150 | static String getRealUser() { 151 | UserGroupInformation ugi = getUgi(); 152 | if (ugi.getRealUser() != null) { 153 | return ugi.getRealUser().getShortUserName(); 154 | } 155 | //If UGI getRealUser returns null, fall back to system property. 156 | LOG.debug("Failed to get RealUser from UGI. Defaulting to System Property"); 157 | return System.getProperty("user.name"); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /emr-user-role-mapper-credentials-provider/src/main/java/com/amazonaws/emr/urm/credentialsprovider/URMCredentialsProviderChain.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.credentialsprovider; 2 | 3 | import com.amazonaws.auth.AWSCredentialsProviderChain; 4 | import com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper; 5 | import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; 6 | import com.amazonaws.auth.InstanceProfileCredentialsProvider; 7 | import com.amazonaws.auth.SystemPropertiesCredentialsProvider; 8 | import com.amazonaws.auth.profile.ProfileCredentialsProvider; 9 | import org.apache.hadoop.conf.Configuration; 10 | 11 | import java.net.URI; 12 | 13 | /** 14 | * Provider Chain that adds URM Credentials Provider. 15 | */ 16 | public class URMCredentialsProviderChain 17 | extends AWSCredentialsProviderChain 18 | { 19 | 20 | /** 21 | * Default constructor that provides the original DefaultCredentialsProviderChain with URMCredentialsProvider as the 22 | * first provider. 23 | */ 24 | public URMCredentialsProviderChain() 25 | { 26 | super(new URMCredentialsProvider(), 27 | InstanceProfileCredentialsProvider.getInstance(), 28 | new EnvironmentVariableCredentialsProvider(), 29 | new SystemPropertiesCredentialsProvider(), 30 | new ProfileCredentialsProvider(), 31 | new EC2ContainerCredentialsProviderWrapper()); 32 | } 33 | 34 | /** 35 | * Constructor that EMRFS calls and passes in configuration information. It provides the original DefaultCredentialsProviderChain 36 | * with URMCredentialsProvider as the first provider. 37 | */ 38 | public URMCredentialsProviderChain(URI uri, Configuration configuration) 39 | { 40 | super(new URMCredentialsProvider(configuration), 41 | InstanceProfileCredentialsProvider.getInstance(), 42 | new EnvironmentVariableCredentialsProvider(), 43 | new SystemPropertiesCredentialsProvider(), 44 | new ProfileCredentialsProvider(), 45 | new EC2ContainerCredentialsProviderWrapper()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /emr-user-role-mapper-credentials-provider/src/test/java/com/amazonaws/emr/urm/credentialsprovider/URMCredentialsProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.credentialsprovider; 2 | 3 | import com.amazonaws.auth.AWSCredentials; 4 | import com.amazonaws.auth.BasicSessionCredentials; 5 | import com.amazonaws.auth.URMCredentialsFetcher; 6 | import org.apache.hadoop.conf.Configuration; 7 | import org.apache.hadoop.security.UserGroupInformation; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.BDDMockito; 11 | import org.powermock.api.mockito.PowerMockito; 12 | import org.powermock.core.classloader.annotations.PrepareForTest; 13 | import org.powermock.modules.junit4.PowerMockRunner; 14 | 15 | import java.io.IOException; 16 | import java.util.Arrays; 17 | import java.util.Collections; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertNotNull; 23 | import static org.junit.Assert.assertNull; 24 | import static org.junit.Assert.assertTrue; 25 | import static org.mockito.Mockito.mock; 26 | import static org.mockito.Mockito.when; 27 | 28 | @RunWith(PowerMockRunner.class) 29 | @PrepareForTest(UserGroupInformation.class) 30 | public class URMCredentialsProviderTest 31 | { 32 | public static final String USER = "a_user"; 33 | public static final String REALUSER = "realuser"; 34 | 35 | @Test 36 | public void test_nonimpersonation() 37 | { 38 | URMCredentialsFetcher mockURMCredentialsFetcher = mock(URMCredentialsFetcher.class); 39 | URMCredentialsProvider urmCredentialsProvider = new URMCredentialsProvider(mockURMCredentialsFetcher, 40 | new HashSet<>(Arrays.asList("user1", "user2")), USER, REALUSER); 41 | assertNull(urmCredentialsProvider.getCredentials()); 42 | } 43 | 44 | @Test 45 | public void test_gettingCredentials() throws IOException 46 | { 47 | // Mock out URMCredentialsFetcher 48 | URMCredentialsFetcher mockURMCredentialsFetcher = mock(URMCredentialsFetcher.class); 49 | BasicSessionCredentials basicSessionCredentials = new BasicSessionCredentials("accesskey", "secretKey", "sessionToken"); 50 | when(mockURMCredentialsFetcher.getCredentials()).thenReturn(basicSessionCredentials); 51 | 52 | // Mock out UGI for the impersonated user, and the real user 53 | UserGroupInformation mockUgi = mock(UserGroupInformation.class); 54 | when(mockUgi.getShortUserName()).thenReturn(USER); 55 | 56 | UserGroupInformation mockRealUserUGI = mock(UserGroupInformation.class); 57 | when(mockUgi.getRealUser()).thenReturn(mockRealUserUGI); 58 | when(mockRealUserUGI.getShortUserName()).thenReturn(REALUSER); 59 | 60 | // Mock out UGI singleton 61 | PowerMockito.mockStatic(UserGroupInformation.class); 62 | BDDMockito.given(UserGroupInformation.getCurrentUser()).willReturn(mockUgi); 63 | when(mockUgi.getRealUser()).thenReturn(mockRealUserUGI); 64 | 65 | Set allowedList = new HashSet<>(Collections.singletonList(REALUSER)); 66 | URMCredentialsProvider urmCredentialsProvider = new URMCredentialsProvider(mockURMCredentialsFetcher, 67 | allowedList, USER, REALUSER); 68 | 69 | AWSCredentials returnedCreds = urmCredentialsProvider.getCredentials(); 70 | 71 | assertNotNull(returnedCreds); 72 | assertTrue(returnedCreds instanceof BasicSessionCredentials); 73 | assertEquals(basicSessionCredentials.getAWSAccessKeyId(), returnedCreds.getAWSAccessKeyId()); 74 | assertEquals(basicSessionCredentials.getAWSSecretKey(), returnedCreds.getAWSSecretKey()); 75 | assertEquals(basicSessionCredentials.getSessionToken(), ((BasicSessionCredentials) returnedCreds).getSessionToken()); 76 | } 77 | 78 | @Test 79 | public void test_AllowListFromConfiguration() 80 | { 81 | Configuration mockConfiguration = mock(Configuration.class); 82 | when(mockConfiguration.getTrimmedStrings(URMCredentialsProvider.EMRFS_SITE_CONF_ALLOWED_USERS)) 83 | .thenReturn(new String[] {"user1", "user2"}); 84 | 85 | Set users = URMCredentialsProvider.getUsersAllowedToImpersonate(mockConfiguration); 86 | 87 | assertNotNull(users); 88 | assertEquals(2, users.size()); 89 | assertTrue(users.contains("user1")); 90 | assertTrue(users.contains("user2")); 91 | } 92 | 93 | @Test 94 | public void test_ugiReturnNull() throws IOException 95 | { 96 | UserGroupInformation mockUgi = mock(UserGroupInformation.class); 97 | when(mockUgi.getRealUser()).thenReturn(null); 98 | 99 | PowerMockito.mockStatic(UserGroupInformation.class); 100 | BDDMockito.given(UserGroupInformation.getCurrentUser()).willReturn(mockUgi); 101 | 102 | String realUser = URMCredentialsProvider.getRealUser(); 103 | 104 | assertNotNull(realUser); 105 | assertEquals(System.getProperty("user.name"), realUser); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /emr-user-role-mapper-interface/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.amazonaws.emr 9 | amazon-emr-user-role-mapper 10 | 1.2.0-SNAPSHOT 11 | 12 | 13 | emr-user-role-mapper-provider 14 | Amazon EMR User Role Mapper Interface 15 | 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 22 | 8 23 | 8 24 | 25 | 26 | 27 | 28 | jar 29 | 30 | 1.11.483 31 | 32 | 33 | 34 | com.amazonaws 35 | aws-java-sdk-sts 36 | ${aws-java-sdk.version} 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /emr-user-role-mapper-interface/src/main/java/com/amazon/aws/emr/rolemapper/UserRoleMapperProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package com.amazon.aws.emr.rolemapper; 5 | 6 | import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; 7 | 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | public interface UserRoleMapperProvider { 12 | 13 | /** 14 | * Used to initialize the mapper. This is invoked once at Application start. 15 | */ 16 | void init(Map configMap); 17 | 18 | /** 19 | * Fetch the {@link AssumeRoleRequest} to assume for a given user. 20 | * 21 | * @param username 22 | * @return an {@link Optional} containing the mapped {@link AssumeRoleRequest} 23 | */ 24 | Optional getMapping(String username); 25 | 26 | /** 27 | * Refresh the mapping to consult for mapping at a periodic interval. 28 | */ 29 | void refresh(); 30 | } 31 | -------------------------------------------------------------------------------- /emr-user-role-mapper-s3storagebasedauthorizationmanager/README.md: -------------------------------------------------------------------------------- 1 | # S3 storage based authorization manager for Hive Metastore service. 2 | 3 | This is a plug in intended for Hive Metastore Service (HMS) for its authorization manager. Referring to the below link for more info regarding storage based authorization manager for Hive Metastore Service: https://cwiki.apache.org/confluence/display/Hive/Storage+Based+Authorization+in+the+Metastore+Server. This plugin can be used within Hive Metastore Server to conduct authorization for metadata requests, such as reading and updating tables. It works by impersonating the end user, and making requests to S3 using the end users permissions. If its successful, it will allow the operation. Else, it will reject the operation. 4 | 5 | ## Build 6 | 7 | Change *hive.version* in pom.xml for the version of hive that you will be using. It should work with Hive 3.x, however, if Hive 2.x is needed, then some code changes may be necessary. 8 | 9 | **NOTE:** We have not tested this plugin with HMS in Standalone mode. 10 | 11 | ``` 12 | git clone https://github.com/awslabs/amazon-emr-user-role-mapper.git 13 | cd s3storagebasedauthorizationmanager 14 | mvn clean install 15 | ``` 16 | 17 | ## Install 18 | 19 | copy the artifact jar over to path "/lib/hive/auxlib". 20 | Follow the aformentioned hive link for specific hive-site.xml config settings. 21 | Change the hive.security.metastore.authorization.manager value in /etc/hive/conf/hive-site.xml to: 22 | 23 | ``` 24 | com.amazonaws.emr.urm.hive.urmstoragebasedauthorizer.S3StorageBasedAuthorizationProvider 25 | ``` 26 | 27 | Add hive.security.metastore.authenticator.manager property to hive-site.xml and its value to: 28 | ``` 29 | org.apache.hadoop.hive.ql.security.HadoopDefaultMetastoreAuthenticator. 30 | ``` 31 | 32 | then restart hive metastore service and hive server2 service 33 | 34 | ## Special notes for PrestoSQL/Trino 35 | 36 | Trino(PrestoSql) currently is able to do impersonation when interacting with HMS when you set hive.metastore.thrift.impersonation.enabled=true for file 37 | /etc/presto/conf/catalog/hive.properties. This will enable the plugin to work for Trino as well. However, in order to fully enable all the WRITE operations, we also need to update the following configs to true: 38 | hive.allow-drop-table, hive.allow-rename-table, hive.allow-add-column, hive.allow-drop-column and hive.allow-rename-column 39 | -------------------------------------------------------------------------------- /emr-user-role-mapper-s3storagebasedauthorizationmanager/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.amazonaws.emr 8 | s3storagebasedauthorizationmanager 9 | 10 | 1.2.0-SNAPSHOT 11 | Amazon EMR User Role Mapper S3 Storage Based Authorizer for Hive 12 | jar 13 | 14 | 15 | 16 | 1.11.852 17 | 3.1.2 18 | 19 | 20 | 2.8.9 21 | 4.13 22 | 1.7.4 23 | 24 | 25 | 1.8 26 | 1.8 27 | 28 | 29 | 30 | 31 | org.apache.hive 32 | hive-common 33 | ${hive.version} 34 | 35 | 36 | org.apache.hive 37 | hive-exec 38 | ${hive.version} 39 | 40 | 41 | org.apache.hive 42 | hive-service 43 | ${hive.version} 44 | 45 | 46 | org.apache.hive 47 | hive-metastore 48 | ${hive.version} 49 | 50 | 51 | 52 | com.amazonaws.glue 53 | aws-glue-datacatalog-client-common 54 | 1.10.0-SNAPSHOT 55 | provided 56 | 57 | 58 | 59 | com.amazonaws.emr 60 | urm-credentials-provider 61 | 1.2.0-SNAPSHOT 62 | 63 | 64 | 65 | com.amazonaws 66 | aws-java-sdk-s3 67 | ${aws.version} 68 | provided 69 | 70 | 71 | com.amazonaws 72 | aws-java-sdk-core 73 | ${aws.version} 74 | provided 75 | 76 | 77 | 78 | org.powermock 79 | powermock-module-junit4 80 | ${powermock.version} 81 | test 82 | 83 | 84 | org.powermock 85 | powermock-api-mockito2 86 | ${powermock.version} 87 | test 88 | 89 | 90 | 91 | org.mockito 92 | mockito-core 93 | ${mockito.version} 94 | test 95 | 96 | 97 | 98 | junit 99 | junit 100 | ${junit.version} 101 | test 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-compiler-plugin 111 | 3.8.0 112 | 113 | 8 114 | 8 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /emr-user-role-mapper-s3storagebasedauthorizationmanager/src/main/java/com/amazonaws/emr/urm/glue/credentialsprovider/URMCredentialsProviderFactory.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.glue.credentialsprovider; 2 | 3 | import com.amazonaws.auth.AWSCredentialsProvider; 4 | import com.amazonaws.emr.urm.credentialsprovider.URMCredentialsProvider; 5 | import com.amazonaws.glue.catalog.metastore.AWSCredentialsProviderFactory; 6 | import org.apache.hadoop.hive.conf.HiveConf; 7 | 8 | public class URMCredentialsProviderFactory implements AWSCredentialsProviderFactory { 9 | 10 | @Override 11 | public AWSCredentialsProvider buildAWSCredentialsProvider(HiveConf hiveConf) { 12 | return new URMCredentialsProvider(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /emr-user-role-mapper-s3storagebasedauthorizationmanager/src/main/java/com/amazonaws/emr/urm/hive/urmstoragebasedauthorizer/S3Action.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.hive.urmstoragebasedauthorizer; 2 | 3 | public enum S3Action { 4 | NONE, 5 | WRITE, 6 | READ, 7 | ALL 8 | } 9 | -------------------------------------------------------------------------------- /emr-user-role-mapper-s3storagebasedauthorizationmanager/src/main/java/com/amazonaws/emr/urm/hive/urmstoragebasedauthorizer/URMCredentialsRetriever.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.hive.urmstoragebasedauthorizer; 2 | 3 | import com.amazonaws.auth.AWSCredentials; 4 | import com.amazonaws.auth.BasicSessionCredentials; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.apache.commons.logging.Log; 8 | import org.apache.commons.logging.LogFactory; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | import java.net.URI; 15 | import java.net.URLConnection; 16 | 17 | public class URMCredentialsRetriever 18 | { 19 | private static final String URM_ADDRESS_FOR_IMPERSONATION = "http://localhost:9944/latest/meta-data/iam/security-credentials/impersonation/"; 20 | private static final Log LOG = LogFactory.getLog(URMCredentialsRetriever.class); 21 | 22 | AWSCredentials getCredentialsForUser(String userName) { 23 | try { 24 | //Create http-client to get user mapped role credentials 25 | URI uri = URI.create(URM_ADDRESS_FOR_IMPERSONATION + userName); 26 | URLConnection connection = uri.toURL().openConnection(); 27 | InputStream response = connection.getInputStream(); 28 | try (BufferedReader rd = new BufferedReader(new InputStreamReader(response))) { 29 | StringBuilder responseString = new StringBuilder(); // or StringBuffer if Java version 5+ 30 | String line; 31 | while ((line = rd.readLine()) != null) { 32 | responseString.append(line); 33 | responseString.append('\r'); 34 | } 35 | return extractCredentialsFromResponse(responseString.toString()); 36 | } 37 | } catch (Exception e) { 38 | throw new RuntimeException(String.format("Caught exception [%s] while fetching user mapped role credentials for user %s.", e, userName), e); 39 | } 40 | } 41 | 42 | /** 43 | * Returns the AWSCredentials after parsing inputResponse. 44 | * @param inputResponse Json string 45 | * @return AWS Credentials 46 | * @throws IOException Thrown if inputResponse is not parsable. 47 | */ 48 | private AWSCredentials extractCredentialsFromResponse(String inputResponse) throws IOException 49 | { 50 | ObjectMapper objectMapper = new ObjectMapper(); 51 | JsonNode rootNode = objectMapper.readTree(inputResponse); 52 | 53 | if (rootNode == null) { 54 | throw new RuntimeException("AwsCredentialExtractor: Rootnode is null!"); 55 | } 56 | 57 | JsonNode accessKeyId = rootNode.path("AccessKeyId"); 58 | JsonNode secretAccessKey = rootNode.path("SecretAccessKey"); 59 | JsonNode sessionToken = rootNode.path("Token"); 60 | 61 | if (accessKeyId == null || secretAccessKey == null || sessionToken == null) { 62 | LOG.error(String.format("ExtractCredentialsFromResponse: AccessKeyId isNull:%s secretAccessKey isNull: %s token isNull: %s", 63 | accessKeyId == null, secretAccessKey == null, sessionToken == null)); 64 | throw new RuntimeException("ExtractCredentialsFromResponse: Credentials from URM came back with null value! "); 65 | } 66 | 67 | // Create a BasicSessionCredentials object that contains the credentials you just retrieved. 68 | return new BasicSessionCredentials(accessKeyId.asText(), secretAccessKey.asText(), sessionToken.asText()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /emr-user-role-mapper-s3storagebasedauthorizationmanager/src/test/java/com/amazonaws/emr/urm/hive/urmstoragebasedauthorizer/S3StorageBasedAuthorizationProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.emr.urm.hive.urmstoragebasedauthorizer; 2 | 3 | import com.amazonaws.AmazonServiceException; 4 | import com.amazonaws.auth.AWSCredentials; 5 | import com.amazonaws.auth.AWSCredentialsProvider; 6 | import com.amazonaws.services.s3.AmazonS3; 7 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 8 | import com.amazonaws.services.s3.model.AbortMultipartUploadRequest; 9 | import com.amazonaws.services.s3.model.AmazonS3Exception; 10 | import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; 11 | import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; 12 | import com.amazonaws.services.s3.model.ListObjectsV2Request; 13 | import com.amazonaws.services.s3.model.ListObjectsV2Result; 14 | import org.apache.hadoop.conf.Configuration; 15 | import org.apache.hadoop.hive.metastore.api.Database; 16 | import org.apache.hadoop.hive.ql.metadata.AuthorizationException; 17 | import org.apache.hadoop.hive.ql.metadata.HiveException; 18 | import org.apache.hadoop.hive.ql.security.HiveAuthenticationProvider; 19 | import org.apache.hadoop.hive.ql.security.authorization.Privilege; 20 | import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAccessControlException; 21 | 22 | import org.eclipse.jetty.http.HttpStatus; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.ArgumentCaptor; 27 | import org.mockito.Captor; 28 | import org.mockito.Mock; 29 | import org.powermock.core.classloader.annotations.PowerMockIgnore; 30 | import org.powermock.core.classloader.annotations.PrepareForTest; 31 | import org.powermock.modules.junit4.PowerMockRunner; 32 | 33 | import java.security.AccessControlException; 34 | 35 | import static junit.framework.TestCase.assertEquals; 36 | import static junit.framework.TestCase.assertTrue; 37 | import static org.mockito.ArgumentMatchers.any; 38 | import static org.mockito.ArgumentMatchers.anyBoolean; 39 | import static org.mockito.ArgumentMatchers.eq; 40 | import static org.mockito.Mockito.mock; 41 | import static org.mockito.Mockito.never; 42 | import static org.mockito.Mockito.reset; 43 | import static org.mockito.Mockito.verify; 44 | import static org.mockito.Mockito.when; 45 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 46 | 47 | @RunWith(PowerMockRunner.class) 48 | @PrepareForTest({AmazonS3ClientBuilder.class}) 49 | @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"}) 50 | public class S3StorageBasedAuthorizationProviderTest 51 | { 52 | public static final String USER = "someuser"; 53 | private static final String UPLOAD_ID = "UPLOAD_ID"; 54 | 55 | @Mock 56 | AmazonS3ClientBuilder clientBuilder; 57 | 58 | @Mock 59 | AmazonS3 s3Client; 60 | 61 | @Mock 62 | HiveAuthenticationProvider mockHiveAuthenticationProvider; 63 | 64 | @Mock 65 | URMCredentialsRetriever mockURMCredentialsRetriever; 66 | 67 | @Mock 68 | AWSCredentials mockCredentials; 69 | 70 | @Mock 71 | Database mockDatabase; 72 | 73 | @Mock 74 | Configuration mockConfiguration; 75 | 76 | @Captor 77 | ArgumentCaptor s3InitiateUploadRequestCaptor; 78 | 79 | @Captor 80 | ArgumentCaptor s3AbortUploadRequestCaptor; 81 | 82 | @Captor 83 | ArgumentCaptor s3ListObjectsV2RequestCaptor; 84 | 85 | S3StorageBasedAuthorizationProvider provider; 86 | 87 | @Before 88 | public void setUp() { 89 | mockStatic(AmazonS3ClientBuilder.class); 90 | 91 | //AWS S3 Client Builder mocking 92 | when(AmazonS3ClientBuilder.standard()).thenReturn(clientBuilder); 93 | when(clientBuilder.withCredentials(any(AWSCredentialsProvider.class))).thenReturn(clientBuilder); 94 | when(clientBuilder.build()).thenReturn(s3Client); 95 | 96 | when(mockHiveAuthenticationProvider.getUserName()).thenReturn(USER); 97 | when(mockURMCredentialsRetriever.getCredentialsForUser(eq(USER))) 98 | .thenReturn(mockCredentials); 99 | 100 | when(mockConfiguration.getBoolean(eq(S3StorageBasedAuthorizationProvider.SKIP_READ_PERMISSIONS_CONF), anyBoolean())).thenReturn(false); 101 | when(mockDatabase.getLocationUri()).thenReturn("s3://somebucket/somePrefix"); 102 | 103 | provider = new S3StorageBasedAuthorizationProvider(mockURMCredentialsRetriever); 104 | provider.setConf(mockConfiguration); 105 | provider.setAuthenticator(mockHiveAuthenticationProvider); 106 | } 107 | 108 | @Test 109 | public void test_privilegeCheckOnTable_write_privileges() 110 | throws HiveException 111 | { 112 | Privilege[] readPrivileges = new Privilege[] {}; 113 | Privilege[] writePrivileges = new Privilege[] {Privilege.ALTER_DATA}; 114 | 115 | InitiateMultipartUploadResult response = mock(InitiateMultipartUploadResult.class); 116 | when(response.getUploadId()).thenReturn(UPLOAD_ID); 117 | when(s3Client.initiateMultipartUpload(any())).thenReturn(response); 118 | 119 | provider.authorize(mockDatabase, readPrivileges, writePrivileges); 120 | 121 | verify(s3Client).initiateMultipartUpload(s3InitiateUploadRequestCaptor.capture()); 122 | verify(s3Client).abortMultipartUpload(s3AbortUploadRequestCaptor.capture()); 123 | 124 | assertEquals(s3InitiateUploadRequestCaptor.getValue().getBucketName(), "somebucket"); 125 | assertTrue(s3InitiateUploadRequestCaptor.getValue().getKey().startsWith("somePrefix/")); 126 | 127 | assertEquals(s3AbortUploadRequestCaptor.getValue().getBucketName(), "somebucket"); 128 | assertTrue(s3AbortUploadRequestCaptor.getValue().getKey().startsWith("somePrefix/")); 129 | assertEquals(s3AbortUploadRequestCaptor.getValue().getUploadId(), UPLOAD_ID); 130 | } 131 | 132 | @Test 133 | public void test_privilegeCheckOnTable_read_privileges() 134 | throws HiveException 135 | { 136 | Privilege[] readPrivileges = new Privilege[] {Privilege.SELECT}; 137 | Privilege[] writePrivileges = new Privilege[] {}; 138 | 139 | ListObjectsV2Result listObjectsV2Result = mock(ListObjectsV2Result.class); 140 | when(s3Client.listObjectsV2((ListObjectsV2Request) any())).thenReturn(listObjectsV2Result); 141 | 142 | provider.authorize(mockDatabase, readPrivileges, writePrivileges); 143 | 144 | verify(s3Client).listObjectsV2(s3ListObjectsV2RequestCaptor.capture()); 145 | 146 | assertEquals(s3ListObjectsV2RequestCaptor.getValue().getBucketName(), "somebucket"); 147 | assertTrue(s3ListObjectsV2RequestCaptor.getValue().getPrefix().startsWith("somePrefix/")); 148 | } 149 | 150 | @Test 151 | public void test_privilegeCheckOnTable_read_privileges_skipped_from_conf() 152 | throws HiveException 153 | { 154 | reset(mockConfiguration); 155 | when(mockConfiguration.getBoolean(eq(S3StorageBasedAuthorizationProvider.SKIP_READ_PERMISSIONS_CONF), anyBoolean())).thenReturn(true); 156 | 157 | Privilege[] readPrivileges = new Privilege[] {Privilege.SELECT}; 158 | Privilege[] writePrivileges = new Privilege[] {}; 159 | 160 | provider.authorize(mockDatabase, readPrivileges, writePrivileges); 161 | 162 | verify(s3Client, never()).listObjectsV2((ListObjectsV2Request) any()); 163 | } 164 | 165 | @Test(expected = AuthorizationException.class) 166 | public void test_unauthorized_s3_returns_403() throws HiveException 167 | { 168 | Privilege[] readPrivileges = new Privilege[] {}; 169 | Privilege[] writePrivileges = new Privilege[] {Privilege.ALTER_DATA}; 170 | 171 | InitiateMultipartUploadResult response = mock(InitiateMultipartUploadResult.class); 172 | when(response.getUploadId()).thenReturn(UPLOAD_ID); 173 | 174 | AmazonServiceException ase = new AmazonS3Exception("Access Denied"); 175 | ase.setErrorCode("AccessDenied"); 176 | ase.setErrorType(AmazonServiceException.ErrorType.Client); 177 | ase.setStatusCode(HttpStatus.FORBIDDEN_403); 178 | 179 | when(s3Client.initiateMultipartUpload(any())).thenThrow(ase); 180 | 181 | try { 182 | provider.authorize(mockDatabase, readPrivileges, writePrivileges); 183 | } catch (AccessControlException ace) { 184 | verify(s3Client).initiateMultipartUpload(s3InitiateUploadRequestCaptor.capture()); 185 | assertEquals(s3InitiateUploadRequestCaptor.getValue().getBucketName(), "somebucket"); 186 | assertTrue(s3InitiateUploadRequestCaptor.getValue().getKey().startsWith("somePrefix/")); 187 | 188 | throw ace; 189 | } 190 | } 191 | 192 | @Test(expected = HiveAccessControlException.class) 193 | public void test_lock_operation() throws HiveException 194 | { 195 | Privilege[] readPrivileges = new Privilege[] {Privilege.LOCK}; 196 | Privilege[] writePrivileges = new Privilege[] {}; 197 | 198 | provider.authorize(mockDatabase, readPrivileges, writePrivileges); 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.amazonaws.emr 7 | amazon-emr-user-role-mapper 8 | 1.2.0-SNAPSHOT 9 | pom 10 | Amazon EMR User Role Mapper 11 | 12 | 13 | emr-user-role-mapper-interface 14 | emr-user-role-mapper-application 15 | 16 | 17 | 18 | 19 | impersonation-credentials-provider 20 | 21 | emr-user-role-mapper-credentials-provider 22 | 23 | 24 | 25 | hive-metastore-connector 26 | 27 | emr-user-role-mapper-s3storagebasedauthorizationmanager 28 | 29 | 30 | 31 | 32 | 33 | The Apache Software License, Version 2.0 34 | http://www.apache.org/licenses/LICENSE-2.0.txt 35 | repo 36 | 37 | 38 | 39 | --------------------------------------------------------------------------------