Permalink
Browse files

Fixes EUCA-3112. Updated signature method for Walrus internal requests.

New signature method for internal Walrus requests that are signed using
eucalyptus system certificates. Called 'EUCA2' authentication it uses a
single Authorization header of the form:

Authorization: EUCA2-RSA-SHA256 <cert fingerprint> <signed-header-list> <signature>

This also requires changes to the eucadmin tools/euca2ools that handle
the bundle upload from NCs. This fix includes changes to the Java client, server,
and Walrus C client in the NC.

Also includes changes to the SystemCredentials class to calculate certificate
fingerprints for lookup. There are not stored in the DB, just calculated at
load time.

Conflicts:
	util/Makefile
	util/euca_auth.c
  • Loading branch information...
1 parent 5225aa1 commit b8a99baed5d24b57fd7124905370f038dfe2c547 Zach Hill committed Jan 16, 2013
Showing with 2,543 additions and 286 deletions.
  1. +1 −0 clc/.classpath
  2. +5 −5 clc/modules/msgs/src/main/java/com/eucalyptus/auth/login/WalrusWrappedComponentCredentials.java
  3. +24 −0 clc/modules/msgs/src/main/java/com/eucalyptus/auth/util/X509CertHelper.java
  4. +2 −1 clc/modules/msgs/src/main/java/com/eucalyptus/component/Partition.java
  5. +8 −0 clc/modules/msgs/src/main/java/com/eucalyptus/component/auth/SystemCredentials.java
  6. +1 −1 clc/modules/msgs/src/main/java/com/eucalyptus/http/MappingHttpRequest.java
  7. +2 −1 clc/modules/storage-controller/src/main/java/edu/ucsb/eucalyptus/cloud/ws/HttpReader.java
  8. +203 −18 clc/modules/storage-controller/src/main/java/edu/ucsb/eucalyptus/cloud/ws/HttpTransfer.java
  9. +23 −6 clc/modules/storage-controller/src/main/java/edu/ucsb/eucalyptus/cloud/ws/HttpWriter.java
  10. +42 −36 clc/modules/walrus/src/main/java/com/eucalyptus/auth/login/WalrusComponentLoginModule.java
  11. 0 clc/modules/walrus/src/{main → test}/java/edu/ucsb/eucalyptus/cloud/ws/tests/BukkitImageTest.java
  12. 0 clc/modules/walrus/src/{main → test}/java/edu/ucsb/eucalyptus/cloud/ws/tests/BukkitTest.java
  13. 0 clc/modules/walrus/src/{main → test}/java/edu/ucsb/eucalyptus/cloud/ws/tests/ImageCacheTest.java
  14. 0 clc/modules/walrus/src/{main → test}/java/edu/ucsb/eucalyptus/cloud/ws/tests/ObjectTest.java
  15. +105 −0 clc/modules/walrus/src/test/java/edu/ucsb/eucalyptus/cloud/ws/tests/WalrusAuthenticationTest.java
  16. 0 clc/modules/walrus/src/{main → test}/java/edu/ucsb/eucalyptus/cloud/ws/tests/WalrusBucketTests.java
  17. +560 −188 clc/modules/wsstack/src/main/java/com/eucalyptus/ws/handlers/WalrusAuthenticationHandler.java
  18. +1 −1 clc/modules/wsstack/src/main/java/com/eucalyptus/ws/handlers/WalrusRESTBinding.java
  19. +74 −0 cluster/cc-client-policy.xml
  20. +74 −0 node/nc-client-policy.xml
  21. +246 −7 storage/walrus.c
  22. +3 −0 util/Makefile
  23. +990 −15 util/euca_auth.c
  24. +179 −7 util/euca_auth.h
View
1 clc/.classpath
@@ -27,6 +27,7 @@
<classpathentry kind="src" path="modules/storage-controller/src/main/java"/>
<classpathentry kind="src" path="modules/walrus/src/main/java"/>
<classpathentry kind="src" path="modules/walrus/conf/drbd"/>
+ <classpathentry kind="src" path="modules/walrus/src/test/java"/>
<classpathentry kind="src" path="modules/wsstack/src/main/java"/>
<classpathentry kind="src" path="modules/wsstack/conf/scripts"/>
<classpathentry kind="src" path="modules/www/src/main/java"/>
View
10 ...dules/msgs/src/main/java/com/eucalyptus/auth/login/WalrusWrappedComponentCredentials.java
@@ -65,14 +65,14 @@
public class WalrusWrappedComponentCredentials extends WrappedCredentials<String> {
private String queryId;
private String signature;
- private String certString;
+ private String certMD5Fingerprint;
public WalrusWrappedComponentCredentials(String correlationId, String data,
- String accessKeyId, String signature, String certString) {
+ String accessKeyId, String signature, String certFingerprint) {
super( correlationId, data );
this.queryId = accessKeyId;
this.signature = signature;
- this.certString = certString;
+ this.certMD5Fingerprint = certFingerprint;
}
public String getQueryId() {
@@ -83,7 +83,7 @@ public String getSignature() {
return this.signature;
}
- public String getCertString() {
- return certString;
+ public String getCertMD5Fingerprint() {
+ return this.certMD5Fingerprint;
}
}
View
24 clc/modules/msgs/src/main/java/com/eucalyptus/auth/util/X509CertHelper.java
@@ -63,6 +63,7 @@
package com.eucalyptus.auth.util;
import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import com.eucalyptus.crypto.util.B64;
@@ -102,4 +103,27 @@ public static String privateKeyToPem( PrivateKey pk ) {
}
}
+ public static String calcFingerprint(X509Certificate cert) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] der = cert.getEncoded();
+ md.update(der);
+ byte[] digest = md.digest();
+ return hexify(digest);
+ } catch(Exception e) {
+ return null;
+ }
+ }
+
+ public static String hexify (byte bytes[]) {
+ StringBuilder builder = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ builder.append(Integer.toHexString((b & 0xf0) >> 4));
+ builder.append(Integer.toHexString(b & 0x0f));
+ }
+
+ return builder.toString();
+ }
+
+
}
View
3 clc/modules/msgs/src/main/java/com/eucalyptus/component/Partition.java
@@ -82,6 +82,7 @@
import org.hibernate.annotations.Entity;
import org.hibernate.annotations.Type;
import org.hibernate.type.StringClobType;
+import com.eucalyptus.auth.util.X509CertHelper;
import com.eucalyptus.bootstrap.SystemIds;
import com.eucalyptus.component.auth.SystemCredentials;
import com.eucalyptus.component.id.Eucalyptus;
@@ -161,7 +162,7 @@ public PrivateKey getNodePrivateKey( ) {
public PrivateKey getPrivateKey( ) {
return PEMFiles.toKeyPair( this.getPemPrivateKey( ) ).getPrivate( );
}
-
+
@PrePersist
void prepareKeyDirectory( ) {
File keyDir = SubDirectory.KEYS.getChildFile( this.name );
View
8 clc/modules/msgs/src/main/java/com/eucalyptus/component/auth/SystemCredentials.java
@@ -71,6 +71,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.Logger;
+
+import com.eucalyptus.auth.util.X509CertHelper;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.Bootstrapper;
import com.eucalyptus.bootstrap.DependsLocal;
@@ -118,12 +120,14 @@
private final String name;
private final X509Certificate cert;
private final KeyPair keyPair;
+ private final String certFingerprint;
private Credentials( ComponentId componentId ) throws Exception {
this.componentId = componentId;
this.name = componentId.name( );
this.cert = loadCertificate( componentId );
this.keyPair = loadKeyPair( componentId );
+ this.certFingerprint = X509CertHelper.calcFingerprint(this.cert);
EventRecord.here( SystemCredentials.class, EventType.COMPONENT_INFO, "initialized", this.name, this.cert.getSubjectDN( ).toString( ) ).info( );
SystemCredentials.providers.put( this.name, this );
}
@@ -206,6 +210,10 @@ public KeyPair getKeyPair( ) {
return this.keyPair;
}
+ public String getCertFingerprint() {
+ return this.certFingerprint;
+ }
+
}
static boolean checkKeystore( ComponentId name ) throws Exception {
View
2 clc/modules/msgs/src/main/java/com/eucalyptus/http/MappingHttpRequest.java
@@ -85,7 +85,7 @@
private final String uri;
private String servicePath;
private String query;
- private final Map<String, String> parameters;
+ private final Map<String, String> parameters; //Parameters are URLDecoded when populated
private String restNamespace;
private final Map formFields;
View
3 clc/modules/storage-controller/src/main/java/edu/ucsb/eucalyptus/cloud/ws/HttpReader.java
@@ -102,7 +102,8 @@ public HttpReader(String path, LinkedBlockingQueue<WalrusDataMessage> getQueue,
String httpVerb = "GET";
String addr = StorageProperties.WALRUS_URL + "/" + path;
- method = constructHttpMethod(httpVerb, addr, eucaOperation, eucaHeader);
+ method = constructHttpMethod(httpVerb, addr, eucaOperation, eucaHeader,true);
+ //signEucaInternal(method);
}
public HttpReader(String path, LinkedBlockingQueue<WalrusDataMessage> getQueue, File file, String eucaOperation, String eucaHeader, boolean compressed, String tempPath) {
View
221 clc/modules/storage-controller/src/main/java/edu/ucsb/eucalyptus/cloud/ws/HttpTransfer.java
@@ -65,31 +65,222 @@
import java.net.URL;
import java.security.PrivateKey;
import java.security.Signature;
+import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
+import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.httpclient.util.DateUtil;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import com.eucalyptus.component.auth.SystemCredentials;
+import com.eucalyptus.component.auth.SystemCredentials.Credentials;
import com.eucalyptus.component.id.Storage;
import com.eucalyptus.util.StorageProperties;
+import com.google.common.base.Strings;
+import java.util.Arrays;
//All HttpTransfer operations should be called asynchronously. The operations themselves are synchronous.
-public class HttpTransfer {
-
+public class HttpTransfer {
private static Logger LOG = Logger.getLogger(HttpTransfer.class);
+
+ protected static final String EUCA2_AUTH_ID = "EUCA2-RSA-SHA256";
+ protected static final String EUCA2_AUTH_HEADER_NAME = "Authorization";
+ protected static final String ISO_8601_FORMAT = "yyyyMMdd'T'HHmmss'Z'"; //Use the ISO8601 format
+
+ /**
+ * Calculates and sets the Authorization header value for the request using the EucaRSA-V2 signing algorithm
+ * Algorithm Overview:
+ *
+ * 1. Generate the canonical Request
+ * a.) CanonicalRequest =
+ * HTTPRequestMethod + '\n' +
+ * CanonicalURI + '\n' +
+ * CanonicalQueryString + '\n' +
+ * CanonicalHeaders + '\n' +
+ * SignedHeaders
+ * b.) Where CanonicalURI =
+ * c.) Where CanonicalQueryString =
+ * d.) Where CanonicalHeaders = sorted (by lowercased header name) ';' delimited list of <lowercase(headername)>:<value> items
+ * e.) Where SignedHeaders = sorted, ';' delimited list of headers in CanonicalHeaders
+ *
+ * 2. Signature = RSA(privkey, SHA256(CanonicalRequest))
+ *
+ * 3. Add an Authorization HTTP header to the request that contains the following strings, separated by spaces:
+ * EUCA2-RSA-SHA256
+ * The lower-case hexadecimal encoding of the component's X.509 certificate's md5 fingerprint
+ * The SignedHeaders list calculated in Task 1
+ * The Base64 encoding of the Signature calculated in Task 2
+ *
+ * @param httpBaseRequest -- the request, the 'Authorization' header will be added to the request
+ */
+ public static void signEucaInternal(HttpMethodBase httpBaseRequest) {
+ StringBuilder canonicalRequest = new StringBuilder();
+ String canonicalURI = null;
+ String verb = httpBaseRequest.getName();
+ canonicalURI = httpBaseRequest.getPath();
+
+ String canonicalQuery = calcCanonicalQuery(httpBaseRequest);
+ String[] processedHeaders = getCanonicalAndSignedHeaders(httpBaseRequest);
+ String canonicalHeaders = processedHeaders[0];
+ String signedHeaders = processedHeaders[1];
+
+ canonicalRequest.append(verb).append('\n');
+ canonicalRequest.append(canonicalURI).append('\n');
+ canonicalRequest.append(canonicalQuery).append('\n');
+ canonicalRequest.append(canonicalHeaders).append('\n');
+ canonicalRequest.append(signedHeaders);
- public HttpMethodBase constructHttpMethod(String verb, String addr, String eucaOperation, String eucaHeader) {
- String date = new Date().toString();
+ StringBuilder authHeader = new StringBuilder(EUCA2_AUTH_ID);
+ String signature = null;
+ String fingerprint = null;
+ try {
+ Credentials ccCreds = SystemCredentials.lookup(Storage.class);
+ PrivateKey ccPrivateKey = ccCreds.getPrivateKey();
+ fingerprint = ccCreds.getCertFingerprint();
+ Signature sign = Signature.getInstance("SHA256withRSA");
+ sign.initSign(ccPrivateKey);
+ LOG.debug("Signing canonical request: " + canonicalRequest.toString());
+ sign.update(canonicalRequest.toString().getBytes());
+ byte[] sig = sign.sign();
+ signature = new String(Base64.encode(sig));
+ } catch(Exception ex) {
+ LOG.error("Signing error while signing request", ex);
+ }
+
+ authHeader.append(" ").append(fingerprint.toLowerCase()).append(" ").append(signedHeaders.toString()).append(" ").append(signature);
+ httpBaseRequest.addRequestHeader(EUCA2_AUTH_HEADER_NAME, authHeader.toString());
+ }
+
+ /**
+ * Constructs and returns the canonicalQuery string per EucaRSA-V2 specs.
+ * @param httpBaseRequest
+ * @return
+ */
+ private static String calcCanonicalQuery(HttpMethodBase httpBaseRequest) {
+ StringBuilder canonicalQuery = new StringBuilder();
+ //Sort query elements, assume all values are already URL encoded
+ String tmpQuery = httpBaseRequest.getQueryString();
+ HashMap<String, String> parameters = new HashMap<String,String>();
+ if(!Strings.isNullOrEmpty(tmpQuery)) {
+ String[] rawQueryParams = tmpQuery.split("&");
+ String[] queryParamNames = new String[rawQueryParams.length];
+ String[] tmpKV = null;
+ int i = 0;
+ for(String paramKV : rawQueryParams) {
+ tmpKV = paramKV.split("=");
+ queryParamNames[i++] = tmpKV[0];
+ if(tmpKV.length == 2) {
+ parameters.put(tmpKV[0],tmpKV[1]);
+ }
+ else {
+ parameters.put(tmpKV[0], "");
+ }
+ }
+
+ Arrays.sort(queryParamNames);
+ for(String paramName : queryParamNames) {
+ canonicalQuery.append(paramName).append('=');
+ if(parameters.get(paramName) != null) {
+ canonicalQuery.append(parameters.get(paramName));
+ } else {
+ //Single key, no value
+ canonicalQuery.append("");
+ }
+ canonicalQuery.append('&');
+ }
+
+ if(canonicalQuery.length() > 0) {
+ canonicalQuery.deleteCharAt(canonicalQuery.length() -1); //Delete the trailing '&'
+ }
+ }
+ return canonicalQuery.toString();
+ }
+
+ /**
+ * Calculates the canonical and signed header strings in a single pass, done in one pass for efficiency
+ * @param httpBaseRequest
+ * @return Array of 2 elements, first element is the canonicalHeader string, second element is the signedHeaders string
+ */
+ private static String[] getCanonicalAndSignedHeaders(HttpMethodBase httpBaseRequest) {
+ /*
+ * The host header is required for EucaV2 signing, but it is not constructed by the HttpClient until the method is executed.
+ * So, here we add a header with the same value and name so that we can do the proper sigining, but know that this value will
+ * be overwritten when HttpMethodBase is executed to send the request.
+ *
+ * This code is specific to the jakarta commons httpclient because that client will set the host header to hostname:port rather than
+ * just hostname.
+ *
+ * Supposedly you can force the value of the Host header with: httpBaseRequest.getParams().setVirtualHost("hostname"), but that was not successful
+ */
+ try {
+ httpBaseRequest.addRequestHeader("Host", httpBaseRequest.getURI().getHost() + ":" + httpBaseRequest.getURI().getPort());
+ } catch(URIException e) {
+ LOG.error("Could not add Host header for canonical headers during authorization header creation in HTTP client: ",e);
+ return null;
+ }
+
+ Header[] headers = httpBaseRequest.getRequestHeaders();
+ StringBuilder signedHeaders = new StringBuilder();
+ StringBuilder canonicalHeaders = new StringBuilder();
+
+ if(headers != null) {
+ Arrays.sort(headers, new Comparator<Header>() {
+ @Override
+ public int compare(Header arg0, Header arg1) {
+ return arg0.getName().toLowerCase().compareTo(arg1.getName().toLowerCase());
+ }
+
+ });
+
+ for(Header header : headers) {
+ //Add to the signed headers
+ signedHeaders.append(header.getName().toLowerCase()).append(';');
+ //Add the name and value to the canonical header
+ canonicalHeaders.append(header.getName().toLowerCase()).append(':').append(header.getValue().trim()).append('\n');
+ }
+
+ if(signedHeaders.length() > 0) {
+ signedHeaders.deleteCharAt(signedHeaders.length() - 1); //Delete the trailing semi-colon
+ }
+
+ if(canonicalHeaders.length() > 0) {
+ canonicalHeaders.deleteCharAt(canonicalHeaders.length() -1); //Delete the trialing '\n' just to make things clear and consistent
+ }
+ }
+ String[] result = new String[2];
+ result[0] = canonicalHeaders.toString();
+ result[1] = signedHeaders.toString();
+ return result;
+ }
+
+ /**
+ * Constructs the requested method, optionally signing the request via EucaRSA-V2 signing method if signRequest=true
+ * Signing the request can be done later as well by explicitly calling signEucaInternal() and passing it the output of this method.
+ * That case is useful for constructing the request and then adding headers explicitly before signing takes place.
+ * @param verb - The HTTP verb GET|PUT|POST|DELETE|UPDATE
+ * @param addr - THe destination address for the request
+ * @param eucaOperation - The EucaOperation, if any (e.g. StoreSnapshot, GetWalrusSnapshot, or other values from WalrusProperties.StorageOperations)
+ * @param eucaHeader - The Euca Header value, if any. This is not typically used.
+ * @param signRequest - Determines if the request is signed at construction time or must be done explicitly later (boolean)
+ * @return
+ */
+ public HttpMethodBase constructHttpMethod(String verb, String addr, String eucaOperation, String eucaHeader, boolean signRequest) {
+ String date = DateUtil.formatDate(new Date(),ISO_8601_FORMAT);
+ //String date = new Date().toString();
String httpVerb = verb;
- String addrPath;
+ String addrPath = null;
+ java.net.URI addrUri = null;
try {
- java.net.URI addrUri = new URL(addr).toURI();
+ addrUri = new URL(addr).toURI();
addrPath = addrUri.getPath().toString();
String query = addrUri.getQuery();
if(query != null) {
@@ -99,7 +290,6 @@ public HttpMethodBase constructHttpMethod(String verb, String addr, String eucaO
LOG.error(ex, ex);
return null;
}
- String data = httpVerb + "\n" + date + "\n" + addrPath + "\n";
HttpMethodBase method = null;
if(httpVerb.equals("PUT")) {
@@ -109,24 +299,19 @@ public HttpMethodBase constructHttpMethod(String verb, String addr, String eucaO
} else {
method = new GetMethod(addr);
}
- method.setRequestHeader("Authorization", "Euca");
+
method.setRequestHeader("Date", date);
//method.setRequestHeader("Expect", "100-continue");
+
method.setRequestHeader(StorageProperties.EUCALYPTUS_OPERATION, eucaOperation);
if(eucaHeader != null) {
method.setRequestHeader(StorageProperties.EUCALYPTUS_HEADER, eucaHeader);
}
- try {
- PrivateKey ccPrivateKey = SystemCredentials.lookup(Storage.class).getPrivateKey();
- Signature sign = Signature.getInstance("SHA1withRSA");
- sign.initSign(ccPrivateKey);
- sign.update(data.getBytes());
- byte[] sig = sign.sign();
-
- method.setRequestHeader("EucaSignature", new String(Base64.encode(sig)));
- } catch(Exception ex) {
- LOG.error(ex, ex);
+
+ if(signRequest) {
+ signEucaInternal(method);
}
+
return method;
}
View
29 clc/modules/storage-controller/src/main/java/edu/ucsb/eucalyptus/cloud/ws/HttpWriter.java
@@ -66,27 +66,31 @@
import java.util.Map;
import java.util.Set;
+import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.log4j.Logger;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.StorageProperties;
public class HttpWriter extends HttpTransfer {
-
+ private static Logger LOG = Logger.getLogger(HttpWriter.class);
+
private HttpClient httpClient;
private HttpMethodBase method;
public HttpWriter(String httpVerb, String bucket, String key, String eucaOperation, String eucaHeader) {
httpClient = new HttpClient();
String walrusAddr = StorageProperties.WALRUS_URL;
if(walrusAddr != null) {
String addr = walrusAddr + "/" + bucket + "/" + key;
- method = constructHttpMethod(httpVerb, addr, eucaOperation, eucaHeader);
+ method = constructHttpMethod(httpVerb, addr, eucaOperation, eucaHeader, true);
}
}
public HttpWriter(String httpVerb, File file, String size, CallBack callback, String bucket, String key, String eucaOperation, String eucaHeader, Map<String, String> httpParameters) {
+
httpClient = new HttpClient();
String walrusAddr = StorageProperties.WALRUS_URL;
if(walrusAddr != null) {
@@ -105,16 +109,29 @@ public HttpWriter(String httpVerb, File file, String size, CallBack callback, St
if(value != null)
addr += "=" + value;
}
- method = constructHttpMethod(httpVerb, addr, eucaOperation, eucaHeader);
- if(method != null) {
+ method = constructHttpMethod(httpVerb, addr, eucaOperation, eucaHeader, false);
+ if(method != null) {
+ method.addRequestHeader(StorageProperties.StorageParameters.EucaSnapSize.toString(), size);
method.setRequestHeader("Transfer-Encoding", "chunked");
- method.addRequestHeader(StorageProperties.StorageParameters.EucaSnapSize.toString(), size);
+
+ //Sign the request
+ signEucaInternal(method);
+
((PutMethodWithProgress)method).setOutFile(file);
((PutMethodWithProgress)method).setCallBack(callback);
- }
+ }
}
}
+ private String print() {
+ StringBuilder requestString = new StringBuilder();
+ for(Header h : method.getRequestHeaders()) {
+ requestString.append("Header name: " + h.getName() + " = " + h.getValue() + "\n");
+ }
+
+ return requestString.toString();
+ }
+
public void run() throws EucalyptusCloudException {
try {
httpClient.executeMethod(method);
View
78 clc/modules/walrus/src/main/java/com/eucalyptus/auth/login/WalrusComponentLoginModule.java
@@ -62,8 +62,11 @@
package com.eucalyptus.auth.login;
+import java.io.IOException;
+import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
+import java.security.SignatureException;
import java.security.cert.X509Certificate;
import org.apache.log4j.Logger;
import org.apache.xml.security.utils.Base64;
@@ -75,7 +78,7 @@
import com.eucalyptus.component.auth.SystemCredentials;
import com.eucalyptus.auth.api.BaseLoginModule;
import com.eucalyptus.auth.principal.User;
-import com.eucalyptus.auth.util.Hashes;
+import com.eucalyptus.auth.util.X509CertHelper;
import com.eucalyptus.component.id.Storage;
public class WalrusComponentLoginModule extends BaseLoginModule<WalrusWrappedComponentCredentials> {
@@ -93,44 +96,47 @@ public boolean authenticate( WalrusWrappedComponentCredentials credentials ) thr
boolean valid = false;
String data = credentials.getLoginData();
String signature = credentials.getSignature();
+ boolean found = false;
+ X509Certificate signingCert = null;
+
try {
- try {
- PublicKey publicKey = SystemCredentials.lookup(Storage.class).getCertificate().getPublicKey();
- sig = Signature.getInstance("SHA1withRSA");
- sig.initVerify(publicKey);
- sig.update(data.getBytes());
- valid = sig.verify(Base64.decode(signature));
- } catch ( Exception e ) {
- LOG.warn ("Authentication: certificate not found in keystore");
- } finally {
- if( !valid && credentials.getCertString() != null ) {
- try {
- boolean found = false;
- X509Certificate nodeCert = Hashes.getPemCert( Base64.decode( credentials.getCertString() ) );
- for (Partition part : Partitions.list()) {
- if (nodeCert.equals(part.getNodeCertificate())) {
- found = true;
- break;
- }
- }
- if (!found) {
- throw new AuthenticationException("Invalid certificate");
- }
- if(nodeCert != null) {
- PublicKey publicKey = nodeCert.getPublicKey( );
- sig = Signature.getInstance( "SHA1withRSA" );
- sig.initVerify( publicKey );
- sig.update( data.getBytes( ) );
- valid = sig.verify( Base64.decode( signature ) );
- }
- } catch ( Exception e2 ) {
- LOG.error ("Authentication error: " + e2.getMessage());
- return false;
- }
+ //Find which cert to use based on the fingerprint, which is in the certstring.
+ String scFingerprint = SystemCredentials.lookup(Storage.class).getCertFingerprint();
+
+ //Check the SC first
+ if(scFingerprint.equals(credentials.getCertMD5Fingerprint())) {
+ found = true;
+ signingCert = SystemCredentials.lookup(Storage.class).getCertificate();
+ }
+ else {
+ //Check the NCs and CCs credentials for a match
+ for(Partition part : Partitions.list()) {
+ if(X509CertHelper.calcFingerprint(part.getCertificate()).equals(credentials.getCertMD5Fingerprint())) {
+ signingCert = part.getCertificate();
+ found = true;
+ break;
+ }
+ else if(X509CertHelper.calcFingerprint(part.getNodeCertificate()).equals(credentials.getCertMD5Fingerprint())) {
+ signingCert = part.getNodeCertificate();
+ found = true;
+ break;
+ }
}
}
- } catch (Exception ex) {
- LOG.error ("Authentication error: " + ex.getMessage());
+
+ if (!found) {
+ throw new AuthenticationException("Invalid certificate");
+ }
+
+ if(signingCert != null) {
+ PublicKey publicKey = signingCert.getPublicKey( );
+ sig = Signature.getInstance( "SHA256withRSA" );
+ sig.initVerify( publicKey );
+ sig.update(data.getBytes());
+ valid = sig.verify( Base64.decode( signature ) );
+ }
+ } catch ( Exception e2 ) {
+ LOG.error ("Authentication error: " + e2.getMessage());
return false;
}
View
0 ...yptus/cloud/ws/tests/BukkitImageTest.java → ...yptus/cloud/ws/tests/BukkitImageTest.java
File renamed without changes.
View
0 ...eucalyptus/cloud/ws/tests/BukkitTest.java → ...eucalyptus/cloud/ws/tests/BukkitTest.java
File renamed without changes.
View
0 ...lyptus/cloud/ws/tests/ImageCacheTest.java → ...lyptus/cloud/ws/tests/ImageCacheTest.java
File renamed without changes.
View
0 ...eucalyptus/cloud/ws/tests/ObjectTest.java → ...eucalyptus/cloud/ws/tests/ObjectTest.java
File renamed without changes.
View
105 ...les/walrus/src/test/java/edu/ucsb/eucalyptus/cloud/ws/tests/WalrusAuthenticationTest.java
@@ -0,0 +1,105 @@
+package edu.ucsb.eucalyptus.cloud.ws.tests;
+
+import java.io.File;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpVersion;
+
+import com.eucalyptus.auth.login.AuthenticationException;
+import com.eucalyptus.http.MappingHttpRequest;
+import com.eucalyptus.util.EucalyptusCloudException;
+import com.eucalyptus.util.StorageProperties;
+import com.eucalyptus.ws.handlers.WalrusAuthenticationHandler;
+import com.google.gwt.user.client.Random;
+
+import edu.ucsb.eucalyptus.cloud.ws.HttpReader;
+import edu.ucsb.eucalyptus.cloud.ws.HttpWriter;
+import edu.ucsb.eucalyptus.util.WalrusDataMessage;
+
+public class WalrusAuthenticationTest {
+
+ private static ChannelBuffer getRandomContent(int size) {
+ ChannelBuffer buffer = ChannelBuffers.buffer(size);
+ for(int i = 0; i < size; i++) {
+ buffer.writeByte((byte)Random.nextInt(Byte.MAX_VALUE));
+ }
+
+ return buffer;
+ }
+
+ @Test
+ public static void testWalrusAuthenticationHandler() {
+ String bucket = "testbucket";
+ String object = "testobject";
+ String destURI = StorageProperties.WALRUS_URL + "/" + bucket + "/" + object;
+ MappingHttpRequest httpRequest = new MappingHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, destURI);
+
+ httpRequest.setContent(getRandomContent(1024));
+
+ //Try the handler
+ try {
+ WalrusAuthenticationHandler.EucaAuthentication.authenticate(httpRequest, WalrusAuthenticationHandler.processAuthorizationHeader(httpRequest.getAndRemoveHeader("Authorization")));
+ } catch (AuthenticationException e) {
+ e.printStackTrace();
+ System.out.println("Failed!");
+ }
+ }
+
+ @Test
+ public static void testWriter() {
+ String bucket = "testbucket";
+ String key = "key";
+ String eucaOperation = null;
+ String eucaHeader = null;
+ HttpWriter writer = new HttpWriter("PUT", bucket, key, eucaOperation, eucaHeader);
+ try {
+ writer.run();
+ } catch (EucalyptusCloudException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public static void testReader() {
+ LinkedBlockingQueue<WalrusDataMessage> queue = new LinkedBlockingQueue<WalrusDataMessage>();
+ File outputFile = null;
+ String eucaOperation = null;
+ String eucaHeader = null;
+ HttpReader reader = new HttpReader("path", queue, outputFile, eucaOperation, eucaHeader);
+
+ String snapshotId = "snap-12345";
+ String snapshotLocation = "snapshots" + "/" + snapshotId;
+ String absoluteSnapshotPath = "/opt/eucalyptus/testreaderfile";
+ String tmpStorage = "/opt/eucalyptus/";
+ File file = new File(absoluteSnapshotPath);
+
+ HttpReader snapshotGetter = new HttpReader(snapshotLocation, null, file, "GetWalrusSnapshot", "", true, tmpStorage);
+ snapshotGetter.run();
+
+ reader.run();
+
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ System.out.println("Running authenticate test");
+ testWalrusAuthenticationHandler();
+
+ System.out.println("Running write test");
+ testWriter();
+
+ System.out.println("Running read test");
+ testReader();
+
+ }
+
+}
View
0 ...tus/cloud/ws/tests/WalrusBucketTests.java → ...tus/cloud/ws/tests/WalrusBucketTests.java
File renamed without changes.
View
748 ...modules/wsstack/src/main/java/com/eucalyptus/ws/handlers/WalrusAuthenticationHandler.java
@@ -63,13 +63,18 @@
package com.eucalyptus.ws.handlers;
import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.Arrays;
+import java.util.TreeSet;
+
+import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.log4j.Logger;
import org.jboss.netty.channel.ChannelFutureListener;
@@ -95,11 +100,20 @@
import com.eucalyptus.util.StorageProperties;
import com.eucalyptus.util.WalrusProperties;
import com.eucalyptus.util.WalrusUtil;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
@ChannelPipelineCoverage("one")
public class WalrusAuthenticationHandler extends MessageStackHandler {
private static Logger LOG = Logger.getLogger( WalrusAuthenticationHandler.class );
- public enum SecurityParameter {
+ private static final String AWS_AUTH_TYPE = "AWS";
+ private static final String EUCA_AUTH_TYPE = "EUCA2-RSA-SHA256";
+ private static final String EUCA_OLD_AUTH_TYPE = "Euca";
+ protected static final String ISO_8601_FORMAT = "yyyyMMdd'T'hhmmss'Z'"; //Use the ISO8601 format
+
+
+ public static enum SecurityParameter {
AWSAccessKeyId,
Timestamp,
Expires,
@@ -110,257 +124,615 @@
Content_Type,
SecurityToken,
}
+
+ /* The possible fields in an authorization header */
+ private static enum AuthorizationField {
+ Type,
+ AccessKeyId,
+ CertFingerPrint,
+ SignedHeaders,
+ Signature
+ }
+
+ /**
+ * Ensure that only one header for each name exists (i.e. not 2 Authorization headers)
+ * Accomplish this by comma-delimited concatenating any duplicates found as per HTTP 1.1 RFC 2616 section 4.2
+ *
+ * TODO: Also, should convert all headers to lower-case for consistent processing later. This is okay since headers are case-insensitive.
+ *
+ * in HTTP
+ * @param httpRequest
+ */
+ private static void canonicalizeHeaders(MappingHttpRequest httpRequest) {
+ //Iterate through headers and find duplicates, concatenate their values together and remove from
+ // request as we find them.
+ TreeMap<String, String> headerMap = new TreeMap<String, String>();
+ String value = null;
+
+ //Construct a map of the normalized headers, cannot modify in-place since
+ // conconcurrent-modify exception may result
+ for(String header : httpRequest.getHeaderNames()) {
+ //TODO: zhill, put in the map in lower-case form.
+ headerMap.put(header, Joiner.on(',').join(httpRequest.getHeaders(header)));
+ }
+
+ //Remove *all* headers
+ httpRequest.clearHeaders();
+ //Add the normalized headers back into the request
+ for(String foundHeader : headerMap.keySet()) {
+ httpRequest.addHeader(foundHeader, headerMap.get(foundHeader).toString());
+ }
+ }
+
@Override
public void incomingMessage( ChannelHandlerContext ctx, MessageEvent event ) throws Exception {
if ( event.getMessage( ) instanceof MappingHttpRequest ) {
MappingHttpRequest httpRequest = ( MappingHttpRequest ) event.getMessage( );
+
+ //Consolidate duplicates, etc.
+ canonicalizeHeaders(httpRequest);
if(httpRequest.containsHeader(WalrusProperties.Headers.S3UploadPolicy.toString())) {
checkUploadPolicy(httpRequest);
}
handle(httpRequest);
}
}
+
+ /**
+ * Process the authorization header
+ * @param authorization
+ * @return
+ * @throws AuthenticationException
+ */
+ public static Map<AuthorizationField,String> processAuthorizationHeader(String authorization) throws AuthenticationException {
+ if(Strings.isNullOrEmpty(authorization)) {
+ return null;
+ }
+
+ HashMap<AuthorizationField, String> authMap = new HashMap<AuthorizationField,String>();
+ String[] components = authorization.split(" ");
+
+ if(components.length < 2) {
+ throw new AuthenticationException("Invalid authoriztion header");
+ }
+
+ if(AWS_AUTH_TYPE.equals(components[0]) && components.length == 2) {
+ //Expect: components[1] = <AccessKeyId>:<Signature>
+ authMap.put(AuthorizationField.Type, AWS_AUTH_TYPE);
+ String[] signatureElements = components[1].split(":");
+ authMap.put(AuthorizationField.AccessKeyId, signatureElements[0]);
+ authMap.put(AuthorizationField.Signature, signatureElements[1]);
+ }
+ else if(EUCA_AUTH_TYPE.equals(components[0]) && components.length == 4){
+ //Expect: components[0] = EUCA2-RSA-SHA256 components[1] = <fingerprint of signing certificate> components[2] = <list of signed headers> components[3] = <Signature>
+ authMap.put(AuthorizationField.Type, EUCA_AUTH_TYPE);
+ authMap.put(AuthorizationField.CertFingerPrint, components[1].trim());
+ authMap.put(AuthorizationField.SignedHeaders, components[2].trim());
+ authMap.put(AuthorizationField.Signature, components[3].trim());
+ }
+ else if(EUCA_OLD_AUTH_TYPE.equals(components[0]) && components.length == 1){
+ authMap.put(AuthorizationField.Type, EUCA_OLD_AUTH_TYPE);
+ }
+ else {
+ throw new AuthenticationException("Invalid authorization header");
+ }
+
+ return authMap;
+ }
+
+/*
+ * Handle S3UploadPolicy optionally sent as headers for bundle-upload calls.
+ * Simply verifies the policy and signature of the policy.
+ */
+private static void checkUploadPolicy(MappingHttpRequest httpRequest) throws AuthenticationException {
+ Map<String, String> fields = new HashMap<String, String>();
+ String policy = httpRequest.getHeader(WalrusProperties.Headers.S3UploadPolicy.toString());
+ fields.put(WalrusProperties.FormField.policy.toString(), policy);
+ String policySignature = httpRequest.getHeader(WalrusProperties.Headers.S3UploadPolicySignature.toString());
+ if(policySignature == null)
+ throw new AuthenticationException("Policy signature must be specified with policy.");
+ String awsAccessKeyId = httpRequest.getHeader(SecurityParameter.AWSAccessKeyId.toString());
+ if(awsAccessKeyId == null)
+ throw new AuthenticationException("AWSAccessKeyID must be specified.");
+ fields.put(WalrusProperties.FormField.signature.toString(), policySignature);
+ fields.put(SecurityParameter.AWSAccessKeyId.toString(), awsAccessKeyId);
+ String acl = httpRequest.getHeader(WalrusProperties.AMZ_ACL.toString());
+ if(acl != null)
+ fields.put(WalrusProperties.FormField.acl.toString(), acl);
+ String operationPath = httpRequest.getServicePath().replaceAll(WalrusProperties.walrusServicePath, "");
+ String[] target = WalrusUtil.getTarget(operationPath);
+ if(target != null) {
+ fields.put(WalrusProperties.FormField.bucket.toString(), target[0]);
+ if(target.length > 1)
+ fields.put(WalrusProperties.FormField.key.toString(), target[1]);
+ }
+ UploadPolicyChecker.checkPolicy(httpRequest, fields);
+}
+
+ /**
+ * Class contains methods for implementing EucaRSA-V2 Authentication.
+ * @author zhill
+ *
+ */
+ public static class EucaAuthentication {
+ private static final Set<String> SAFE_HEADER_SET = Sets.newHashSet("transfer-encoding");
+
+ /**
+ * Implements the Euca2 auth method
+ *
+ * Add an Authorization HTTP header to the request that contains the following strings, separated by spaces:
+ * EUCA2-RSA-SHA256
+ * The lower-case hexadecimal encoding of the component's X.509 certificate's fingerprint
+ * The SignedHeaders list calculated in Task 1
+ * The Base64 encoding of the Signature calculated in Task 2
+ *
+ * Signature = RSA(privkey, SHA256(CanonicalRequest))
+ *
+ * CanonicalRequest =
+ * HTTPRequestMethod + '\n' +
+ * CanonicalURI + '\n' +
+ * CanonicalQueryString + '\n' +
+ * CanonicalHeaders + '\n' +
+ * SignedHeaders
+
+ * @param httpRequest
+ * @param authMap
+ * @throws AuthenticationException
+ */
+ public static void authenticate(MappingHttpRequest httpRequest, Map<AuthorizationField, String> authMap) throws AuthenticationException {
+ if(authMap == null || !EUCA_AUTH_TYPE.equals(authMap.get(AuthorizationField.Type))) {
+ throw new AuthenticationException("Mismatch between expected and found authentication types");
+ }
+
+ //Remove unsigned headers so they are not consumed accidentally later
+ cleanHeaders(httpRequest, authMap.get(AuthorizationField.SignedHeaders));
+
+ //Must contain a date of some sort signed
+ checkDate(httpRequest);
+
+ //Must be certificate signed
+ String certString = null;
+ if(authMap.containsKey(AuthorizationField.CertFingerPrint)) {
+ certString = authMap.get(AuthorizationField.CertFingerPrint);
+ }
+ else {
+ throw new AuthenticationException("Invalid Authorization Header");
+ }
+
+ String verb = httpRequest.getMethod().getName();
+ String canonicalURI = getCanonicalURI(httpRequest);
+ String canonicalQueryString = getCanonicalQueryString(httpRequest);
+ String canonicalHeaders = getCanonicalHeaders(httpRequest, authMap.get(AuthorizationField.SignedHeaders));
+ String signedHeaders = getSignedHeaders(httpRequest, authMap);
+
+ String data = verb + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders;
+ String AWSAccessKeyID = httpRequest.getAndRemoveHeader(SecurityParameter.AWSAccessKeyId.toString());
+ String signature = authMap.get(AuthorizationField.Signature);
+
+ try {
+ SecurityContext.getLoginContext(new WalrusWrappedComponentCredentials(httpRequest.getCorrelationId(), data, AWSAccessKeyID, signature, certString)).login();
+ } catch(Exception ex) {
+ LOG.error(ex);
+ throw new AuthenticationException(ex);
+ }
+
+ }
+
+ private static void checkDate(MappingHttpRequest httpRequest) throws AuthenticationException {
+ String date;
+ String verifyDate;
+ if(httpRequest.containsHeader("x-amz-date")) {
+ date = "";
+ verifyDate = httpRequest.getHeader("x-amz-date");
+ } else {
+ date = httpRequest.getHeader(SecurityParameter.Date.toString());
+ verifyDate = date;
+ if(date == null || date.length() <= 0)
+ throw new AuthenticationException("User authentication failed. Date must be specified.");
+ }
- public void handle(MappingHttpRequest httpRequest) throws AuthenticationException
- {
- Map<String,String> parameters = httpRequest.getParameters( );
- String verb = httpRequest.getMethod().getName();
- String addr = httpRequest.getUri();
+ try {
+ ArrayList<String> formats = new ArrayList<String>();
+ formats.add(ISO_8601_FORMAT);
+ Date dateToVerify = DateUtil.parseDate(verifyDate, formats);
+ Date currentDate = new Date();
+ if(Math.abs(currentDate.getTime() - dateToVerify.getTime()) > WalrusProperties.EXPIRATION_LIMIT)
+ throw new AuthenticationException("Message expired. Sorry.");
+ } catch(DateParseException ex) {
+ throw new AuthenticationException("Unable to parse date.");
+ }
+ }
+
+ /**
+ * Gets the signed header string for Euca2 auth.
+ * @param httpRequest
+ * @param authMap
+ * @return
+ * @throws AuthenticationException
+ */
+ private static String getSignedHeaders(MappingHttpRequest httpRequest, Map<AuthorizationField, String> authMap) throws AuthenticationException {
+ String signedHeaders = authMap.get(AuthorizationField.SignedHeaders);
+ if(signedHeaders != null) return signedHeaders.trim();
+ return "";
+ }
+ /**
+ * Returns the canonical URI for euca2 auth, just the path from the end of the host header value to the first ?
+ * @param httpRequest
+ * @return
+ * @throws AuthenticationException
+ */
+ private static String getCanonicalURI(MappingHttpRequest httpRequest) throws AuthenticationException {
+ String addr = httpRequest.getUri();
+ String targetHost = httpRequest.getHeader(HttpHeaders.Names.HOST);
+ if(targetHost != null && targetHost.contains(".walrus")) {
+ String bucket = targetHost.substring(0, targetHost.indexOf(".walrus"));
+ addr = "/" + bucket + addr;
+ }
+ String[] addrStrings = addr.split("\\?");
+ String addrString = addrStrings[0];
+ return addrString;
+ }
+
+ /**
+ * Get the canonical headers list, a string composed of sorted headers and values, taken from the list of signed headers given by the request
+ * @param httpRequest
+ * @return
+ * @throws AuthenticationException
+ */
+ private static String getCanonicalHeaders(MappingHttpRequest httpRequest, String signedHeaders) throws AuthenticationException {
+ String[] signedHeaderArray = signedHeaders.split(";");
+ StringBuilder canonHeader = new StringBuilder();
+ boolean foundHost = false;
+ for(String headerName : signedHeaderArray) {
+ String headerNameString = headerName.toLowerCase().trim();
+ if("host".equals(headerNameString)) {
+ foundHost = true;
+ }
+ String value = httpRequest.getHeader(headerName);
+ if(value != null) {
+ value = value.trim();
+ String[] parts = value.split("\n");
+ value = "";
+ for(String part: parts) {
+ part = part.trim();
+ value += part + " ";
+ }
+ value = value.trim();
+ }
+ else {
+ value = "";
+ }
+ canonHeader.append(headerNameString).append(":").append(value).append('\n');
+ }
+
+ if(!foundHost) {
+ throw new AuthenticationException("Host header not found when canonicalizing headers");
+ }
+
+ return canonHeader.toString().trim();
+ }
- if(httpRequest.containsHeader(StorageProperties.StorageParameters.EucaSignature.toString())) {
- //possible internal request -- perform authentication using internal credentials
- String date = httpRequest.getAndRemoveHeader(SecurityParameter.Date.toString());
- String signature = httpRequest.getAndRemoveHeader(StorageProperties.StorageParameters.EucaSignature.toString());
- String certString = null;
- if( httpRequest.containsHeader( StorageProperties.StorageParameters.EucaCert.toString( ) ) ) {
- certString= httpRequest.getAndRemoveHeader(StorageProperties.StorageParameters.EucaCert.toString());
+ /**
+ * Gets Euca2 signing canonical query string.
+ * @param httpRequest
+ * @return
+ * @throws AuthenticationException
+ */
+ private static String getCanonicalQueryString(MappingHttpRequest httpRequest) throws AuthenticationException {
+ String addr = httpRequest.getUri();
+ String[] addrStrings = addr.split("\\?");
+ StringBuilder addrString = new StringBuilder();
+
+ NavigableSet<String> sortedParams = new TreeSet<String>( );
+ Map<String, String> params = httpRequest.getParameters();
+ if(params == null) {
+ return "";
+ }
+
+ sortedParams.addAll(params.keySet());
+
+ String key = null;
+ while((key = sortedParams.pollFirst()) != null) {
+ addrString.append(key).append('=').append(params.get(key)).append('&');
+ }
+
+ if(addrString.length() > 0) {
+ addrString.deleteCharAt(addrString.length() - 1); //delete trailing '&';
}
- String data = verb + "\n" + date + "\n" + addr + "\n";
- String effectiveUserID = httpRequest.getAndRemoveHeader(StorageProperties.StorageParameters.EucaEffectiveUserId.toString());
+
+ return addrString.toString();
+ }
+
+ /**
+ * Removes all headers that are not in the signed-headers list. This prevents potentially modified headers from being used by later stages.
+ * @param httpRequest
+ * @param signedHeaders - semicolon delimited list of header names
+ */
+ private static void cleanHeaders(MappingHttpRequest httpRequest, String signedHeaders) {
+ if(Strings.isNullOrEmpty(signedHeaders)) {
+ //Remove all headers.
+ signedHeaders = "";
+ }
+
+ //Remove ones not found in the list
+ Set<String> signedNames = new TreeSet<String>();
+ String[] names = signedHeaders.split(";");
+ for(String n : names) {
+ signedNames.add(n.toLowerCase());
+ }
+
+ signedNames.addAll(SAFE_HEADER_SET);
+
+ Set<String> removeSet = new TreeSet<String>();
+ for(String headerName : httpRequest.getHeaderNames()) {
+ if(!signedNames.contains(headerName.toLowerCase())) {
+ removeSet.add(headerName);
+ }
+ }
+
+ for(String headerName : removeSet) {
+ httpRequest.removeHeader(headerName);
+ }
+ }
+
+ } //End class EucaAuthentication
+
+ private static class S3Authentication {
+ /**
+ * Authenticate using S3-spec REST authentication
+ * @param httpRequest
+ * @param authMap
+ * @throws AuthenticationException
+ */
+ private static void authenticate(MappingHttpRequest httpRequest, Map<AuthorizationField, String> authMap) throws AuthenticationException {
+ if(!authMap.get(AuthorizationField.Type).equals(AWS_AUTH_TYPE)) {
+ throw new AuthenticationException("Mismatch between expected and found authentication types");
+ }
+
+ //Standard S3 authentication signed by SecretKeyID
+ String verb = httpRequest.getMethod().getName();
+ String date = getDate(httpRequest);
+ String addrString = getS3AddressString(httpRequest);
+ String content_md5 = httpRequest.getHeader("Content-MD5");
+ content_md5 = content_md5 == null ? "" : content_md5;
+ String content_type = httpRequest.getHeader(WalrusProperties.CONTENT_TYPE);
+ content_type = content_type == null ? "" : content_type;
+ String securityToken = httpRequest.getHeader(WalrusProperties.X_AMZ_SECURITY_TOKEN);
+
+ String data = verb + "\n" + content_md5 + "\n" + content_type + "\n" + date + "\n" + getCanonicalizedAmzHeaders(httpRequest) + addrString;
+ String accessKeyId = authMap.get(AuthorizationField.AccessKeyId);
+ String signature = authMap.get(AuthorizationField.Signature);
+
try {
- SecurityContext.getLoginContext(new WalrusWrappedComponentCredentials(httpRequest.getCorrelationId(), data, effectiveUserID, signature, certString)).login();
+ SecurityContext.getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(), data, accessKeyId, signature, securityToken)).login();
} catch(Exception ex) {
LOG.error(ex);
throw new AuthenticationException(ex);
}
- } else {
- //external user request
+ }
+
+ /**
+ * Authenticate using S3-spec query string authentication
+ * @param httpRequest
+ * @throws AuthenticationException
+ */
+ private static void authenticateQueryString(MappingHttpRequest httpRequest) throws AuthenticationException {
+ //Standard S3 query string authentication
+ Map<String,String> parameters = httpRequest.getParameters( );
+ String verb = httpRequest.getMethod().getName();
String content_md5 = httpRequest.getHeader("Content-MD5");
content_md5 = content_md5 == null ? "" : content_md5;
String content_type = httpRequest.getHeader(WalrusProperties.CONTENT_TYPE);
content_type = content_type == null ? "" : content_type;
+ String addrString = getS3AddressString(httpRequest);
+ String accesskeyid = parameters.remove(SecurityParameter.AWSAccessKeyId.toString());
+
+ try {
+ //Parameter url decode happens during MappingHttpRequest construction.
+ String signature = parameters.remove(SecurityParameter.Signature.toString());
+ if(signature == null) {
+ throw new AuthenticationException("User authentication failed. Null signature.");
+ }
+ String expires = parameters.remove(SecurityParameter.Expires.toString());
+ if(expires == null) {
+ throw new AuthenticationException("Authentication failed. Expires must be specified.");
+ }
+ String securityToken = parameters.get(SecurityParameter.SecurityToken.toString());
+
+ if(checkExpires(expires)) {
+ String stringToSign = verb + "\n" + content_md5 + "\n" + content_type + "\n" + Long.parseLong(expires) + "\n" + getCanonicalizedAmzHeaders(httpRequest) + addrString;
+ try {
+ SecurityContext.getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(), stringToSign, accesskeyid, signature, securityToken)).login();
+ } catch(Exception ex) {
+ LOG.error(ex);
+ throw new AuthenticationException(ex);
+ }
+ } else {
+ throw new AuthenticationException("Cannot process request. Expired.");
+ }
+ } catch (Exception ex) {
+ throw new AuthenticationException("Could not verify request " + ex.getMessage());
+ }
+ }
+
+ /**
+ * See if the expires string indicates the message is expired.
+ * @param expires
+ * @return
+ */
+ private static boolean checkExpires(String expires) {
+ Long expireTime = Long.parseLong(expires);
+ Long currentTime = new Date().getTime() / 1000;
+ if(currentTime > expireTime)
+ return false;
+ return true;
+ }
+
+ /**
+ * Gets the date for S3-spec authentication
+ * @param httpRequest
+ * @return
+ * @throws AuthenticationException
+ */
+ private static String getDate(MappingHttpRequest httpRequest) throws AuthenticationException {
+ String date;
+ String verifyDate;
+ if(httpRequest.containsHeader("x-amz-date")) {
+ date = "";
+ verifyDate = httpRequest.getHeader("x-amz-date");
+ } else {
+ date = httpRequest.getAndRemoveHeader(SecurityParameter.Date.toString());
+ verifyDate = date;
+ if(date == null || date.length() <= 0)
+ throw new AuthenticationException("User authentication failed. Date must be specified.");
+ }
+ try {
+ Date dateToVerify = DateUtil.parseDate(verifyDate);
+ Date currentDate = new Date();
+ if(Math.abs(currentDate.getTime() - dateToVerify.getTime()) > WalrusProperties.EXPIRATION_LIMIT)
+ throw new AuthenticationException("Message expired. Sorry.");
+ } catch(Exception ex) {
+ throw new AuthenticationException("Unable to parse date.");
+ }
+
+ return verifyDate;
+ }
+
+ private static String getCanonicalizedAmzHeaders(MappingHttpRequest httpRequest) {
+ String result = "";
+ Set<String> headerNames = httpRequest.getHeaderNames();
+
+ TreeMap<String,String> amzHeaders = new TreeMap<String, String>();
+ for(String headerName : headerNames) {
+ String headerNameString = headerName.toLowerCase().trim();
+ if(headerNameString.startsWith("x-amz-")) {
+ String value = httpRequest.getHeader(headerName).trim();
+ String[] parts = value.split("\n");
+ value = "";
+ for(String part: parts) {
+ part = part.trim();
+ value += part + " ";
+ }
+ value = value.trim();
+ if(amzHeaders.containsKey(headerNameString)) {
+ String oldValue = (String) amzHeaders.remove(headerNameString);
+ oldValue += "," + value;
+ amzHeaders.put(headerNameString, oldValue);
+ } else {
+ amzHeaders.put(headerNameString, value);
+ }
+ }
+ }
+
+ Iterator<String> iterator = amzHeaders.keySet().iterator();
+ while(iterator.hasNext()) {
+ String key = iterator.next();
+ String value = (String) amzHeaders.get(key);
+ result += key + ":" + value + "\n";
+ }
+ return result;
+ }
+
+ //Old method for getting signature info from Auth header
+ private static String[] getSigInfo (String auth_part) {
+ int index = auth_part.lastIndexOf(" ");
+ String sigString = auth_part.substring(index + 1);
+ return sigString.split(":");
+ }
+
+ /**
+ * AWS S3-spec address string, which includes the query parameters
+ * @param httpRequest
+ * @return
+ * @throws AuthenticationException
+ */
+ private static String getS3AddressString(MappingHttpRequest httpRequest) throws AuthenticationException {
+ String addr = httpRequest.getUri();
String targetHost = httpRequest.getHeader(HttpHeaders.Names.HOST);
if(targetHost.contains(".walrus")) {
String bucket = targetHost.substring(0, targetHost.indexOf(".walrus"));
addr = "/" + bucket + addr;
}
String[] addrStrings = addr.split("\\?");
- String addrString = addrStrings[0];
-
+ StringBuilder addrString = new StringBuilder(addrStrings[0]);
+
if(addrStrings.length > 1) {
//Split into individual parameter=value strings
String[] params = addrStrings[1].split("&");
-
+
//Sort the query parameters before adding them to the canonical string
Arrays.sort(params);
String[] pair = null;
boolean first = true;
try {
for(String qparam : params) {
pair = qparam.split("="); //pair[0] = param name, pair[1] = param value if it is present
-
+
for(WalrusProperties.SubResource subResource : WalrusProperties.SubResource.values()) {
if(pair[0].equals(subResource.toString())) {
if(first) {
- addrString += "?";
+ addrString.append("?");
first = false;
}
else {
- addrString += "&";
+ addrString.append("&");
}
- addrString += subResource.toString() + (pair.length > 1 ? "=" + WalrusUtil.URLdecode(pair[1]) : "");
+ addrString.append(subResource.toString()).append((pair.length > 1 ? "=" + WalrusUtil.URLdecode(pair[1]) : ""));
}
}
}
} catch(UnsupportedEncodingException e) {
throw new AuthenticationException("Could not verify request. Failed url decoding query parameters: " + e.getMessage());
}
- }
+ }
+ return addrString.toString();
+ }
+
+ } //End class S3Authentication
+
+ /**
+ * Authentication Handler for Walrus REST requests (POST method and SOAP are processed using different handlers)
+ * @param httpRequest
+ * @throws AuthenticationException
+ */
+ public void handle(MappingHttpRequest httpRequest) throws AuthenticationException {
+ //Clean up the headers such that no duplicates may exist etc.
+ //sanitizeHeaders(httpRequest);
+ Map<String,String> parameters = httpRequest.getParameters();
- if(httpRequest.containsHeader(SecurityParameter.Authorization.toString())) {
- String date;
- String verifyDate;
- if(httpRequest.containsHeader("x-amz-date")) {
- date = "";
- verifyDate = httpRequest.getHeader("x-amz-date");
- } else {
- date = httpRequest.getAndRemoveHeader(SecurityParameter.Date.toString());
- verifyDate = date;
- if(date == null || date.length() <= 0)
- throw new AuthenticationException("User authentication failed. Date must be specified.");
- }
+ if(httpRequest.containsHeader(SecurityParameter.Authorization.toString())) {
+ String authHeader = httpRequest.getAndRemoveHeader(SecurityParameter.Authorization.toString());
+ Map<AuthorizationField, String> authMap = processAuthorizationHeader(authHeader);
- try {
- Date dateToVerify = DateUtil.parseDate(verifyDate);
- Date currentDate = new Date();
- if(Math.abs(currentDate.getTime() - dateToVerify.getTime()) > WalrusProperties.EXPIRATION_LIMIT)
- throw new AuthenticationException("Message expired. Sorry.");
- } catch(Exception ex) {
- throw new AuthenticationException("Unable to parse date.");
- }
- String data = verb + "\n" + content_md5 + "\n" + content_type + "\n" + date + "\n" + getCanonicalizedAmzHeaders(httpRequest) + addrString;
- String authPart = httpRequest.getAndRemoveHeader(SecurityParameter.Authorization.toString());
- String sigString[] = getSigInfo(authPart);
- if(sigString.length < 2) {
- throw new AuthenticationException("Invalid authentication header");
- }
- String accessKeyId = sigString[0];
- String signature = sigString[1];
- String securityToken = httpRequest.getHeader(WalrusProperties.X_AMZ_SECURITY_TOKEN);
- try {
- SecurityContext.getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(), data, accessKeyId, signature, securityToken)).login();
- } catch(Exception ex) {
- LOG.error(ex);
- throw new AuthenticationException(ex);
- }
- } else if(parameters.containsKey(SecurityParameter.AWSAccessKeyId.toString())) {
- //query string authentication
- String accesskeyid = parameters.remove(SecurityParameter.AWSAccessKeyId.toString());
- try {
- //No need to decode the parameter, that is done during HTTP message creation
- String signature = parameters.remove(SecurityParameter.Signature.toString());
- if(signature == null) {
- throw new AuthenticationException("User authentication failed. Null signature.");
- }
- String expires = parameters.remove(SecurityParameter.Expires.toString());
- if(expires == null) {
- throw new AuthenticationException("Authentication failed. Expires must be specified.");
- }
- if(checkExpires(expires)) {
- String stringToSign = verb + "\n" + content_md5 + "\n" + content_type + "\n" + Long.parseLong(expires) + "\n" + getCanonicalizedAmzHeaders(httpRequest) + addrString;
- String securityToken = parameters.get(SecurityParameter.SecurityToken.toString());
- try {
- SecurityContext.getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(), stringToSign, accesskeyid, signature, securityToken)).login();
- } catch(Exception ex) {
- LOG.error(ex);
- throw new AuthenticationException(ex);
- }
- } else {
- throw new AuthenticationException("Cannot process request. Expired.");
- }
- } catch (Exception ex) {
- throw new AuthenticationException("Could not verify request " + ex.getMessage());
- }
- } else{
- //anonymous request
+ if(EUCA_AUTH_TYPE.equals(authMap.get(AuthorizationField.Type))) {
+ //Internally signed request. Using a certificate for signing
+ EucaAuthentication.authenticate(httpRequest, authMap);
+ } else if(AWS_AUTH_TYPE.equals(authMap.get(AuthorizationField.Type))) {
+ //Normally signed request using AccessKeyId/SecretKeyId pair
+ S3Authentication.authenticate(httpRequest, authMap);
+ } else {
+ throw new AuthenticationException("Malformed Authentication Header");
+ }
+ }
+ else {
+ if(parameters.containsKey(SecurityParameter.AWSAccessKeyId.toString())) {
+ //Query String Auth
+ S3Authentication.authenticateQueryString(httpRequest);
+ } else {
+ //Anonymous request, no query string, no Authorization header
try {
Context ctx = Contexts.lookup(httpRequest.getCorrelationId());
ctx.setUser(Principals.nobodyUser());
} catch (NoSuchContextException e) {
LOG.error(e, e);
throw new AuthenticationException(e);
- }
+ }
}
}
}
-
- private boolean checkExpires(String expires) {
- Long expireTime = Long.parseLong(expires);
- Long currentTime = new Date().getTime() / 1000;
- if(currentTime > expireTime)
- return false;
- return true;
- }
-
- private String[] getSigInfo (String auth_part) {
- int index = auth_part.lastIndexOf(" ");
- String sigString = auth_part.substring(index + 1);
- return sigString.split(":");
- }
-
- private String getCanonicalizedAmzHeaders(MappingHttpRequest httpRequest) {
- String result = "";
- Set<String> headerNames = httpRequest.getHeaderNames();
-
- TreeMap amzHeaders = new TreeMap<String, String>();
- for(String headerName : headerNames) {
- String headerNameString = headerName.toLowerCase().trim();
- if(headerNameString.startsWith("x-amz-")) {
- String value = httpRequest.getHeader(headerName).trim();
- String[] parts = value.split("\n");
- value = "";
- for(String part: parts) {
- part = part.trim();
- value += part + " ";
- }
- value = value.trim();
- if(amzHeaders.containsKey(headerNameString)) {
- String oldValue = (String) amzHeaders.remove(headerNameString);
- oldValue += "," + value;
- amzHeaders.put(headerNameString, oldValue);
- } else {
- amzHeaders.put(headerNameString, value);
- }
- }
- }
-
- Iterator<String> iterator = amzHeaders.keySet().iterator();
- while(iterator.hasNext()) {
- String key = iterator.next();
- String value = (String) amzHeaders.get(key);
- result += key + ":" + value + "\n";
- }
- return result;
- }
-
- private void checkUploadPolicy(MappingHttpRequest httpRequest) throws AuthenticationException {
- Map<String, String> fields = new HashMap<String, String>();
- String policy = httpRequest.getAndRemoveHeader(WalrusProperties.Headers.S3UploadPolicy.toString());
- fields.put(WalrusProperties.FormField.policy.toString(), policy);
- String policySignature = httpRequest.getAndRemoveHeader(WalrusProperties.Headers.S3UploadPolicySignature.toString());
- if(policySignature == null)
- throw new AuthenticationException("Policy signature must be specified with policy.");
- String awsAccessKeyId = httpRequest.getAndRemoveHeader(SecurityParameter.AWSAccessKeyId.toString());
- if(awsAccessKeyId == null)
- throw new AuthenticationException("AWSAccessKeyID must be specified.");
- fields.put(WalrusProperties.FormField.signature.toString(), policySignature);
- fields.put(SecurityParameter.AWSAccessKeyId.toString(), awsAccessKeyId);
- String acl = httpRequest.getAndRemoveHeader(WalrusProperties.AMZ_ACL.toString());
- if(acl != null)
- fields.put(WalrusProperties.FormField.acl.toString(), acl);
- String operationPath = httpRequest.getServicePath().replaceAll(WalrusProperties.walrusServicePath, "");
- String[] target = WalrusUtil.getTarget(operationPath);
- if(target != null) {
- fields.put(WalrusProperties.FormField.bucket.toString(), target[0]);
- if(target.length > 1)
- fields.put(WalrusProperties.FormField.key.toString(), target[1]);
- }
- UploadPolicyChecker.checkPolicy(httpRequest, fields);
-
- String data = httpRequest.getAndRemoveHeader(WalrusProperties.FormField.FormUploadPolicyData.toString());
- String auth_part = httpRequest.getAndRemoveHeader(SecurityParameter.Authorization.toString());
- String securityToken = httpRequest.getHeader(WalrusProperties.X_AMZ_SECURITY_TOKEN);
- if(auth_part != null) {
- String sigString[] = getSigInfo(auth_part);
- if(sigString.length < 2) {
- throw new AuthenticationException("Invalid authentication header");
- }
- String accessKeyId = sigString[0];
- String signature = sigString[1];
- try {
- SecurityContext.getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(), data, accessKeyId, signature, securityToken)).login();
- } catch(Exception ex) {
- LOG.error(ex);
- throw new AuthenticationException(ex);
- }
- } else {
- throw new AuthenticationException("User authentication failed. Invalid policy signature.");
- }
-
- }
-
+
public void exceptionCaught( final ChannelHandlerContext ctx, final ExceptionEvent exceptionEvent ) throws Exception {
LOG.info("[exception " + exceptionEvent + "]");
final HttpResponse response = new DefaultHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR );
View
2 clc/modules/wsstack/src/main/java/com/eucalyptus/ws/handlers/WalrusRESTBinding.java
@@ -194,7 +194,7 @@ public void incomingMessage( ChannelHandlerContext ctx, MessageEvent event ) thr
if(msg instanceof WalrusDataRequestType) {
String expect = httpRequest.getHeader(HttpHeaders.Names.EXPECT);
if(expect != null) {
- if(expect.equals("100-continue")) {
+ if(expect.toLowerCase().equals("100-continue")) {
HttpResponse response = new DefaultHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE );
DownstreamMessageEvent newEvent = new DownstreamMessageEvent( ctx.getChannel( ), event.getFuture(), response, null );
ctx.sendDownstream( newEvent );
View
74 cluster/cc-client-policy.xml
@@ -0,0 +1,74 @@
+<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
+ <wsp:ExactlyOne>
+ <wsp:All>
+ <sp:AsymmetricBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
+ <wsp:Policy>
+ <sp:InitiatorToken>
+ <wsp:Policy>
+ <sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always">
+ <wsp:Policy>
+ <sp:RequireEmbeddedTokenReference/>
+ <sp:WssX509V3Token10/>
+ </wsp:Policy>
+ </sp:X509Token>
+ </wsp:Policy>
+ </sp:InitiatorToken>
+ <sp:RecipientToken>
+ <wsp:Policy>
+ <sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always">
+ <wsp:Policy>
+ <sp:RequireEmbeddedTokenReference/>
+ <sp:WssX509V3Token10/>
+ </wsp:Policy>
+ </sp:X509Token>
+ </wsp:Policy>
+ </sp:RecipientToken>
+
+ <sp:AlgorithmSuite>
+ <wsp:Policy>
+ <sp:Basic256Rsa15/>
+ </wsp:Policy>
+ </sp:AlgorithmSuite>
+
+ <sp:Layout>
+ <wsp:Policy>
+ <sp:Strict/>
+ </wsp:Policy>
+ </sp:Layout>
+
+ <sp:IncludeTimestamp/>
+ <sp:OnlySignEntireHeadersAndBody/>
+ </wsp:Policy>
+ </sp:AsymmetricBinding>
+
+ <sp:Wss10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
+ <wsp:Policy>
+ <sp:MustSupportRefKeyIdentifier/>
+ <sp:MustSupportRefEmbeddedToken/>
+ </wsp:Policy>
+ </sp:Wss10>
+
+ <sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
+ <sp:Body/>
+ <sp:Header Namespace="http://www.w3.org/2005/08/addressing"/>
+ </sp:SignedParts>
+
+ <rampc:RampartConfig xmlns:rampc="http://ws.apache.org/rampart/c/policy">
+ <rampc:ReceiverCertificate>/opt/eucalyptus/var/lib/eucalyptus/keys/cluster-cert.pem</rampc:ReceiverCertificate>
+ <rampc:Certificate>/opt/eucalyptus/var/lib/eucalyptus/keys/cloud-cert.pem</rampc:Certificate>
+ <rampc:PrivateKey>/opt/eucalyptus/var/lib/eucalyptus/keys/cloud-pk.pem</rampc:PrivateKey>
+ <rampc:ClockSkewBuffer>20</rampc:ClockSkewBuffer>
+ <!-- <rampc:TimeToLive>14400</rampc:TimeToLive> -->
+ <!--
+ <rampc:User>eucalyptus</rampc:User>
+ <rampc:PasswordType>Digest</rampc:PasswordType>
+ <rampc:PasswordCallbackClass>/opt/eucalyptus/var/lib/eucalyptus/keys/libpwcb.so</rampc:PasswordCallbackClass>
+ <rampc:ReceiverCertificate>/opt/eucalyptus/var/lib/eucalyptus/keys/cluster-cert.pem</rampc:ReceiverCertificate>
+ <rampc:Certificate>/opt/eucalyptus/var/lib/eucalyptus/keys/cloud-cert.pem</rampc:Certificate>
+ <rampc:PrivateKey>/opt/eucalyptus/var/lib/eucalyptus/keys/cloud-pk.pem</rampc:PrivateKey>
+ -->
+ </rampc:RampartConfig>
+ </wsp:All>
+ </wsp:ExactlyOne>
+</wsp:Policy>
+
View
74 node/nc-client-policy.xml
@@ -0,0 +1,74 @@
+<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
+ <wsp:ExactlyOne>
+ <wsp:All>
+ <sp:AsymmetricBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
+ <wsp:Policy>
+ <sp:InitiatorToken>
+ <wsp:Policy>
+ <sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always">
+ <wsp:Policy>
+ <sp:RequireEmbeddedTokenReference/>
+ <sp:WssX509V3Token10/>
+ </wsp:Policy>
+ </sp:X509Token>
+ </wsp:Policy>
+ </sp:InitiatorToken>
+ <sp:RecipientToken>
+ <wsp:Policy>
+ <sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Always">
+ <wsp:Policy>
+ <sp:RequireEmbeddedTokenReference/>
+ <sp:WssX509V3Token10/>
+ </wsp:Policy>
+ </sp:X509Token>
+ </wsp:Policy>
+ </sp:RecipientToken>
+
+ <sp:AlgorithmSuite>
+ <wsp:Policy>
+ <sp:Basic256Rsa15/>
+ </wsp:Policy>
+ </sp:AlgorithmSuite>
+
+ <sp:Layout>
+ <wsp:Policy>
+ <sp:Strict/>
+ </wsp:Policy>
+ </sp:Layout>
+
+ <sp:IncludeTimestamp/>
+ <sp:OnlySignEntireHeadersAndBody/>
+ </wsp:Policy>
+ </sp:AsymmetricBinding>
+
+ <sp:Wss10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
+ <wsp:Policy>
+ <sp:MustSupportRefKeyIdentifier/>
+ <sp:MustSupportRefEmbeddedToken/>
+ </wsp:Policy>
+ </sp:Wss10>
+
+ <sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
+ <sp:Body/>
+ <sp:Header Namespace="http://www.w3.org/2005/08/addressing"/>
+ </sp:SignedParts>
+
+ <rampc:RampartConfig xmlns:rampc="http://ws.apache.org/rampart/c/policy">
+ <rampc:ReceiverCertificate>/opt/eucalyptus/var/lib/eucalyptus/keys/node-cert.pem</rampc:ReceiverCertificate>
+ <rampc:Certificate>/opt/eucalyptus/var/lib/eucalyptus/keys/cluster-cert.pem</rampc:Certificate>
+ <rampc:PrivateKey>/opt/eucalyptus/var/lib/eucalyptus/keys/cluster-pk.pem</rampc:PrivateKey>
+ <rampc:ClockSkewBuffer>20</rampc:ClockSkewBuffer>
+ <!-- <rampc:TimeToLive>14400</rampc:TimeToLive> -->
+ <!--
+ <rampc:User>eucalyptus</rampc:User>
+ <rampc:PasswordType>Digest</rampc:PasswordType>
+ <rampc:PasswordCallbackClass>/opt/eucalyptus/var/lib/eucalyptus/keys/libpwcb.so</rampc:PasswordCallbackClass>
+ <rampc:ReceiverCertificate>/opt/eucalyptus/var/lib/eucalyptus/keys/node-cert.pem</rampc:ReceiverCertificate>
+ <rampc:Certificate>/opt/eucalyptus/var/lib/eucalyptus/keys/cluster-cert.pem</rampc:Certificate>
+ <rampc:PrivateKey>/opt/eucalyptus/var/lib/eucalyptus/keys/cluster-pk.pem</rampc:PrivateKey>
+ -->
+ </rampc:RampartConfig>
+ </wsp:All>
+ </wsp:ExactlyOne>
+</wsp:Policy>
+
View
253 storage/walrus.c
@@ -119,13 +119,12 @@ static pthread_mutex_t wreq_mutex = PTHREAD_MUTEX_INITIALIZER;
/* downloads a decrypted image from Walrus based on the manifest URL,
* saves it to outfile */
-static int walrus_request_timeout(const char *walrus_op, const char *verb, const char *requested_url, const char *outfile, const int do_compress,
- int connect_timeout, int total_timeout)
+static int fallback_walrus_request_timeout (const char * walrus_op, const char * verb, const char * requested_url, const char * outfile, const int do_compress, int connect_timeout, int total_timeout)
{
int code = ERROR;
- char url[BUFSIZE];
-
- pthread_mutex_lock(&wreq_mutex); /* lock for curl construction */
+ char url [BUFSIZE];
+
+ pthread_mutex_lock(&wreq_mutex); /* lock for curl construction */
safe_strncpy(url, requested_url, BUFSIZE);
#if defined(CAN_GZIP)
@@ -360,6 +359,246 @@ static int walrus_request_timeout(const char *walrus_op, const char *verb, const
return code;
}
+
+/*
+ * Uses EucaV2 signing for the request. We keep both functions to enable backwards compatibility.
+ *
+ * downloads a decrypted image from Walrus based on the manifest URL,
+ * saves it to outfile */
+static int walrus_request_timeout (const char * walrus_op, const char * verb, const char * requested_url, const char * outfile, const int do_compress, int connect_timeout, int total_timeout)
+{
+ int code = ERROR;
+ char url [BUFSIZE];
+
+ pthread_mutex_lock(&wreq_mutex); /* lock for curl construction */
+
+ safe_strncpy (url, requested_url, BUFSIZE);
+#if defined(CAN_GZIP)
+ if (do_compress)
+ snprintf (url, BUFSIZE, "%s%s", requested_url, "?IsCompressed=true");
+#endif
+
+ /* isolate the PATH in the URL as it will be needed for signing */
+ char * url_path;
+ if (strncasecmp (url, "http://", 7)!=0 &&
+ strncasecmp (url, "https://", 8)!=0) {
+ logprintfl (EUCAERROR, "{%u} walrus_request: URL must start with http(s)://...\n",(unsigned int)pthread_self());
+ pthread_mutex_unlock(&wreq_mutex);
+ return code;
+ }
+
+ if ((url_path=strchr(url+8, '/'))==NULL) { /* find first '/' after hostname */
+ logprintfl (EUCAERROR, "{%u} walrus_request: URL has no path\n",(unsigned int)pthread_self());
+ pthread_mutex_unlock(&wreq_mutex);
+ return code;
+ }
+
+ if (euca_init_cert()) {
+ logprintfl (EUCAERROR, "{%u} walrus_request: failed to initialize certificate\n",(unsigned int)pthread_self());
+ pthread_mutex_unlock(&wreq_mutex);
+ return code;
+ }
+
+ int fd = open (outfile, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); // we do not truncate the file
+ if (fd==-1 || lseek (fd, 0, SEEK_SET)==-1) {
+ logprintfl (EUCAERROR, "{%u} walrus_request: failed to open %s for writing\n", (unsigned int)pthread_self(), outfile);
+ pthread_mutex_unlock(&wreq_mutex);
+ if(fd >= 0) close(fd);
+ return code;
+ }
+
+ logprintfl(EUCADEBUG, "{%u} walrus_request: calling URL=%s\n", (unsigned int)pthread_self(), url);
+
+ CURL * curl;
+ CURLcode result;
+ curl = curl_easy_init ();
+ if (curl==NULL) {
+ logprintfl (EUCAERROR, "{%u} walrus_request: could not initialize libcurl\n",(unsigned int)pthread_self());
+ close(fd);
+ pthread_mutex_unlock(&wreq_mutex);
+ return code;
+ }
+
+ char error_msg [CURL_ERROR_SIZE];
+ curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, error_msg);
+ curl_easy_setopt (curl, CURLOPT_URL, url);
+ curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, write_header);
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); // TODO: make this optional?
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
+ // curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1); // TODO: remove the comment once we want to follow redirects (e.g., on HTTP 407)
+
+ if (strncmp (verb, "GET", 4)==0) {
+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L);
+ } else if (strncmp (verb, "HEAD", 5)==0) {
+ /* TODO: HEAD isn't very useful atm since we don't look at headers */
+ curl_easy_setopt (curl, CURLOPT_NOBODY, 1L);
+ } else {
+ close(fd);
+ logprintfl (EUCAERROR, "{%u} walrus_request: invalid HTTP verb %s\n", (unsigned int)pthread_self(), verb);
+ pthread_mutex_unlock(&wreq_mutex);
+ return ERROR; /* TODO: dealloc structs before returning! */
+ }
+
+ if (connect_timeout > 0) {
+ curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, connect_timeout);
+ }
+ if (total_timeout > 0) {
+ curl_easy_setopt (curl, CURLOPT_TIMEOUT, total_timeout);
+ }
+
+ /* set up the default write function, but possibly override
+ * it below, if compression is desired and possible */
+ struct request params;
+ params.fd = fd;
+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &params);
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data);
+#if defined(CAN_GZIP)
+ if (do_compress) {
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data_zlib);
+ }
+#endif
+
+ struct curl_slist * headers = NULL; /* beginning of a DLL with headers */
+
+ char op_hdr [STRSIZE];
+ if(walrus_op != NULL) {
+ snprintf (op_hdr, STRSIZE, "EucaOperation: %s", walrus_op);
+ headers = curl_slist_append (headers, op_hdr);
+ }
+
+ time_t t;
+ t = time(&t);
+ struct tm tmp_t;
+ gmtime_r(&t, &tmp_t);
+ char date_str [17];
+
+ //Format for time
+ if(strftime(date_str, 17, "%Y%m%dT%H%M%SZ",&tmp_t)<0) {
+ close(fd);
+ pthread_mutex_unlock(&wreq_mutex);
+ return ERROR;
+ }
+
+ assert (strlen(date_str)+7<=STRSIZE);
+ char * newline = strchr (date_str, '\n');
+ if (newline!=NULL) { * newline = '\0'; } // remove newline if found
+ char date_hdr [STRSIZE];
+ snprintf (date_hdr, STRSIZE, "Date: %s", date_str);
+ headers = curl_slist_append (headers, date_hdr);
+
+ char host_hdr [STRSIZE];
+ char * url_host = NULL;
+ if((url_host=process_url(url,URL_HOSTNAME)) == NULL)
+ {
+ logprintfl (EUCAERROR, "{%u} walrus_request: URL has no host\n",(unsigned int)pthread_self());
+ pthread_mutex_unlock(&wreq_mutex);
+ return code;
+ }
+
+ snprintf (host_hdr, STRSIZE, "Host: %s", url_host);
+ headers = curl_slist_append (headers, host_hdr);
+
+ char * auth_str = eucav2_sign_request (verb, url, headers); /* create Walrus-compliant sig */
+ if (auth_str==NULL) {
+ close(fd);
+ pthread_mutex_unlock(&wreq_mutex);
+ return ERROR;
+ }
+ assert (strlen(auth_str)+16<=BUFSIZE);
+ headers = curl_slist_append (headers, auth_str);
+
+ curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers); /* register headers */
+ if (walrus_op) {
+ logprintfl (EUCADEBUG, "{%u} walrus_request: writing %s/%s output\n", (unsigned int)pthread_self(), verb, walrus_op);
+ logprintfl (EUCADEBUG, "{%u} from %s\n", (unsigned int)pthread_self(), url);
+ logprintfl (EUCADEBUG, "{%u} to %s\n", (unsigned int)pthread_self(), outfile);
+ } else {
+ logprintfl (EUCADEBUG, "{%u} walrus_request: writing %s output to %s\n", (unsigned int)pthread_self(), verb, outfile);
+ }
+ int retries = TOTAL_RETRIES;
+ int timeout = FIRST_TIMEOUT;
+ do {
+ params.total_wrote = 0L;
+ params.total_calls = 0L;
+#if defined(CAN_GZIP)
+ if (do_compress) {
+ /* allocate zlib inflate state */
+ params.strm.zalloc = Z_NULL;
+ params.strm.zfree = Z_NULL;
+ params.strm.opaque = Z_NULL;
+ params.strm.avail_in = 0;
+ params.strm.next_in = Z_NULL;
+ params.ret = inflateInit2 (&(params.strm), 31);
+ if (params.ret != Z_OK) {
+ zerr (params.ret, "walrus_request");
+ break;
+ }
+ }
+#endif
+
+ pthread_mutex_unlock(&wreq_mutex); /* unlock for message exchange */
+ result = curl_easy_perform (curl); /* do it */
+ pthread_mutex_lock(&wreq_mutex); /* relock for curl teardown */
+ logprintfl (EUCADEBUG, "{%u} walrus_request: wrote %lld byte(s) in %ld write(s)\n", (unsigned int)pthread_self(), params.total_wrote, params.total_calls);
+
+#if defined(CAN_GZIP)
+ if (do_compress) {
+ inflateEnd(&(params.strm));
+ if (params.ret != Z_STREAM_END) {
+ zerr (params.ret, "walrus_request");
+ }
+ }
+#endif
+
+ if (result) { // curl error (connection or transfer failed)
+ logprintfl (EUCAERROR, "{%u} walrus_request: %s (%d)\n", (unsigned int)pthread_self(), error_msg, result);
+
+ } else {
+ long httpcode;
+ curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &httpcode);
+ /* TODO: pull out response message, too */
+
+ switch (httpcode) {
+ case 200L: /* all good */
+ logprintfl (EUCAINFO, "{%u} walrus_request: to %s\n", (unsigned int)pthread_self(), outfile);
+ code = OK;
+ break;
+ case 408L: /* timeout, retry */
+ logprintfl (EUCAWARN, "{%u} walrus_request: server responded with HTTP code %ld (timeout)\n", (unsigned int)pthread_self(), httpcode);
+ //logcat (EUCADEBUG, outfile); /* dump the error from outfile into the log */
+ break;
+ default: /* some kind of error */
+ logprintfl (EUCAERROR, "{%u} walrus_request: server responded with HTTP code %ld\n", (unsigned int)pthread_self(), httpcode);
+ //logcat (EUCADEBUG, outfile); /* dump the error from outfile into the log */
+ retries=0;
+ }
+ }
+
+ if (code!=OK && retries>0) {
+ logprintfl (EUCAERROR, " download retry %d of %d will commence in %d seconds\n", retries, TOTAL_RETRIES, timeout);
+ sleep (timeout);
+ lseek (fd, 0L, SEEK_SET);
+ timeout <<= 1;
+ if (timeout > MAX_TIMEOUT)
+ timeout = MAX_TIMEOUT;
+ }
+
+ retries--;
+ } while (code!=OK && retries>0);
+ close (fd);
+
+ if ( code != OK ) {
+ logprintfl (EUCAINFO, "{%u} walrus_request: due to error, removing %s\n", (unsigned int)pthread_self(), outfile);
+ remove (outfile);
+ }
+
+ free (auth_str);
+ curl_slist_free_all (headers);
+ curl_easy_cleanup (curl);
+ pthread_mutex_unlock(&wreq_mutex);
+ return code;
+}
+
#if 0
/* Unused function */
static int walrus_request(const char *walrus_op, const char *verb, const char *requested_url, const char *outfile, const int do_compress)
@@ -368,8 +607,8 @@ static int walrus_request(const char *walrus_op, const char *verb, const char *r
}
#endif /* 0 */
-/* downloads a Walrus object from the URL, saves it to outfile */
-int walrus_object_by_url(const char *url, const char *outfile, const int do_compress)
+/* downloads a Walrus object from the URL, is it to outfile */
+int walrus_object_by_url (const char * url, const char * outfile, const int do_compress)
{
return walrus_request_timeout(NULL, "GET", url, outfile, do_compress, 120, 0);
}
View
3 util/Makefile
@@ -95,6 +95,9 @@ test_sensor: sensor.c sensor.h misc.o log.o ipc.o ../storage/diskutil.o
../storage/diskutil.o:
make -C ../storage
+test_auth: euca_auth.c log.o misc.o ipc.o ../storage/diskutil.o
+ $(CC) $(CFLAGS) $(INCLUDES) $(DEBUGS) -D_UNIT_TEST -o test_auth euca_auth.c log.o misc.o ../storage/diskutil.o ipc.o $(LIBS) $(LDFLAGS) $(EFENCE)
+
%.o: %.c %.h
$(CC) -c $(CFLAGS) $(INCLUDES) $(DEBUGS) `xslt-config --cflags` $<
View
1,005 util/euca_auth.c
@@ -74,6 +74,12 @@
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/err.h>
+#include <curl/curl.h>
+#include <pthread.h>
+#include <regex.h>
#include "euca_auth.h"
#include "misc.h" /* get_string_stats, logprintf */
@@ -83,16 +89,34 @@
static int initialized = 0;
#define FILENAME 512
-static char cert_file[FILENAME];
-static char pk_file[FILENAME];
+static char cert_file [FILENAME];
+static char pk_file [FILENAME];
+static char hex_digits[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
-int euca_init_cert(void)
+static regex_t* uri_regex = NULL;
+//Mutex to guard initialization and compile of the uri_regex
+static pthread_mutex_t regex_init_mutex = PTHREAD_MUTEX_INITIALIZER;
+//TODO: this pattern does not exclude some invalid URLs, but does require a protocol section
+static const char *url_pattern = "([^:?&]+://)([^:/?&]+)(:([0-9]+)?)?(/[^?&=]*)?(\\?(.*)?)?($)";
+
+//Mutex to guard certificate and ssl init to enforce the function as a singleton.
+static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int euca_init_cert (void)
{
- if (initialized)
- return 0;
+ if(initialized) return 0; //no need for lock if this is true
- char root[] = "";
- char *euca_home = getenv("EUCALYPTUS");
+ //Lock to enforce the singleton nature of this method, should only actually lock on first use
+ pthread_mutex_lock(&init_mutex);
+ if (initialized)
+ {
+ //Previous holder of lock initialized, so this thread can skip
+ pthread_mutex_unlock(&init_mutex);
+ return 0;
+ }
+
+ char root [] = "";
+ char * euca_home = getenv("EUCALYPTUS");
if (!euca_home) {
euca_home = root;
}