/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security.helpers;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.security.SecureRandom;
import javax.crypto.SecretKey;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.HostnameVerifier;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.security.SignatureHelper;
import com.emc.storageos.security.authentication.AbstractHMACAuthFilter;
import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator;
import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator.SignatureKeyType;
import com.emc.storageos.security.authentication.RequestProcessingUtils;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.client.urlconnection.HTTPSProperties;
/**
* Helper class for constructing Jersey Client objects, adding HMAC signatures, etc.
*
* Once configured via its setters, this class is intended to be thread-safe in its
* operation
*/
public class ClientRequestHelper {
private static final Logger log = LoggerFactory.getLogger(ClientRequestHelper.class);
public static final int DEFAULT_READ_TIMEOUT = 30 * 1000;
public static final int DEFAULT_CONNECT_TIMEOUT = 30 * 1000;
private InternalApiSignatureKeyGenerator _keyGenerator = null;
private SignatureKeyType defaultSignatureType = SignatureKeyType.INTERNAL_API;
private int clientReadTimeout;
private int clientConnectTimeout;
// typically the default constructor will be used for clients that are not
// using signature based requests (coordinator/keygenerator not needed)
public ClientRequestHelper() {
}
/**
* Construct with the supplied coordinator
*
* @param coordinatorClient
*/
public ClientRequestHelper(CoordinatorClient coordinatorClient) {
this(coordinatorClient, DEFAULT_READ_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
}
/**
* This is the preferred constructor. Pass in an existing keygenerator that has been instantiated
* by the container.
* Construct with the supplied keyGenerator and non-default timeout values for clients
*
* @param coordinatorClient
* @param clientReadTimeout
* @param clientConnectTimeout
*/
public ClientRequestHelper(InternalApiSignatureKeyGenerator keyGen, int clientReadTimeout, int clientConnectTimeout) {
this.clientReadTimeout = clientReadTimeout;
this.clientConnectTimeout = clientConnectTimeout;
this._keyGenerator = keyGen;
this._keyGenerator.loadKeys();
}
/**
* Construct with the supplied coordinator and non-default timeout values for clients
* Use this constructor if a keygenerator is not already available.
*
* @param coordinatorClient
* @param clientReadTimeout
* @param clientConnectTimeout
*/
public ClientRequestHelper(CoordinatorClient coordinatorClient, int clientReadTimeout, int clientConnectTimeout) {
this.clientReadTimeout = clientReadTimeout;
this.clientConnectTimeout = clientConnectTimeout;
this._keyGenerator = new InternalApiSignatureKeyGenerator();
this._keyGenerator.setCoordinator(coordinatorClient);
this._keyGenerator.loadKeys();
}
public final int getClientConnectTimeout() {
return clientConnectTimeout;
}
public final void setClientConnectTimeout(int clientConnectTimeout) {
this.clientConnectTimeout = clientConnectTimeout;
}
public final int getClientReadTimeout() {
return clientReadTimeout;
}
public final void setClientReadTimeout(int clientReadTimeout) {
this.clientReadTimeout = clientReadTimeout;
}
public SignatureKeyType getDefaultSignatureType() {
return defaultSignatureType;
}
public void setDefaultSignatureType(SignatureKeyType defaultSignatureType) {
this.defaultSignatureType = defaultSignatureType;
}
/**
* Create an SSL-trusting Client using the default configurations
*
* This method adds permissive HTTPSProperties settings to the
* configuration of the client.
*
* Note: Client objects are expensive to create and largely
* thread-safe so they should be re-used across requests
*
* @return the client
*/
public Client createClient() {
return createClient(clientReadTimeout, clientConnectTimeout);
}
/**
* Create an SSL-trusting Client using the specified configurations
*
* This method adds permissive HTTPSProperties settings to the
* configuration of the client.
*
* Note: Client objects are expensive to create and largely
* thread-safe so they should be re-used across requests
*
* @param readTimeout the read timeout
* @param connectTimeout the connect timeout
* @return the client
*/
public Client createClient(int readTimeout, int connectTimeout) {
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(JacksonJaxbJsonProvider.class);
config.getProperties().put(ClientConfig.PROPERTY_READ_TIMEOUT, readTimeout);
config.getProperties().put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, connectTimeout);
return createClient(config);
}
/**
* Create an SSL-trusting Client using the specified configurations
*
* This method adds permissive HTTPSProperties settings to the
* configuration of the client.
*
* Note: Client objects are expensive to create and largely
* thread-safe so they should be re-used across requests
*
* @param config the configuration
* @return the client
*/
public Client createClient(ClientConfig config) {
try {
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, createPermissiveHTTPSProperties());
} catch (Exception ex) {
log.error("Unexpected failure while configuring trusting HTTPS properties", ex);
}
return Client.create(config);
}
/**
* Create a client request using the provided client, base URI, and request URI
*
* @param client
* @param baseURI
* @param requestPath
* @return the request
*/
public WebResource createRequest(Client client, String baseURI, String requestPath) {
return createRequest(client, URI.create(baseURI), URI.create(requestPath));
}
/**
* Create a client request using the provided client, base URI, and request URI
*
* @param client
* @param baseURI
* @param requestPath
* @return the request
*/
public WebResource createRequest(Client client, URI baseURI, URI requestPath) {
URI fullURI = baseURI.resolve(requestPath);
return client.resource(fullURI);
}
/**
* Add HMAC signature headers to this request using internal key
* (Use this version of addSignature for internal api calls)
*
* Note: In order for the signature to be valid, all query parameters
* should have been added to the request prior to calling this method
*
* @param webResource client request to sign
* @return a builder object for this request with the signatures added
*/
public Builder addSignature(WebResource webResource) {
long timestamp = System.currentTimeMillis();
return webResource.header(AbstractHMACAuthFilter.INTERNODE_HMAC, getSignature(webResource, timestamp, null))
.header(AbstractHMACAuthFilter.INTERNODE_TIMESTAMP, timestamp);
}
/**
* Add HMAC signature headers to this request using the provided key
* (Use this version of addSignature to inter vdc requests, with the key
* corresponding to the target vdc)
*
* @param webResource client request to sign
* @param key secret key
* @return a builder object for this request with the signatures added
*/
public Builder addSignature(WebResource webResource, SecretKey key) {
long timestamp = System.currentTimeMillis();
return webResource.header(AbstractHMACAuthFilter.INTERNODE_HMAC, getSignature(webResource, timestamp, key))
.header(AbstractHMACAuthFilter.INTERNODE_TIMESTAMP, timestamp);
}
/**
* Add a user token to the returned request builder
*
* @param requestBuilder the request builder
* @param token the user token
* @return the builder with token added
*/
public Builder addToken(Builder requestBuilder, String token) {
return addTokens(requestBuilder, token, null);
}
/**
* Add user and/or proxy tokens to the returned request builder
*
* @param requestBuilder the request builder
* @param token the user token (or null)
* @param proxyToken the proxy token (or null)
* @return the builder with token(s) added
*/
public Builder addTokens(Builder requestBuilder, String token, String proxyToken) {
if (StringUtils.isNotEmpty(token)) {
requestBuilder = requestBuilder.header(RequestProcessingUtils.AUTH_TOKEN_HEADER, token);
}
if (StringUtils.isNotEmpty(proxyToken)) {
requestBuilder = requestBuilder.header(RequestProcessingUtils.AUTH_PROXY_TOKEN_HEADER, proxyToken);
}
return requestBuilder;
}
/**
* Get an HMAC signature for the specified client request
*
* Note: In order for the signature to be valid, all query parameters
* should have been added to the request prior to calling this method
*
* @param webResource client request to sign
* @param timestamp timestamp of the request
* @param key optional, if supplied, signature will be computed on the fly based on
* the provided key. If omitted, the signature will be computed based on the internal api
* key cached in the keygenerator
*
* @return HMAC signature for this request
*/
private String getSignature(WebResource webResource, long timestamp, SecretKey key) {
StringBuilder buf = new StringBuilder(webResource.getURI().toString().toLowerCase());
buf.append(timestamp);
String sig = (key == null) ? _keyGenerator.sign(buf.toString(), defaultSignatureType) :
SignatureHelper.sign2(buf.toString(), key, key.getAlgorithm());
log.debug("getSignature(): buffer: {} signature: {}", buf.toString(), sig);
return sig;
}
/**
* Create an HTTPProperties object that will allow communication
* with self-signed certs
*
* @return the properties
*/
private HTTPSProperties createPermissiveHTTPSProperties() throws Exception {
// Create an SSL context that trusts all certs
SSLContext sc = null;
sc = SSLContext.getInstance("TLS");
sc.init(null, trustingTrustManager, new SecureRandom());
return new HTTPSProperties(trustingHostVerifier, sc);
}
/**
* A HostnameVerifier that trusts everything
*/
private static final HostnameVerifier trustingHostVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
/**
* A TrustManager that trusts everything
*/
private static final TrustManager[] trustingTrustManager = new TrustManager[] { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
}