From df4eb587c262c93582916b344201e72e16534576 Mon Sep 17 00:00:00 2001 From: JohnZ Date: Fri, 27 Apr 2012 09:04:56 +0100 Subject: [PATCH] Auth correct for uploads --- cloudbridge/deploy-cloud-bridge.sh | 0 .../cloud/bridge/service/S3RestServlet.java | 2 +- .../service/controller/s3/S3BucketAction.java | 15 +++-- .../service/controller/s3/S3ObjectAction.java | 18 +++-- .../src/com/cloud/bridge/util/RestAuth.java | 67 ++++++++++++------- 5 files changed, 65 insertions(+), 37 deletions(-) mode change 100644 => 100755 cloudbridge/deploy-cloud-bridge.sh diff --git a/cloudbridge/deploy-cloud-bridge.sh b/cloudbridge/deploy-cloud-bridge.sh old mode 100644 new mode 100755 diff --git a/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java b/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java index 324ed8d1867..b21afd41f8a 100644 --- a/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java +++ b/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java @@ -311,7 +311,7 @@ public class S3RestServlet extends HttpServlet { if (info == null) throw new PermissionDeniedException("Unable to authenticate access key: " + AWSAccessKey); try { - if (auth.verifySignature( request.getMethod(), info.getSecretKey(), signature )) { + if (auth.verifySignature( request.getMethod(), info.getSecretKey(), signature )) { UserContext.current().initContext(AWSAccessKey, info.getSecretKey(), AWSAccessKey, info.getDescription(), request); return; } diff --git a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java index dfb21e9ccf9..55b5429fd2a 100644 --- a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java +++ b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java @@ -604,19 +604,19 @@ public class S3BucketAction implements ServletAction { public void executeGetBucketLogging(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO -- this is a beta feature of S3 - response.setStatus(501); + response.setStatus(405); } public void executeGetBucketLocation(HttpServletRequest request, HttpServletResponse response) throws IOException { - response.setStatus(501); + response.setStatus(405); } public void executeGetBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException { - response.setStatus(501); + response.setStatus(405); } public void executeDeleteBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException { - response.setStatus(501); + response.setStatus(405); } public void executePutBucket(HttpServletRequest request, HttpServletResponse response) throws IOException @@ -757,8 +757,11 @@ public class S3BucketAction implements ServletAction { } public void executePutBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException { - // TODO -- HiPri - Implement Put Bucket Website - response.setStatus(501); + // TODO -- HiPri - Undertake checks on Put Bucket Website + // Tested using configuration \nAllowOverride FileInfo AuthConfig Limit... in httpd.conf + // Need some way of using AllowOverride to allow use of .htaccess and then pushing .httaccess file to bucket subdirectory of mount point + // Currently has noop effect in the sense that a running apachectl process sees the directory contents without further action + response.setStatus(200); } public void executeDeleteBucket(HttpServletRequest request, HttpServletResponse response) throws IOException diff --git a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java index ea85d508d06..542859c0e8a 100644 --- a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java +++ b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java @@ -105,7 +105,7 @@ public class S3ObjectAction implements ServletAction { throws IOException, XMLStreamException { String method = request.getMethod(); - String queryString = request.getQueryString(); + String queryString = request.getQueryString(); String copy = null; response.addHeader( "x-amz-request-id", UUID.randomUUID().toString()); @@ -143,9 +143,9 @@ public class S3ObjectAction implements ServletAction { } else executeDeleteObject(request, response); } - else if (method.equalsIgnoreCase( "HEAD" )) - { - executeHeadObject(request, response); + else if (method.equalsIgnoreCase( "HEAD" )) + { + executeHeadObject(request, response); } else if (method.equalsIgnoreCase( "POST" )) { @@ -302,7 +302,8 @@ public class S3ObjectAction implements ServletAction { private void executeGetObject(HttpServletRequest request, HttpServletResponse response) throws IOException { String bucket = (String) request.getAttribute(S3Constants.BUCKET_ATTR_KEY); - String key = (String) request.getAttribute(S3Constants.OBJECT_ATTR_KEY); + String key = (String) request.getAttribute(S3Constants.OBJECT_ATTR_KEY); + S3GetObjectRequest engineRequest = new S3GetObjectRequest(); engineRequest.setBucketName(bucket); @@ -713,8 +714,10 @@ public class S3ObjectAction implements ServletAction { String cannedAccess = null; int uploadId = -1; - // -> Amazon defines to keep connection alive by sending whitespace characters until done + // AWS S3 specifies that the keep alive connection is by sending whitespace characters until done + // Therefore the XML version prolog is prepended to the stream in advance OutputStream os = response.getOutputStream(); + os.write("".getBytes()); String temp = request.getParameter("uploadId"); if (null != temp) uploadId = Integer.parseInt( temp ); @@ -781,7 +784,8 @@ public class S3ObjectAction implements ServletAction { xml.append( "" ).append( key ).append( "" ); xml.append( "\"" ).append( engineResponse.getETag()).append( "\"" ); xml.append( "" ); - os.write( xml.toString().getBytes()); + String xmlString = xml.toString().replaceAll("^\\s+", ""); // Remove leading whitespace characters + os.write( xmlString.getBytes()); os.close(); } else returnErrorXML( result, null, os ); diff --git a/cloudbridge/src/com/cloud/bridge/util/RestAuth.java b/cloudbridge/src/com/cloud/bridge/util/RestAuth.java index 5941f44c266..b289b5f1933 100755 --- a/cloudbridge/src/com/cloud/bridge/util/RestAuth.java +++ b/cloudbridge/src/com/cloud/bridge/util/RestAuth.java @@ -48,14 +48,33 @@ public class RestAuth { protected boolean amzDateSet = false; protected boolean useSubDomain = false; + protected Set allowedQueryParams; + public RestAuth() { // these must be lexicographically sorted AmazonHeaders = new TreeMap(); + allowedQueryParams = new HashSet() {{ + add("acl"); + add("lifecycle"); + add("location"); + add("logging"); + add("notification"); + add("partNumber"); + add("policy"); + add("requestPayment"); + add("torrent"); + add("uploadId"); + add("uploads"); + add("versionId"); + add("versioning"); + add("versions"); + add("website"); + }}; } public RestAuth(boolean useSubDomain) { - // these must be lexicographically sorted - AmazonHeaders = new TreeMap(); + //invoke the other constructor + this(); this.useSubDomain = useSubDomain; } @@ -140,22 +159,28 @@ public class RestAuth { return; } - query = new String( "?" + query.trim()); + // Sub-resources (i.e.: query params) must be lex sorted + Set subResources = new TreeSet(); - // -> only interested in this subset of parameters - if (query.startsWith( "?acl") || query.startsWith( "?lifecycle" ) || query.startsWith( "?location" ) || - query.startsWith( "?logging" ) || query.startsWith( "?notification" ) || query.startsWith( "?partNumber" ) || - query.startsWith( "?policy" ) || query.startsWith( "?requestPayment" ) || query.startsWith( "?torrent" ) || - query.startsWith( "?uploadId" ) || query.startsWith( "?uploads" ) || query.startsWith( "?versionId" ) || - query.startsWith( "?versioning" ) || query.startsWith( "?versions" ) || query.startsWith( "?website" ) - ) - { - - // The query string includes the value given to the start parameter (using =) but ignores all other arguments after & - int offset = query.indexOf( "&" ); - if ( -1 != offset ) query = query.substring( 0, offset ); - this.queryString = query; + String [] queryParams = query.split("&"); + StringBuffer builtQuery= new StringBuffer(); + for (String queryParam:queryParams) { + // lookup parameter name + String paramName = queryParam.split("=")[0]; + if (allowedQueryParams.contains(paramName)) { + subResources.add(queryParam); + } } + for (String subResource:subResources) { + builtQuery.append(subResource + "&"); + } + // If anything inside the string buffer, add a "?" at the beginning, + // and then remove the last '&' + if (builtQuery.length() > 0) { + builtQuery.insert(0, "?"); + builtQuery.deleteCharAt(builtQuery.length()-1); + } + this.queryString = builtQuery.toString(); } @@ -235,7 +260,7 @@ public class RestAuth { throws SignatureException, UnsupportedEncodingException { if (null == httpVerb || null == secretKey || null == signature) return false; - + httpVerb = httpVerb.trim(); secretKey = secretKey.trim(); signature = signature.trim(); @@ -243,7 +268,6 @@ public class RestAuth { // First calculate the StringToSign after the caller has initialized all the header values String StringToSign = genStringToSign( httpVerb ); String calSig = calculateRFC2104HMAC( StringToSign, secretKey ); - // Was the passed in signature URL encoded? (it must be base64 encoded) int offset = signature.indexOf( "%" ); if (-1 != offset) signature = URLDecoder.decode( signature, "UTF-8" ); @@ -273,14 +297,12 @@ public class RestAuth { StringBuffer canonicalized = new StringBuffer(); String temp = null; String canonicalizedResourceElement = genCanonicalizedResourceElement(); - - canonicalized.append( httpVerb ).append( "\n" ); - if ( (null != this.contentMD5) && (null == canonicalizedResourceElement) ) + if ( (null != this.contentMD5) ) canonicalized.append( this.contentMD5 ); canonicalized.append( "\n" ); - if ( (null != this.contentType) && (null == canonicalizedResourceElement) ) + if ( (null != this.contentType) ) canonicalized.append( this.contentType ); canonicalized.append( "\n" ); @@ -356,7 +378,6 @@ public class RestAuth { */ private String calculateRFC2104HMAC( String signIt, String secretKey ) throws SignatureException { - String result = null; try { SecretKeySpec key = new SecretKeySpec( secretKey.getBytes(), "HmacSHA1" );