KNOX-1204 - KIP-11 S3 Access through Knox API Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/18e49c4b Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/18e49c4b Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/18e49c4b
Branch: refs/heads/KNOX-1204 Commit: 18e49c4bccb6319ffdc370c313f6fe62fa06d31f Parents: 43567b0 Author: Larry McCay <[email protected]> Authored: Fri Mar 30 11:22:21 2018 -0400 Committer: Larry McCay <[email protected]> Committed: Fri Mar 30 11:22:21 2018 -0400 ---------------------------------------------------------------------- ...ctIdentityAsserterDeploymentContributor.java | 4 + .../filter/AbstractIdentityAssertionFilter.java | 27 +-- .../filter/CommonIdentityAssertionFilter.java | 76 ++++++- .../filter/HadoopGroupProviderFilter.java | 1 - gateway-service-knoxs3/pom.xml | 29 ++- .../gateway/service/knoxs3/AWSPolicyModel.java | 60 +++++ .../service/knoxs3/IdentityBrokerResource.java | 133 +++++++++++ .../service/knoxs3/KnoxS3ClientBuilder.java | 227 +++++++++++++++++++ .../service/knoxs3/S3BucketsResource.java | 214 ++++++++++++++++- ....gateway.deploy.ServiceDeploymentContributor | 2 +- .../service/knoxs3/S3BucketsResourceTest.java | 4 +- .../security/ClientImpersonatedPrincipal.java | 33 +++ .../knox/gateway/security/SubjectUtils.java | 8 + .../services/s3client/KnoxS3ClientBuilder.java | 219 ------------------ .../services/s3client/S3PolicyModel.java | 60 ----- 15 files changed, 786 insertions(+), 311 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAsserterDeploymentContributor.java ---------------------------------------------------------------------- diff --git a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAsserterDeploymentContributor.java b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAsserterDeploymentContributor.java index 8f2adb8..170a5f0 100644 --- a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAsserterDeploymentContributor.java +++ b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAsserterDeploymentContributor.java @@ -50,6 +50,10 @@ public abstract class AbstractIdentityAsserterDeploymentContributor extends if (params == null) { params = new ArrayList<FilterParamDescriptor>(); } + // add resource role to params so that we can determine the service specific impersonation + // method when configured + params.add( resource.createFilterParam().name( "resource.role" ).value(resource.role() ) ); + Map<String, String> providerParams = provider.getParams(); for(Entry<String, String> entry : providerParams.entrySet()) { params.add( resource.createFilterParam().name(entry.getKey().toLowerCase()).value(entry.getValue())); http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java ---------------------------------------------------------------------- diff --git a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java index a007ab6..c2ca08d 100644 --- a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java +++ b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java @@ -23,6 +23,7 @@ import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import javax.security.auth.Subject; @@ -44,6 +45,7 @@ import org.apache.knox.gateway.audit.log4j.audit.AuditConstants; import org.apache.knox.gateway.filter.security.AbstractIdentityAssertionBase; import org.apache.knox.gateway.i18n.GatewaySpiResources; import org.apache.knox.gateway.i18n.resources.ResourcesFactory; +import org.apache.knox.gateway.security.ClientImpersonatedPrincipal; import org.apache.knox.gateway.security.GroupPrincipal; import org.apache.knox.gateway.security.ImpersonatedPrincipal; import org.apache.knox.gateway.security.PrimaryPrincipal; @@ -68,7 +70,7 @@ public abstract class AbstractIdentityAssertionFilter extends } /** - * This method returns a Stringp[] of new group principal names to use + * This method returns a String[] of new group principal names to use * based on implementation specific mapping or lookup mechanisms. * Returning null means that whatever set of GroupPrincipals is in the * provided Subject is sufficient to use and no additional mapping is required. @@ -87,7 +89,7 @@ public abstract class AbstractIdentityAssertionFilter extends */ public abstract String mapUserPrincipal(String principalName); - /** + /** * @param wrapper * @param response * @param chain @@ -95,22 +97,21 @@ public abstract class AbstractIdentityAssertionFilter extends * @param groups */ protected void continueChainAsPrincipal(HttpServletRequestWrapper request, ServletResponse response, - FilterChain chain, String mappedPrincipalName, String[] groups) throws IOException, - ServletException { + FilterChain chain, String mappedPrincipalName, String[] groups) throws IOException, ServletException { Subject subject = null; Principal impersonationPrincipal = null; Principal primaryPrincipal = null; - + // get the current subject and determine whether we need another doAs with // an impersonatedPrincipal and/or mapped group principals boolean impersonationNeeded = false; boolean groupsMapped = false; - - // look up the current Java Subject and assosciated group principals + + Set<?> currentGroups = null; + // look up the current Java Subject and associated group principals Subject currentSubject = Subject.getSubject(AccessController.getContext()); - Set<?> currentGroups = currentSubject.getPrincipals(GroupPrincipal.class); - - primaryPrincipal = (PrimaryPrincipal) currentSubject.getPrincipals(PrimaryPrincipal.class).toArray()[0]; + currentGroups = currentSubject.getPrincipals(GroupPrincipal.class); + if (primaryPrincipal != null) { if (!primaryPrincipal.getName().equals(mappedPrincipalName)) { impersonationNeeded = true; @@ -126,9 +127,9 @@ public abstract class AbstractIdentityAssertionFilter extends // TODO: log as appropriate primaryPrincipal = new PrimaryPrincipal(((HttpServletRequest) request).getUserPrincipal().getName()); } - + groupsMapped = groups != null || !currentGroups.isEmpty(); - + if (impersonationNeeded || groupsMapped) { // gonna need a new subject and doAs subject = new Subject(); @@ -139,7 +140,7 @@ public abstract class AbstractIdentityAssertionFilter extends for (Object obj : currentGroups) { principals.add((Principal)obj); } - + if (impersonationNeeded) { impersonationPrincipal = new ImpersonatedPrincipal(mappedPrincipalName); subject.getPrincipals().add(impersonationPrincipal); http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java ---------------------------------------------------------------------- diff --git a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java index 391aa9e..2e3adaf 100644 --- a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java +++ b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/CommonIdentityAssertionFilter.java @@ -32,17 +32,56 @@ import org.apache.knox.gateway.security.principal.SimplePrincipalMapper; import java.io.IOException; import java.security.AccessController; +import java.util.HashMap; public class CommonIdentityAssertionFilter extends AbstractIdentityAssertionFilter { private static final String GROUP_PRINCIPAL_MAPPING = "group.principal.mapping"; private static final String PRINCIPAL_MAPPING = "principal.mapping"; private SimplePrincipalMapper mapper = new SimplePrincipalMapper(); + private HashMap<String, Impersonation> impMap = new HashMap<String, Impersonation>(); + private String resourceRole = null; /* (non-Javadoc) * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(FilterConfig filterConfig) throws ServletException { + + /* + <param> + <name>path.segment.impersonated.principal</name> + <value>IDBROKER;credentials</value> + </param> + + <param> + <name>header.impersonated.principal</name> + <value>*;SM_USER</value> + </param> + + <param> + <name>query.param.impersonated.principal</name> + <value>WEBHDFS,HIVE;doas</value> + </param> + */ + + resourceRole = filterConfig.getInitParameter("resource.role"); + String pathSegmentImpersonation = filterConfig.getInitParameter("path.segment.impersonated.principal"); + if (pathSegmentImpersonation != null) { + String services = pathSegmentImpersonation.substring(0, pathSegmentImpersonation.indexOf(";")); + String[] svcArray = services.split(","); + String service = null; + int segmentIndex; + for (int i = 0; i < svcArray.length; i++) { + service = svcArray[i]; + segmentIndex = pathSegmentImpersonation.indexOf(";"); + if (segmentIndex != -1) { + Impersonation imp = new Impersonation(service, "path.segment", + pathSegmentImpersonation.substring(segmentIndex+1)); + impMap.put(svcArray[i], imp); + } + } + } + String principalMapping = filterConfig.getInitParameter(PRINCIPAL_MAPPING); if (principalMapping == null || principalMapping.isEmpty()) { principalMapping = filterConfig.getServletContext().getInitParameter(PRINCIPAL_MAPPING); @@ -78,7 +117,30 @@ public class CommonIdentityAssertionFilter extends AbstractIdentityAssertionFilt String principalName = getPrincipalName(subject); - String mappedPrincipalName = mapUserPrincipalBase(principalName); + String mappedPrincipalName = null; + + Impersonation imp = impMap.get(resourceRole); + if (imp != null) { + if (imp.type.equals("path.segment")) { + String pathInfo = ((HttpServletRequest)request).getPathInfo(); + String[] segments = pathInfo.split("/"); + for (int i = 0; i < segments.length; i++) { + if (segments[i].equals(imp.name)) { + if (segments.length > i+1) { + mappedPrincipalName = segments[i+1]; + break; + } + } + } + } + } + + if (mappedPrincipalName != null && !mappedPrincipalName.equals(principalName)) { + // TODO: audit this + principalName = mappedPrincipalName; + } + + mappedPrincipalName = mapUserPrincipalBase(principalName); mappedPrincipalName = mapUserPrincipal(mappedPrincipalName); String[] mappedGroups = mapGroupPrincipals(mappedPrincipalName, subject); String[] groups = mapGroupPrincipals(mappedPrincipalName, subject); @@ -140,4 +202,16 @@ public class CommonIdentityAssertionFilter extends AbstractIdentityAssertionFilt // NOP return principalName; } + + private class Impersonation { + public String service = null; + public String type = null; + public String name = null; + + public Impersonation(String service, String type, String name) { + this.service = service; + this.type = type; + this.name = name; + } + } } http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-provider-identity-assertion-hadoop-groups/src/main/java/org/apache/knox/gateway/identityasserter/hadoop/groups/filter/HadoopGroupProviderFilter.java ---------------------------------------------------------------------- diff --git a/gateway-provider-identity-assertion-hadoop-groups/src/main/java/org/apache/knox/gateway/identityasserter/hadoop/groups/filter/HadoopGroupProviderFilter.java b/gateway-provider-identity-assertion-hadoop-groups/src/main/java/org/apache/knox/gateway/identityasserter/hadoop/groups/filter/HadoopGroupProviderFilter.java index 1459498..6a5b5a4 100644 --- a/gateway-provider-identity-assertion-hadoop-groups/src/main/java/org/apache/knox/gateway/identityasserter/hadoop/groups/filter/HadoopGroupProviderFilter.java +++ b/gateway-provider-identity-assertion-hadoop-groups/src/main/java/org/apache/knox/gateway/identityasserter/hadoop/groups/filter/HadoopGroupProviderFilter.java @@ -114,7 +114,6 @@ public class HadoopGroupProviderFilter extends CommonIdentityAssertionFilter { } public String mapUserPrincipal(final String principalName) { - /* return the passed principal */ return principalName; } http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/pom.xml ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/pom.xml b/gateway-service-knoxs3/pom.xml index 37fcc52..d83613c 100644 --- a/gateway-service-knoxs3/pom.xml +++ b/gateway-service-knoxs3/pom.xml @@ -22,16 +22,29 @@ <parent> <groupId>org.apache.knox</groupId> <artifactId>gateway</artifactId> - <version>1.0.0</version> + <version>1.1.0-SNAPSHOT</version> </parent> <groupId>org.apache.knox</groupId> <artifactId>gateway-service-knoxs3</artifactId> - <version>1.0.0</version> - <name>gateway-service-health</name> + <version>1.1.0-SNAPSHOT</version> + <name>gateway-service-knoxs3</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-bom</artifactId> + <version>1.11.106</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + <dependencies> <dependency> <groupId>${gateway-group}</groupId> @@ -72,5 +85,15 @@ <groupId>org.apache.knox</groupId> <artifactId>gateway-server</artifactId> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-s3</artifactId> + <version>1.11.106</version> + </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-sts</artifactId> + <version>1.11.106</version> + </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/AWSPolicyModel.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/AWSPolicyModel.java b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/AWSPolicyModel.java new file mode 100644 index 0000000..4b51dc3 --- /dev/null +++ b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/AWSPolicyModel.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.service.knoxs3; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.apache.knox.gateway.util.JsonUtils; + +public class AWSPolicyModel { + HashMap<String, Object> policyModel = new HashMap<String, Object>(); + ArrayList<String> actionArray = new ArrayList<String>(); + HashMap<String, Object> statementMap = new HashMap<String, Object>(); + ArrayList<String> resourcesArray = new ArrayList<String>(); + + public AWSPolicyModel() { + policyModel.put("Version", "2012-10-17"); + ArrayList<Map<String, Object>> statement = new ArrayList<Map<String, Object>>(); + policyModel.put("Statement", statement ); + statement.add(statementMap); + statementMap.put("Action", actionArray ); + statementMap.put("Resource", resourcesArray); + } + + public void setEffect(String effect) { + statementMap.put("Effect", effect); + } + + public void addAction(String action) { + actionArray.add(action); + } + + public void addResource(String resource) { + resourcesArray.add(resource); + } + + public void setResource(String resource) { + statementMap.put("Resource", resource); + } + + public String toString() { + return JsonUtils.renderAsJsonString(policyModel); + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/IdentityBrokerResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/IdentityBrokerResource.java b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/IdentityBrokerResource.java new file mode 100644 index 0000000..ed235dd --- /dev/null +++ b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/IdentityBrokerResource.java @@ -0,0 +1,133 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.service.knoxs3; + +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.security.ImpersonatedPrincipal; +import org.apache.knox.gateway.security.PrimaryPrincipal; +import org.apache.knox.gateway.security.SubjectUtils; + +import com.amazonaws.auth.BasicSessionCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.securitytoken.model.GetFederationTokenResult; + +import javax.annotation.PostConstruct; +import javax.security.auth.Subject; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.io.PrintWriter; +import java.security.AccessController; +import java.security.Principal; +import java.util.Enumeration; +import java.util.Properties; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +@Path(IdentityBrokerResource.RESOURCE_PATH) +public class IdentityBrokerResource { + private static final String CREDENTIALS_API_PATH = "credentials"; + private static final String USER_CREDENTIALS_API_PATH = "credentials/{id}"; + private static KnoxS3ServiceMessages log = MessagesFactory.get(KnoxS3ServiceMessages.class); + private static final String VERSION_TAG = "api/v1"; + static final String RESOURCE_PATH = "/idbroker/" + VERSION_TAG; + + private static final String CONTENT_TYPE = "application/json"; + private static final String CACHE_CONTROL = "Cache-Control"; + private static final String NO_CACHE = "must-revalidate,no-cache,no-store"; + + private KnoxS3ClientBuilder s3b = new KnoxS3ClientBuilder(); + + @Context + HttpServletRequest request; + + @Context + private HttpServletResponse response; + + @Context + ServletContext context; + + @PostConstruct + public void init() { + s3b.init(getProperties()); + } + + private Properties getProperties() { + Properties props = new Properties(); + String paramName = null; + Enumeration<String> e = context.getInitParameterNames(); + while (e.hasMoreElements()) { + paramName = (String)e.nextElement(); + if (paramName.startsWith("s3.")) { + props.setProperty(paramName, context.getInitParameter(paramName)); + } + } + + return props; + } + + @GET + @Produces({APPLICATION_JSON}) + @Path(CREDENTIALS_API_PATH) + public Response getCredentials() { + return getCredentialsResponse(); + } + + @GET + @Produces({APPLICATION_JSON}) + @Path(USER_CREDENTIALS_API_PATH) + public Response getUserCredentials() { + return getCredentialsResponse(); + } + + private Response getCredentialsResponse() { + response.setStatus(HttpServletResponse.SC_OK); + response.setHeader(CACHE_CONTROL, NO_CACHE); + response.setContentType(CONTENT_TYPE); + PrintWriter writer = null; + try { + writer = response.getWriter(); + writer.println(getFederationToken()); + } catch (Exception e) { + log.logException("list", e); + return Response.serverError().entity(String.format("Failed to reply correctly due to : %s ", e)).build(); + } finally { + if (writer != null) { + writer.close(); + } + } + return Response.ok().build(); + } + + protected String getFederationToken() { +// Subject subject = Subject.getSubject(AccessController.getContext()); +// String username = getEffectiveUserName(subject); + GetFederationTokenResult creds = (GetFederationTokenResult) s3b.getFederationTokenResult(); + return creds.toString(); + } + + private String getEffectiveUserName(Subject subject) { + return SubjectUtils.getEffectivePrincipalName(subject); + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/KnoxS3ClientBuilder.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/KnoxS3ClientBuilder.java b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/KnoxS3ClientBuilder.java new file mode 100644 index 0000000..aab399a --- /dev/null +++ b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/KnoxS3ClientBuilder.java @@ -0,0 +1,227 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.service.knoxs3; + +import java.security.AccessController; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.security.auth.Subject; + +import org.apache.knox.gateway.security.GroupPrincipal; +import org.apache.knox.gateway.security.ImpersonatedPrincipal; +import org.apache.knox.gateway.security.PrimaryPrincipal; +import org.apache.knox.gateway.security.SubjectUtils; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicSessionCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import com.amazonaws.services.securitytoken.model.Credentials; +import com.amazonaws.services.securitytoken.model.GetFederationTokenRequest; +import com.amazonaws.services.securitytoken.model.GetFederationTokenResult; + +public class KnoxS3ClientBuilder { + private Map<String, PolicyConfig> userPolicyConfig = new HashMap<String, PolicyConfig>(); + private Map<String, PolicyConfig> groupPolicyConfig = new HashMap<String, PolicyConfig>(); + + public KnoxS3ClientBuilder() { + } + + public AmazonS3 getS3Client() { + BasicSessionCredentials sessionCredentials = (BasicSessionCredentials) getCredentials(); + + AmazonS3 s3 = AmazonS3ClientBuilder.standard().withRegion(Regions.US_EAST_1) + .withCredentials(new AWSStaticCredentialsProvider(sessionCredentials)).build(); + return s3; + } + + /** + * Get an opaque Object representation of the credentials. + * This method will only be called by callers that are aware + * of the actual form of the credentials in the given context + * and therefore able to cast it appropriately. + * @return opaque object + */ + public Object getCredentials() { + BasicSessionCredentials sessionCredentials = getSessionCredentials(); + if (sessionCredentials == null) { + throw new RuntimeException("No S3 credentials available."); + } + return sessionCredentials; + } + + public void init(Properties context) { + buildPolicyMaps(context); + } + + private void buildPolicyMaps(Properties context) { + /* + <service> + <role>KNOXS3</role> + <param> + <name>s3.user.policy.action.guest</name> + <value>s3:Get*,s3:List*</value> + </param> + <param> + <name>s3.user.policy.resource.guest</name> + <value>*</value> + </param> + <param> + <name>s3.group.policy.action.admin</name> + <value>*</value> + </param> + <param> + <name>s3.group.policy.resource.admin</name> + <value>*</value> + </param> + </service> + */ + + String paramName = null; + Enumeration<Object> e = context.keys(); + while (e.hasMoreElements()) { + paramName = (String)e.nextElement(); + if (paramName.startsWith("s3.")) { + String[] elements = paramName.split("\\."); + if (elements[1].equals("user")) { + PolicyConfig policy = userPolicyConfig.get(elements[4]); + if (policy == null) { + policy = new PolicyConfig(); + userPolicyConfig.put(elements[4], policy); + } + if (elements[3].equals("action")) { + policy.actions=context.getProperty(paramName); + } else { + policy.resources=context.getProperty(paramName); + } + if (policy.actions != null && policy.resources != null) { + buildS3PolicyModel(policy); + } + }else if (elements[1].equals("group")) { + PolicyConfig policy = groupPolicyConfig.get(elements[4]); + if (policy == null) { + policy = new PolicyConfig(); + groupPolicyConfig.put(elements[4], policy); + } + if (elements[3].equals("action")) { + policy.actions=context.getProperty(paramName); + } else { + policy.resources=context.getProperty(paramName); + } + if (policy.actions != null && policy.resources != null) { + buildS3PolicyModel(policy); + } + } + } + } + } + + private void buildS3PolicyModel(PolicyConfig policy) { + AWSPolicyModel model = new AWSPolicyModel(); + model.setEffect("Allow"); + String[] actions = policy.actions.split(","); + for (int i = 0; i < actions.length; i++) { + model.addAction(actions[i]); + } + String[] resources = policy.resources.split(","); + if (resources.length > 1) { + for (int i = 0; i < resources.length; i++) { + model.addResource(resources[i]); + } + } else { + model.setResource(resources[0]); + } + policy.policy = model.toString(); + } + + private BasicSessionCredentials getSessionCredentials() { + BasicSessionCredentials sessionCredentials = null; + try { + GetFederationTokenResult result = getFederationTokenResult(); + Credentials session_creds = result.getCredentials(); + sessionCredentials = new BasicSessionCredentials( + session_creds.getAccessKeyId(), + session_creds.getSecretAccessKey(), + session_creds.getSessionToken()); + } catch (Exception e) { + e.printStackTrace(); + } + return sessionCredentials; + } + + public GetFederationTokenResult getFederationTokenResult() { + String policy; + AWSSecurityTokenService sts_client = AWSSecurityTokenServiceClientBuilder.standard().withRegion(Regions.US_EAST_1).build(); + String username = null; + Subject subject = Subject.getSubject(AccessController.getContext()); + username = getEffectiveUserName(subject); + policy = buildPolicy(username, subject); + GetFederationTokenResult result = null; + if (policy != null) { + GetFederationTokenRequest request = new GetFederationTokenRequest(username).withPolicy(policy); + result = sts_client.getFederationToken(request); + System.out.println(result.getCredentials()); + } + return result; + } + + private String getEffectiveUserName(Subject subject) { + return SubjectUtils.getEffectivePrincipalName(subject); + } + + private String buildPolicy(String username, Subject subject) { + String policy = null; + List<String> groupNames = new ArrayList<String>(); + Object[] groups = subject.getPrincipals(GroupPrincipal.class).toArray(); + for (int i = 0; i < groups.length; i++) { + groupNames.add( + ((Principal)groups[0]).getName()); + } + + PolicyConfig config = userPolicyConfig.get(username); + if (config == null) { + // check for a group policy match + for (String groupName : groupNames) { + config = groupPolicyConfig.get(groupName); + if (config != null) { + // just accept first match for now + break; + } + } + } + if (config != null) { + policy = config.policy; + } + return policy; + } + + private class PolicyConfig { + public String actions = null; + public String resources = null; + public String policy = null; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResource.java b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResource.java index d0b25d7..b261749 100644 --- a/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResource.java +++ b/gateway-service-knoxs3/src/main/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResource.java @@ -18,24 +18,48 @@ package org.apache.knox.gateway.service.knoxs3; import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.security.ImpersonatedPrincipal; +import org.apache.knox.gateway.security.PrimaryPrincipal; +import org.apache.knox.gateway.security.SubjectUtils; import org.apache.knox.gateway.services.GatewayServices; import org.apache.knox.gateway.util.JsonUtils; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.Bucket; +import com.amazonaws.services.s3.model.GetObjectRequest; +import com.amazonaws.services.s3.model.ListObjectsRequest; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectSummary; + import javax.annotation.PostConstruct; +import javax.security.auth.Subject; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.io.Writer; +import java.security.AccessController; +import java.security.Principal; import java.util.Enumeration; import java.util.HashMap; import java.util.List; @@ -43,12 +67,16 @@ import java.util.Map; import java.util.Properties; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.APPLICATION_XML; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN; @Path(S3BucketsResource.RESOURCE_PATH) public class S3BucketsResource { private static final String BUCKETS_API_PATH = "buckets"; private static final String BUCKET_API_PATH = "buckets/{id}"; + private static final String OBJECT_API_PATH = "buckets/{bucket}/{id}"; + private static final String OBJECTS_API_PATH = "buckets/{bucket}"; private static KnoxS3ServiceMessages log = MessagesFactory.get(KnoxS3ServiceMessages.class); private static final String VERSION_TAG = "v1"; static final String RESOURCE_PATH = "/knoxs3/" + VERSION_TAG; @@ -87,14 +115,157 @@ public class S3BucketsResource { return props; } + @DELETE + @Consumes({APPLICATION_XML, APPLICATION_JSON, TEXT_PLAIN}) + @Produces({APPLICATION_JSON}) + @Path(OBJECT_API_PATH) + public Response deleteObject(@PathParam("bucket") String bucket, + @PathParam("id") String id, + @Context HttpHeaders headers) { + return getDeleteObjectResponse(bucket, id); + } + + private Response getDeleteObjectResponse(String bucket, String id) { + response.setStatus(HttpServletResponse.SC_OK); + response.setHeader(CACHE_CONTROL, NO_CACHE); + response.setContentType(CONTENT_TYPE); + PrintWriter writer = null; + try { + writer = response.getWriter(); + doDeleteObject(bucket, id); + } catch (Exception ioe) { + log.logException("create", ioe); + return Response.serverError().entity(String.format("Failed to reply correctly due to : %s ", ioe)).build(); + } finally { + if (writer != null) { + writer.close(); + } + } + return Response.ok().build(); + } + + private void doDeleteObject(String bucket, String id) { + AmazonS3 s3 = getS3Client(); + s3.deleteObject(bucket, id); + } + + @PUT + @Consumes({APPLICATION_XML, APPLICATION_JSON, TEXT_PLAIN}) + @Produces({APPLICATION_JSON}) + @Path(OBJECT_API_PATH) + public Response createObject(@PathParam("bucket") String bucket, + @PathParam("id") String id, + @Context HttpHeaders headers, String content) { + return getCreateObjectResponse(bucket, id, content); + } + + private Response getCreateObjectResponse(String bucket, String id, String content) { + response.setStatus(HttpServletResponse.SC_OK); + response.setHeader(CACHE_CONTROL, NO_CACHE); + response.setContentType(CONTENT_TYPE); + PrintWriter writer = null; + try { + writer = response.getWriter(); + doCreateObject(bucket, id, content); + } catch (Exception ioe) { + log.logException("create", ioe); + return Response.serverError().entity(String.format("Failed to reply correctly due to : %s ", ioe)).build(); + } finally { + if (writer != null) { + writer.close(); + } + } + return Response.ok().build(); + } + + private void doCreateObject(String bucket, String id, String content) { + AmazonS3 s3 = getS3Client(); + s3.putObject(bucket, id, content); + } + @PUT @Produces({APPLICATION_JSON}) @Path(BUCKET_API_PATH) public Response createBucket(@PathParam("id") String id) { + return getCreateBucketResponse(id); + } + + @GET + @Produces({APPLICATION_JSON}) + @Path(OBJECTS_API_PATH) + public Response listObjects(@PathParam("bucket") String bucket) { // use this to get to idbroker service once moved there GatewayServices services = (GatewayServices) request.getServletContext() .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); - return getCreateBucketResponse(id); + return getListObjectsResponse(bucket); + } + + private Response getListObjectsResponse(String bucket) { + response.setStatus(HttpServletResponse.SC_OK); + response.setHeader(CACHE_CONTROL, NO_CACHE); + response.setContentType(CONTENT_TYPE); + PrintWriter writer = null; + try { + writer = response.getWriter(); + writer.println(doGetObjects(bucket)); + } catch (Exception e) { + log.logException("list", e); + return Response.serverError().entity(String.format("Failed to reply correctly due to : %s ", e)).build(); + } finally { + if (writer != null) { + writer.close(); + } + } + return Response.ok().build(); + } + + private String doGetObjects(String bucket) { + AmazonS3 s3 = getS3Client(); + Map<String, Object> objectMap = new HashMap<String, Object>(); + ObjectListing objectListing = s3.listObjects(new ListObjectsRequest() + .withBucketName(bucket)); + for (S3ObjectSummary objectSummary : objectListing.getObjectSummaries()) { + objectMap.put(objectSummary.getKey(), objectSummary); + } + return JsonUtils.renderAsJsonString(objectMap); + } + + @GET + @Produces({APPLICATION_JSON}) + @Path(OBJECT_API_PATH) + public Response listObjects(@PathParam("bucket") String bucket, + @PathParam("id") String id) { + return getObjectResponse(bucket, id); + } + + private Response getObjectResponse(String bucket, String id) { + response.setStatus(HttpServletResponse.SC_OK); + response.setHeader(CACHE_CONTROL, NO_CACHE); + response.setContentType(CONTENT_TYPE); + + S3Object o = doGetObject(bucket, id); + BufferedReader reader = new BufferedReader(new InputStreamReader(o.getObjectContent())); + StreamingOutput stream = new StreamingOutput() { + @Override + public void write(OutputStream os) throws IOException, + WebApplicationException { + Writer writer = new BufferedWriter(new OutputStreamWriter(os)); + while (true) { + String line = reader.readLine(); + if (line == null) break; + writer.write(line); + } + writer.flush(); + } + }; + return Response.ok(stream).build(); + } + + private S3Object doGetObject(String bucket, String id) { + AmazonS3 s3 = getS3Client(); + S3Object object = s3.getObject(new GetObjectRequest(bucket, id)); +// System.out.println("Content-Type: " + object.getObjectMetadata().getContentType()); + return object; } private Response getCreateBucketResponse(String id) { @@ -117,7 +288,7 @@ public class S3BucketsResource { } private void doCreateBucket(String id) { - AmazonS3 s3 = s3b.getS3Client(); + AmazonS3 s3 = getS3Client(); s3.createBucket(id); } @@ -125,9 +296,6 @@ public class S3BucketsResource { @Produces({APPLICATION_JSON}) @Path(BUCKET_API_PATH) public Response deleteBucket(@PathParam("id") String id) { - // use this to get to idbroker service once moved there - GatewayServices services = (GatewayServices) request.getServletContext() - .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); return getDeleteBucketResponse(id); } @@ -152,7 +320,7 @@ public class S3BucketsResource { } private void doDeleteBucket(String id) { - AmazonS3 s3 = s3b.getS3Client(); + AmazonS3 s3 = getS3Client(); s3.deleteBucket(id); } @@ -160,9 +328,6 @@ public class S3BucketsResource { @Produces({APPLICATION_JSON}) @Path(BUCKETS_API_PATH) public Response listBuckets() { - // use this to get to idbroker service once moved there - GatewayServices services = (GatewayServices) request.getServletContext() - .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); return getListBucketsResponse(); } @@ -186,14 +351,41 @@ public class S3BucketsResource { } private String getBuckets() { - AmazonS3 s3 = s3b.getS3Client(); + AmazonS3 s3 = getS3Client(); Map<String, Object> bucketMap = new HashMap<String, Object>(); List<Bucket> buckets = s3.listBuckets(); for (Bucket bucket : buckets) { - System.out.println(bucket.getName()); bucketMap.put(bucket.getName(), bucket); } return JsonUtils.renderAsJsonString(bucketMap); } + protected AmazonS3 getS3Client() { + HashMap<String, AmazonS3> registry = (HashMap<String, AmazonS3>) + context.getAttribute("s3.client.registry"); + + if (registry == null) { + registry = new HashMap<String, AmazonS3>(); + context.setAttribute("s3.client.registry", registry); + } + Subject subject = Subject.getSubject(AccessController.getContext()); + String username = getEffectiveUserName(subject); + AmazonS3 s3 = registry.get(username); + if (s3 == null || expired(s3)) { + s3 = s3b.getS3Client(); + registry.put(username, s3); + } + return s3; + } + + private boolean expired(AmazonS3 s3) { + // TODO: change to registering a wrapper that keeps track of expiration time + // return true when a new client should be create for the effective user name + return false; + } + + private String getEffectiveUserName(Subject subject) { + return SubjectUtils.getEffectivePrincipalName(subject); + } + } http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ServiceDeploymentContributor ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ServiceDeploymentContributor b/gateway-service-knoxs3/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ServiceDeploymentContributor index fcfbdd7..14cb164 100644 --- a/gateway-service-knoxs3/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ServiceDeploymentContributor +++ b/gateway-service-knoxs3/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ServiceDeploymentContributor @@ -16,4 +16,4 @@ # limitations under the License. ########################################################################## -org.apache.knox.gateway.service.health.deploy.HealthServiceDeploymentContributor \ No newline at end of file +org.apache.knox.gateway.service.knoxs3.deploy.KnoxS3ServiceDeploymentContributor \ No newline at end of file http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-service-knoxs3/src/test/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResourceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxs3/src/test/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResourceTest.java b/gateway-service-knoxs3/src/test/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResourceTest.java index d75c7ce..ec7f438 100644 --- a/gateway-service-knoxs3/src/test/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResourceTest.java +++ b/gateway-service-knoxs3/src/test/java/org/apache/knox/gateway/service/knoxs3/S3BucketsResourceTest.java @@ -61,14 +61,14 @@ public class S3BucketsResourceTest { System.out.println(JsonUtils.renderAsJsonString(policyModel)); - S3PolicyModel model = new S3PolicyModel(); + AWSPolicyModel model = new AWSPolicyModel(); model.setEffect("Allow"); model.addAction("s3:Get*"); model.addAction("s3:List*"); model.setResource("*"); System.out.println(model); - model = new S3PolicyModel(); + model = new AWSPolicyModel(); model.setEffect("Allow"); model.addAction("s3:Get*"); model.addAction("s3:List*"); http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-spi/src/main/java/org/apache/knox/gateway/security/ClientImpersonatedPrincipal.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/security/ClientImpersonatedPrincipal.java b/gateway-spi/src/main/java/org/apache/knox/gateway/security/ClientImpersonatedPrincipal.java new file mode 100644 index 0000000..81f131c --- /dev/null +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/security/ClientImpersonatedPrincipal.java @@ -0,0 +1,33 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.security; + +import java.security.Principal; + +public class ClientImpersonatedPrincipal implements Principal { + private String name = null; + + public ClientImpersonatedPrincipal(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java index 3fc51ee..65cf9ea 100644 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java @@ -70,6 +70,14 @@ public class SubjectUtils { return name; } + /** + * Determine the effective user name. The order of precedence is: + * "mapped, client impersonated, primary. Rationale is that mapping + * is how we take actual end users and map them to in cluster users. + * Therefore, mapped users take precedence. + * @param subject + * @return + */ public static String getEffectivePrincipalName(Subject subject) { String name = null; http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/KnoxS3ClientBuilder.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/KnoxS3ClientBuilder.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/KnoxS3ClientBuilder.java deleted file mode 100644 index cebfba0..0000000 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/KnoxS3ClientBuilder.java +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.knox.gateway.service.knoxs3; - -import java.security.AccessController; -import java.security.Principal; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.security.auth.Subject; - -import org.apache.knox.gateway.security.GroupPrincipal; -import org.apache.knox.gateway.security.ImpersonatedPrincipal; -import org.apache.knox.gateway.security.PrimaryPrincipal; - -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicSessionCredentials; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.securitytoken.AWSSecurityTokenService; -import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; -import com.amazonaws.services.securitytoken.model.Credentials; -import com.amazonaws.services.securitytoken.model.GetFederationTokenRequest; -import com.amazonaws.services.securitytoken.model.GetFederationTokenResult; - -public class KnoxS3ClientBuilder { - private Map<String, PolicyConfig> userPolicyConfig = new HashMap<String, PolicyConfig>(); - private Map<String, PolicyConfig> groupPolicyConfig = new HashMap<String, PolicyConfig>(); - - public KnoxS3ClientBuilder() { - } - - public AmazonS3 getS3Client() { - BasicSessionCredentials sessionCredentials = getSessionCredentials(); - if (sessionCredentials == null) { - throw new RuntimeException("No S3 credentials available."); - } - - AmazonS3 s3 = AmazonS3ClientBuilder.standard().withRegion(Regions.US_EAST_1) - .withCredentials(new AWSStaticCredentialsProvider(sessionCredentials)).build(); - return s3; - } - - public void init(Properties context) { - buildPolicyMaps(context); - } - - private void buildPolicyMaps(Properties context) { - /* - <service> - <role>KNOXS3</role> - <param> - <name>s3.user.policy.action.guest</name> - <value>s3:Get*,s3:List*</value> - </param> - <param> - <name>s3.user.policy.resource.guest</name> - <value>*</value> - </param> - <param> - <name>s3.group.policy.action.admin</name> - <value>*</value> - </param> - <param> - <name>s3.group.policy.resource.admin</name> - <value>*</value> - </param> - </service> - */ - - String paramName = null; - Enumeration<Object> e = context.keys(); - while (e.hasMoreElements()) { - paramName = (String)e.nextElement(); - if (paramName.startsWith("s3.")) { - String[] elements = paramName.split("\\."); - if (elements[1].equals("user")) { - PolicyConfig policy = userPolicyConfig.get(elements[4]); - if (policy == null) { - policy = new PolicyConfig(); - userPolicyConfig.put(elements[4], policy); - } - if (elements[3].equals("action")) { - policy.actions=context.getProperty(paramName); - } else { - policy.resources=context.getProperty(paramName); - } - if (policy.actions != null && policy.resources != null) { - buildS3PolicyModel(policy); - } - }else if (elements[1].equals("group")) { - PolicyConfig policy = groupPolicyConfig.get(elements[4]); - if (policy == null) { - policy = new PolicyConfig(); - groupPolicyConfig.put(elements[4], policy); - } - if (elements[3].equals("action")) { - policy.actions=context.getProperty(paramName); - } else { - policy.resources=context.getProperty(paramName); - } - if (policy.actions != null && policy.resources != null) { - buildS3PolicyModel(policy); - } - } - } - } - } - - private void buildS3PolicyModel(PolicyConfig policy) { - S3PolicyModel model = new S3PolicyModel(); - model.setEffect("Allow"); - String[] actions = policy.actions.split(","); - for (int i = 0; i < actions.length; i++) { - model.addAction(actions[i]); - } - String[] resources = policy.resources.split(","); - if (resources.length > 1) { - for (int i = 0; i < resources.length; i++) { - model.addResource(resources[i]); - } - } else { - model.setResource(resources[0]); - } - policy.policy = model.toString(); - } - - private BasicSessionCredentials getSessionCredentials() { - AWSSecurityTokenService sts_client = AWSSecurityTokenServiceClientBuilder.standard().withRegion(Regions.US_EAST_1).build(); - String policy = ""; - BasicSessionCredentials sessionCredentials = null; - try { - String username = null; - Subject subject = Subject.getSubject(AccessController.getContext()); - username = getEffectiveUserName(subject); - policy = buildPolicy(username, subject); - - if (policy != null) { - GetFederationTokenRequest request = new GetFederationTokenRequest(username).withPolicy(policy); - GetFederationTokenResult result = sts_client.getFederationToken(request); - System.out.println(result.getCredentials()); - - Credentials session_creds = result.getCredentials(); - sessionCredentials = new BasicSessionCredentials( - session_creds.getAccessKeyId(), - session_creds.getSecretAccessKey(), - session_creds.getSessionToken()); - } - } catch (Exception e) { - e.printStackTrace(); - } - return sessionCredentials; - } - - private String getEffectiveUserName(Subject subject) { - String username; - Principal primaryPrincipal = (Principal)subject.getPrincipals(PrimaryPrincipal.class).toArray()[0]; - Object[] impersonations = subject.getPrincipals(ImpersonatedPrincipal.class).toArray(); - if (impersonations.length > 0) { - username = ((Principal)impersonations[0]).getName(); - } - else { - username = primaryPrincipal.getName(); - } - return username; - } - - private String buildPolicy(String username, Subject subject) { - String policy = null; - List<String> groupNames = new ArrayList<String>(); - Object[] groups = subject.getPrincipals(GroupPrincipal.class).toArray(); - for (int i = 0; i < groups.length; i++) { - groupNames.add( - ((Principal)groups[0]).getName()); - } - - PolicyConfig config = userPolicyConfig.get(username); - if (config == null) { - // check for a group policy match - for (String groupName : groupNames) { - config = groupPolicyConfig.get(groupName); - if (config != null) { - // just accept first match for now - break; - } - } - } - if (config != null) { - policy = config.policy; - } - return policy; - } - - private class PolicyConfig { - public String actions = null; - public String resources = null; - public String policy = null; - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/knox/blob/18e49c4b/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/S3PolicyModel.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/S3PolicyModel.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/S3PolicyModel.java deleted file mode 100644 index f7f3084..0000000 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/s3client/S3PolicyModel.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.knox.gateway.service.knoxs3; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.apache.knox.gateway.util.JsonUtils; - -public class S3PolicyModel { - HashMap<String, Object> policyModel = new HashMap<String, Object>(); - ArrayList<String> actionArray = new ArrayList<String>(); - HashMap<String, Object> statementMap = new HashMap<String, Object>(); - ArrayList<String> resourcesArray = new ArrayList<String>(); - - public S3PolicyModel() { - policyModel.put("Version", "2012-10-17"); - ArrayList<Map<String, Object>> statement = new ArrayList<Map<String, Object>>(); - policyModel.put("Statement", statement ); - statement.add(statementMap); - statementMap.put("Action", actionArray ); - statementMap.put("Resource", resourcesArray); - } - - public void setEffect(String effect) { - statementMap.put("Effect", effect); - } - - public void addAction(String action) { - actionArray.add(action); - } - - public void addResource(String resource) { - resourcesArray.add(resource); - } - - public void setResource(String resource) { - statementMap.put("Resource", resource); - } - - public String toString() { - return JsonUtils.renderAsJsonString(policyModel); - } -}
