/** * 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.hadoop.fs.azure; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.security.PrivilegedExceptionAction; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.azure.security.Constants; import org.apache.hadoop.fs.azure.security.SecurityUtils; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.client.Authenticator; import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.hadoop.fs.azure.WasbRemoteCallHelper.REMOTE_CALL_SUCCESS_CODE; /** * Class implementing a RemoteSASKeyGenerator. This class * uses the url passed in via the Configuration to make a * rest call to generate the required SAS Key. */ public class RemoteSASKeyGeneratorImpl extends SASKeyGeneratorImpl { public static final Logger LOG = LoggerFactory.getLogger(AzureNativeFileSystemStore.class); /** * Container SAS Key generation OP name. {@value} */ private static final String CONTAINER_SAS_OP = "GET_CONTAINER_SAS"; /** * Relative Blob SAS Key generation OP name. {@value} */ private static final String BLOB_SAS_OP = "GET_RELATIVE_BLOB_SAS"; /** * Query parameter specifying the expiry period to be used for sas key * {@value} */ private static final String SAS_EXPIRY_QUERY_PARAM_NAME = "sas_expiry"; /** * Query parameter name for the storage account. {@value} */ private static final String STORAGE_ACCOUNT_QUERY_PARAM_NAME = "storage_account"; /** * Query parameter name for the storage account container. {@value} */ private static final String CONTAINER_QUERY_PARAM_NAME = "container"; /** * Query parameter name for user info {@value} */ private static final String DELEGATION_TOKEN_QUERY_PARAM_NAME = "delegation"; /** * Query parameter name for the relative path inside the storage * account container. {@value} */ private static final String RELATIVE_PATH_QUERY_PARAM_NAME = "relative_path"; private String delegationToken = ""; private String credServiceUrl = ""; private WasbRemoteCallHelper remoteCallHelper = null; private boolean isSecurityEnabled; private boolean isKerberosSupportEnabled; public RemoteSASKeyGeneratorImpl(Configuration conf) { super(conf); } public void initialize(Configuration conf) throws IOException { LOG.debug("Initializing RemoteSASKeyGeneratorImpl instance"); try { delegationToken = SecurityUtils.getDelegationTokenFromCredentials(); } catch (IOException e) { final String msg = "Error in fetching the WASB delegation token"; LOG.error(msg, e); throw new IOException(msg, e); } try { credServiceUrl = SecurityUtils.getCredServiceUrls(conf); } catch (UnknownHostException e) { final String msg = "Invalid CredService Url, configure it correctly"; LOG.error(msg, e); throw new IOException(msg, e); } if (credServiceUrl == null || credServiceUrl.isEmpty()) { final String msg = "CredService Url not found in configuration to " + "initialize RemoteSASKeyGenerator"; LOG.error(msg); throw new IOException(msg); } remoteCallHelper = new WasbRemoteCallHelper(); this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled(); this.isKerberosSupportEnabled = conf.getBoolean( Constants.AZURE_KERBEROS_SUPPORT_PROPERTY_NAME, false); LOG.debug("Initialization of RemoteSASKeyGenerator instance successful"); } @Override public URI getContainerSASUri(String storageAccount, String container) throws SASKeyGenerationException { try { LOG.debug("Generating Container SAS Key for Container {} " + "inside Storage Account {} ", container, storageAccount); URIBuilder uriBuilder = new URIBuilder(credServiceUrl); uriBuilder.setPath("/" + CONTAINER_SAS_OP); uriBuilder.addParameter(STORAGE_ACCOUNT_QUERY_PARAM_NAME, storageAccount); uriBuilder.addParameter(CONTAINER_QUERY_PARAM_NAME, container); uriBuilder.addParameter(SAS_EXPIRY_QUERY_PARAM_NAME, "" + getSasKeyExpiryPeriod()); if (isSecurityEnabled && StringUtils.isNotEmpty(delegationToken)) { uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, this.delegationToken); } UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); UserGroupInformation connectUgi = ugi.getRealUser(); if (connectUgi == null) { connectUgi = ugi; } else { uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); } if (isSecurityEnabled && !connectUgi.hasKerberosCredentials()) { connectUgi = UserGroupInformation.getLoginUser(); } return getSASKey(uriBuilder.build(), connectUgi); } catch (URISyntaxException uriSyntaxEx) { throw new SASKeyGenerationException("Encountered URISyntaxException " + "while building the HttpGetRequest to remote cred service", uriSyntaxEx); } catch (IOException e) { throw new SASKeyGenerationException("Encountered IOException" + " while building the HttpGetRequest to remote service", e); } } @Override public URI getRelativeBlobSASUri(String storageAccount, String container, String relativePath) throws SASKeyGenerationException { try { LOG.debug("Generating RelativePath SAS Key for relativePath {} inside" + " Container {} inside Storage Account {} ", relativePath, container, storageAccount); URIBuilder uriBuilder = new URIBuilder(credServiceUrl); uriBuilder.setPath("/" + BLOB_SAS_OP); uriBuilder.addParameter(STORAGE_ACCOUNT_QUERY_PARAM_NAME, storageAccount); uriBuilder.addParameter(CONTAINER_QUERY_PARAM_NAME, container); uriBuilder.addParameter(RELATIVE_PATH_QUERY_PARAM_NAME, relativePath); uriBuilder.addParameter(SAS_EXPIRY_QUERY_PARAM_NAME, "" + getSasKeyExpiryPeriod()); if (isSecurityEnabled && StringUtils.isNotEmpty( delegationToken)) { uriBuilder.addParameter(DELEGATION_TOKEN_QUERY_PARAM_NAME, this.delegationToken); } UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); UserGroupInformation connectUgi = ugi.getRealUser(); if (connectUgi == null) { connectUgi = ugi; } else { uriBuilder.addParameter(Constants.DOAS_PARAM, ugi.getShortUserName()); } if (isSecurityEnabled && !connectUgi.hasKerberosCredentials()) { connectUgi = UserGroupInformation.getLoginUser(); } return getSASKey(uriBuilder.build(), connectUgi); } catch (URISyntaxException uriSyntaxEx) { throw new SASKeyGenerationException("Encountered URISyntaxException" + " while building the HttpGetRequest to " + " remote service", uriSyntaxEx); } catch (IOException e) { throw new SASKeyGenerationException("Encountered IOException" + " while building the HttpGetRequest to remote service", e); } } private URI getSASKey(final URI uri, UserGroupInformation connectUgi) throws URISyntaxException, SASKeyGenerationException { final RemoteSASKeyGenerationResponse sasKeyResponse; try { connectUgi.checkTGTAndReloginFromKeytab(); sasKeyResponse = connectUgi.doAs( new PrivilegedExceptionAction<RemoteSASKeyGenerationResponse>() { @Override public RemoteSASKeyGenerationResponse run() throws Exception { AuthenticatedURL.Token token = null; if (isKerberosSupportEnabled && UserGroupInformation .isSecurityEnabled() && (delegationToken == null || delegationToken.isEmpty())) { token = new AuthenticatedURL.Token(); final Authenticator kerberosAuthenticator = new KerberosDelegationTokenAuthenticator(); try { kerberosAuthenticator.authenticate(uri.toURL(), token); Validate.isTrue(token.isSet(), "Authenticated Token is NOT present. " + "The request cannot proceed."); } catch (AuthenticationException e) { throw new IOException( "Authentication failed in check authorization", e); } } return makeRemoteRequest(uri, (token != null ? token.toString() : null)); } }); } catch (InterruptedException | IOException e) { final String msg = "Error fetching SAS Key from Remote Service: " + uri; LOG.error(msg, e); if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } throw new SASKeyGenerationException(msg, e); } if (sasKeyResponse.getResponseCode() == REMOTE_CALL_SUCCESS_CODE) { return new URI(sasKeyResponse.getSasKey()); } else { throw new SASKeyGenerationException( "Remote Service encountered error in SAS Key generation : " + sasKeyResponse.getResponseMessage()); } } /** * Helper method to make a remote request. * @param uri - Uri to use for the remote request * @param token - hadoop.auth token for the remote request * @return RemoteSASKeyGenerationResponse */ private RemoteSASKeyGenerationResponse makeRemoteRequest(URI uri, String token) throws SASKeyGenerationException { try { HttpGet httpGet = new HttpGet(uri); if (token != null) { httpGet.setHeader("Cookie", AuthenticatedURL.AUTH_COOKIE + "=" + token); } String responseBody = remoteCallHelper.makeRemoteGetRequest(httpGet); ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(responseBody, RemoteSASKeyGenerationResponse.class); } catch (WasbRemoteCallException remoteCallEx) { throw new SASKeyGenerationException("Encountered RemoteCallException" + " while retrieving SAS key from remote service", remoteCallEx); } catch (JsonParseException jsonParserEx) { throw new SASKeyGenerationException("Encountered JsonParseException " + "while parsing the response from remote" + " service into RemoteSASKeyGenerationResponse object", jsonParserEx); } catch (JsonMappingException jsonMappingEx) { throw new SASKeyGenerationException("Encountered JsonMappingException" + " while mapping the response from remote service into " + "RemoteSASKeyGenerationResponse object", jsonMappingEx); } catch (IOException ioEx) { throw new SASKeyGenerationException("Encountered IOException while " + "accessing remote service to retrieve SAS Key", ioEx); } } } /** * POJO representing the response expected from a Remote * SAS Key generation service. * The remote SAS Key generation service is expected to * return SAS key in json format: * { * "responseCode" : 0 or non-zero <int>, * "responseMessage" : relavant message on failure <String>, * "sasKey" : Requested SAS Key <String> * } */ class RemoteSASKeyGenerationResponse { /** * Response code for the call. */ private int responseCode; /** * An intelligent message corresponding to * result. Specifically in case of failure * the reason for failure. */ private String responseMessage; /** * SAS Key corresponding to the request. */ private String sasKey; public int getResponseCode() { return responseCode; } public void setResponseCode(int responseCode) { this.responseCode = responseCode; } public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; } public String getSasKey() { return sasKey; } public void setSasKey(String sasKey) { this.sasKey = sasKey; } }