/******************************************************************************* *Copyright (c) 2009 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, only version 3 of the License. * * * This file is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. * * Please contact Eucalyptus Systems, Inc., 130 Castilian * Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/> * if you need additional information or have any questions. * * This file may incorporate work covered under the following copyright and * permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, with * or without modification, are permitted provided that the following * conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF * THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE * LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS * SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA * BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN * THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT * OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR * WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH * ANY SUCH LICENSES OR RIGHTS. *******************************************************************************/ package edu.ucsb.eucalyptus.cloud.ws; /* * * Author: Sunil Soman sunils@cs.ucsb.edu */ import edu.ucsb.eucalyptus.cloud.*; import edu.ucsb.eucalyptus.cloud.entities.*; import edu.ucsb.eucalyptus.msgs.*; import edu.ucsb.eucalyptus.storage.StorageManager; import edu.ucsb.eucalyptus.util.*; import org.apache.log4j.Logger; import org.apache.tools.ant.util.DateUtils; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpVersion; import org.jboss.netty.handler.stream.ChunkedFile; import org.jboss.netty.handler.stream.ChunkedInput; import com.eucalyptus.auth.Authentication; import com.eucalyptus.auth.NoSuchUserException; import com.eucalyptus.auth.SystemCredentialProvider; import com.eucalyptus.auth.Users; import com.eucalyptus.auth.X509Cert; import com.eucalyptus.auth.principal.User; import com.eucalyptus.auth.util.EucaKeyStore; import com.eucalyptus.auth.util.Hashes; import com.eucalyptus.bootstrap.Component; import com.eucalyptus.entities.EntityWrapper; import com.eucalyptus.http.MappingHttpResponse; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.auth.crypto.Digest; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.zip.GZIPInputStream; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.log4j.Logger; import org.apache.tools.ant.util.DateUtils; import org.apache.xml.dtm.DTMIterator; import org.apache.xml.dtm.ref.DTMNodeList; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpVersion; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import com.eucalyptus.auth.NoSuchUserException; import com.eucalyptus.auth.SystemCredentialProvider; import com.eucalyptus.auth.Users; import com.eucalyptus.auth.principal.User; import com.eucalyptus.auth.X509Cert; import com.eucalyptus.auth.util.Hashes; import com.eucalyptus.bootstrap.Component; import com.eucalyptus.entities.EntityWrapper; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.WalrusProperties; import edu.ucsb.eucalyptus.cloud.AccessDeniedException; import edu.ucsb.eucalyptus.cloud.BucketLogData; import edu.ucsb.eucalyptus.cloud.DecryptionFailedException; import edu.ucsb.eucalyptus.cloud.NoSuchBucketException; import edu.ucsb.eucalyptus.cloud.NoSuchEntityException; import edu.ucsb.eucalyptus.cloud.NotAuthorizedException; import edu.ucsb.eucalyptus.cloud.entities.BucketInfo; import edu.ucsb.eucalyptus.cloud.entities.ImageCacheInfo; import edu.ucsb.eucalyptus.cloud.entities.ObjectInfo; import edu.ucsb.eucalyptus.msgs.CacheImageResponseType; import edu.ucsb.eucalyptus.msgs.CacheImageType; import edu.ucsb.eucalyptus.msgs.CheckImageResponseType; import edu.ucsb.eucalyptus.msgs.CheckImageType; import edu.ucsb.eucalyptus.msgs.FlushCachedImageResponseType; import edu.ucsb.eucalyptus.msgs.FlushCachedImageType; import edu.ucsb.eucalyptus.msgs.GetDecryptedImageResponseType; import edu.ucsb.eucalyptus.msgs.GetDecryptedImageType; import edu.ucsb.eucalyptus.msgs.ValidateImageResponseType; import edu.ucsb.eucalyptus.msgs.ValidateImageType; import edu.ucsb.eucalyptus.storage.StorageManager; import edu.ucsb.eucalyptus.util.EucaSemaphore; import edu.ucsb.eucalyptus.util.EucaSemaphoreDirectory; import edu.ucsb.eucalyptus.util.WalrusDataMessenger; import edu.ucsb.eucalyptus.util.WalrusMonitor; import edu.ucsb.eucalyptus.util.XMLParser; public class WalrusImageManager { private static Logger LOG = Logger.getLogger( WalrusImageManager.class ); private static ConcurrentHashMap<String, ImageCacher> imageCachers = new ConcurrentHashMap<String, ImageCacher>(); private StorageManager storageManager; private WalrusDataMessenger imageMessenger; public WalrusImageManager(StorageManager storageManager, WalrusDataMessenger imageMessenger) { this.storageManager = storageManager; this.imageMessenger = imageMessenger; } private String decryptImage(String bucketName, String objectKey, String userId, boolean isAdministrator) throws EucalyptusCloudException { EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); List<BucketInfo> bucketList = db.query(bucketInfo); if (bucketList.size() > 0) { EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, objectKey); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if(objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if(objectInfo.canRead(userId)) { String objectName = objectInfo.getObjectName(); File file = new File(storageManager.getObjectPath(bucketName, objectName)); XMLParser parser = new XMLParser(file); //Read manifest String imageKey = parser.getValue("//image/name"); String encryptedKey = parser.getValue("//ec2_encrypted_key"); String encryptedIV = parser.getValue("//ec2_encrypted_iv"); String signature = parser.getValue("//signature"); String image = parser.getXML("image"); String machineConfiguration = parser.getXML("machine_configuration"); String verificationString = machineConfiguration + image; Signature sigVerifier; try { sigVerifier = Signature.getInstance("SHA1withRSA"); } catch (NoSuchAlgorithmException ex) { LOG.error(ex, ex); throw new DecryptionFailedException("SHA1withRSA not found"); } if(isAdministrator) { try { boolean verified = false; for(User user:Users.listAllUsers( )) { for (X509Certificate cert : user.getAllX509Certificates()) { if(cert != null) verified = canVerifySignature(sigVerifier, cert, signature, verificationString); if(verified) break; } } if(!verified) { X509Certificate cert = SystemCredentialProvider.getCredentialProvider(Component.eucalyptus).getCertificate(); if(cert != null) verified = canVerifySignature(sigVerifier, cert, signature, verificationString); } if(!verified) { throw new NotAuthorizedException("Invalid signature"); } } catch (Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("signature verification"); } } else { boolean signatureVerified = false; User user = null; try { user = Users.lookupUser( userId ); } catch ( NoSuchUserException e ) { throw new AccessDeniedException(userId,e); } try { for(X509Certificate cert : user.getAllX509Certificates()) { if(cert != null) { signatureVerified = canVerifySignature(sigVerifier, cert, signature, verificationString); } if(signatureVerified) break; } } catch(Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("signature verification"); } if(!signatureVerified) { try { X509Certificate cert = SystemCredentialProvider.getCredentialProvider(Component.eucalyptus).getCertificate(); if(cert != null) signatureVerified = canVerifySignature(sigVerifier, cert, signature, verificationString); } catch(Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("signature verification"); } } if(!signatureVerified) { throw new NotAuthorizedException("Invalid signature"); } } List<String> parts = parser.getValues("//image/parts/part/filename"); if(parts == null) throw new DecryptionFailedException("Invalid manifest"); ArrayList<String> qualifiedPaths = new ArrayList<String>(); searchObjectInfo = new ObjectInfo(); searchObjectInfo.setBucketName(bucketName); List<ObjectInfo> bucketObjectInfos = dbObject.query(searchObjectInfo); for (String part: parts) { for(ObjectInfo object : bucketObjectInfos) { if(part.equals(object.getObjectKey())) { qualifiedPaths.add(storageManager.getObjectPath(bucketName, object.getObjectName())); } } } //Assemble parts String encryptedImageKey = UUID.randomUUID().toString() + ".crypt.gz";//imageKey + "-" + Hashes.getRandom(5) + ".crypt.gz"; String encryptedImageName = storageManager.getObjectPath(bucketName, encryptedImageKey); String decryptedImageKey = encryptedImageKey.substring(0, encryptedImageKey.lastIndexOf("crypt.gz")) + "tgz"; String decryptedImageName = storageManager.getObjectPath(bucketName, decryptedImageKey); assembleParts(encryptedImageName, qualifiedPaths); //Decrypt key and IV byte[] key; byte[] iv; try { PrivateKey pk = SystemCredentialProvider.getCredentialProvider( Component.eucalyptus ).getPrivateKey(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, pk); String keyString = new String(cipher.doFinal(Hashes.hexToBytes(encryptedKey))); key = Hashes.hexToBytes(keyString); String ivString = new String(cipher.doFinal(Hashes.hexToBytes(encryptedIV))); iv = Hashes.hexToBytes(ivString); } catch(Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("AES params"); } //Unencrypt image try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC"); IvParameterSpec salt = new IvParameterSpec(iv); SecretKey keySpec = new SecretKeySpec(key, "AES"); cipher.init(Cipher.DECRYPT_MODE, keySpec, salt); decryptImage(encryptedImageName, decryptedImageName, cipher); } catch (Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("decryption failed"); } try { storageManager.deleteAbsoluteObject(encryptedImageName); } catch (Exception ex) { LOG.error(ex); throw new EucalyptusCloudException(); } db.commit(); return decryptedImageKey; } } } return null; } private boolean canVerifySignature(Signature sigVerifier, X509Certificate cert, String signature, String verificationString) throws Exception { PublicKey publicKey = cert.getPublicKey(); sigVerifier.initVerify(publicKey); sigVerifier.update((verificationString).getBytes()); return sigVerifier.verify(Hashes.hexToBytes(signature)); } private void checkManifest(String bucketName, String objectKey, String userId) throws EucalyptusCloudException { EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); BucketInfo bucket = null; try { bucket = db.getUnique(bucketInfo); } catch(Throwable t) { throw new EucalyptusCloudException("Unable to get bucket: " + bucketName, t); } if (bucket != null) { EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, objectKey); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if(objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if(objectInfo.canRead(userId)) { String objectName = objectInfo.getObjectName(); File file = new File(storageManager.getObjectPath(bucketName, objectName)); XMLParser parser = new XMLParser(file); //Read manifest String encryptedKey = parser.getValue("//ec2_encrypted_key"); String encryptedIV = parser.getValue("//ec2_encrypted_iv"); String signature = parser.getValue("//signature"); String image = parser.getXML("image"); String machineConfiguration = parser.getXML("machine_configuration"); User user = null; try { user = Users.lookupUser( userId ); } catch ( NoSuchUserException e ) { throw new AccessDeniedException(userId,e); } boolean signatureVerified = false; Signature sigVerifier; try { sigVerifier = Signature.getInstance("SHA1withRSA"); } catch (NoSuchAlgorithmException ex) { LOG.error(ex, ex); throw new DecryptionFailedException("SHA1withRSA not found"); } try { X509Certificate cert = user.getX509Certificate( ); PublicKey publicKey = cert.getPublicKey(); sigVerifier.initVerify(publicKey); sigVerifier.update((machineConfiguration + image).getBytes()); signatureVerified = sigVerifier.verify(Hashes.hexToBytes(signature)); } catch(Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("signature verification"); } //check if Eucalyptus signed it if(!signatureVerified) { try { X509Certificate cert = SystemCredentialProvider.getCredentialProvider(Component.eucalyptus).getCertificate(); PublicKey publicKey = cert.getPublicKey(); sigVerifier.initVerify(publicKey); sigVerifier.update((machineConfiguration + image).getBytes()); signatureVerified = sigVerifier.verify(Hashes.hexToBytes(signature)); } catch(Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("signature verification"); } } if(!signatureVerified) { throw new NotAuthorizedException("Invalid signature"); } //Decrypt key and IV byte[] key; byte[] iv; try { PrivateKey pk = SystemCredentialProvider.getCredentialProvider(Component.eucalyptus).getPrivateKey(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, pk); key = Hashes.hexToBytes(new String(cipher.doFinal(Hashes.hexToBytes(encryptedKey)))); iv = Hashes.hexToBytes(new String(cipher.doFinal(Hashes.hexToBytes(encryptedIV)))); } catch(Exception ex) { db.rollback(); LOG.error(ex, ex); throw new DecryptionFailedException("AES params"); } db.commit(); } else { db.rollback(); throw new AccessDeniedException("Key", objectKey); } } else { db.rollback(); throw new NoSuchEntityException(objectKey); } } else { db.rollback(); throw new NoSuchBucketException(bucketName); } } private boolean isCached(String bucketName, String manifestKey) { EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); try { ImageCacheInfo foundImageCacheInfo = db.getUnique(searchImageCacheInfo); db.commit(); if(foundImageCacheInfo.getInCache()) return true; else return false; } catch(Exception ex) { db.commit(); return false; } } private long checkCachingProgress(String bucketName, String manifestKey, long oldBytesRead) { EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); try { ImageCacheInfo foundImageCacheInfo = db.getUnique(searchImageCacheInfo); String cacheImageKey = foundImageCacheInfo.getImageName().substring(0, foundImageCacheInfo.getImageName().lastIndexOf(".tgz")); long objectSize = storageManager.getObjectSize(bucketName, cacheImageKey); db.commit(); if(objectSize > 0) { return objectSize - oldBytesRead; } return oldBytesRead; } catch (Exception ex) { db.commit(); return oldBytesRead; } } private synchronized void cacheImage(String bucketName, String manifestKey, String userId, boolean isAdministrator) throws EucalyptusCloudException { EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); List<ImageCacheInfo> imageCacheInfos = db.query(searchImageCacheInfo); String decryptedImageKey = null; if(imageCacheInfos.size() != 0) { ImageCacheInfo icInfo = imageCacheInfos.get(0); if(!icInfo.getInCache()) { decryptedImageKey = icInfo.getImageName(); } else { db.commit(); return; } } db.commit(); //unzip, untar image in the background ImageCacher imageCacher = imageCachers.putIfAbsent(bucketName + manifestKey, new ImageCacher(bucketName, manifestKey, decryptedImageKey)); if(imageCacher == null) { if(decryptedImageKey == null) { try { decryptedImageKey = decryptImage(bucketName, manifestKey, userId, isAdministrator); } catch(EucalyptusCloudException ex) { imageCachers.remove(bucketName + manifestKey); throw ex; } //decryption worked. Add it. ImageCacheInfo foundImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); foundImageCacheInfo.setImageName(decryptedImageKey); foundImageCacheInfo.setInCache(false); foundImageCacheInfo.setUseCount(0); foundImageCacheInfo.setSize(0L); db = WalrusControl.getEntityWrapper(); db.add(foundImageCacheInfo); db.commit(); } imageCacher = imageCachers.get(bucketName + manifestKey); imageCacher.setDecryptedImageKey(decryptedImageKey); imageCacher.start(); } } private void flushCachedImage (String bucketName, String objectKey) throws Exception { EucaSemaphore semaphore = EucaSemaphoreDirectory.getSemaphore(bucketName + "/" + objectKey); while(semaphore.inUse()) { try { synchronized (semaphore) { semaphore.wait(); } } catch(InterruptedException ex) { LOG.error(ex); } } EucaSemaphoreDirectory.removeSemaphore(bucketName + "/" + objectKey); EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, objectKey); List<ImageCacheInfo> foundImageCacheInfos = db.query(searchImageCacheInfo); if(foundImageCacheInfos.size() > 0) { ImageCacheInfo foundImageCacheInfo = foundImageCacheInfos.get(0); LOG.info("Attempting to flush cached image: " + bucketName + "/" + objectKey); if(foundImageCacheInfo.getInCache() && (imageCachers.get(bucketName + objectKey) == null)) { db.delete(foundImageCacheInfo); storageManager.deleteObject(bucketName, foundImageCacheInfo.getImageName()); } db.commit(); } else { db.rollback(); LOG.warn("Cannot find image in cache" + bucketName + "/" + objectKey); } } private void validateManifest(String bucketName, String objectKey, String userId) throws EucalyptusCloudException { EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); BucketInfo bucket = null; try { bucket = db.getUnique(bucketInfo); } catch(Throwable t) { throw new EucalyptusCloudException("Unable to get bucket: " + bucketName, t); } if (bucket != null) { EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, objectKey); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if(objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if(objectInfo.canRead(userId)) { String objectName = objectInfo.getObjectName(); File file = new File(storageManager.getObjectPath(bucketName, objectName)); XMLParser parser = new XMLParser(file); String image = parser.getXML("image"); String machineConfiguration = parser.getXML("machine_configuration"); String verificationString = machineConfiguration + image; FileInputStream inStream = null; FileInputStream fileInputStream = null; try { PrivateKey pk = SystemCredentialProvider.getCredentialProvider(Component.eucalyptus).getPrivateKey(); Signature sigCloud = Signature.getInstance("SHA1withRSA"); sigCloud.initSign(pk); sigCloud.update(verificationString.getBytes()); String signature = new String(Hashes.bytesToHex(sigCloud.sign())); //TODO: refactor DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance( ).newDocumentBuilder( ); fileInputStream = new FileInputStream( file ); Document docRoot = docBuilder.parse( fileInputStream ); Element sigElement = docRoot.createElement("signature"); sigElement.setTextContent(signature); Node manifestElem = docRoot.getFirstChild(); manifestElem.appendChild(sigElement); fileInputStream.close(); Source source = new DOMSource(docRoot); Result result = new StreamResult(file); Transformer xformer = TransformerFactory.newInstance().newTransformer(); xformer.transform(source,result); try { MessageDigest digest = Digest.MD5.get(); inStream = new FileInputStream(file); byte[] bytes = new byte[WalrusProperties.IO_CHUNK_SIZE]; int bytesRead = -1; long totalBytesRead = 0; try { while((bytesRead = inStream.read(bytes, 0, bytes.length)) > 0) { digest.update(bytes, 0, bytesRead); totalBytesRead += bytesRead; } } catch (IOException e) { LOG.error(e); throw new EucalyptusCloudException(e); } finally { try { inStream.close(); } catch (IOException e) { LOG.error(e); throw new EucalyptusCloudException(e); } } String md5 = Hashes.bytesToHex(digest.digest()); objectInfo.setEtag(md5); objectInfo.setSize(totalBytesRead); } catch (FileNotFoundException e) { LOG.error(e, e); throw new EucalyptusCloudException(e); } } catch(Exception ex) { if(inStream != null) { try { inStream.close(); } catch(IOException e) { LOG.error(e); } } if(fileInputStream != null) { try { fileInputStream.close(); } catch(IOException e) { LOG.error(e); } } db.rollback(); LOG.error(ex, ex); throw new EucalyptusCloudException("Unable to sign manifest: " + bucketName + "/" + objectKey); } db.commit(); } else { db.rollback(); throw new AccessDeniedException("Key", objectKey); } } else { db.rollback(); throw new NoSuchEntityException(objectKey); } } else { db.rollback(); throw new NoSuchBucketException(bucketName); } } public void startImageCacheFlusher(String bucketName, String manifestName) { ImageCacheFlusher imageCacheFlusher = new ImageCacheFlusher(bucketName, manifestName); imageCacheFlusher.start(); } private class ImageCacheFlusher extends Thread { private String bucketName; private String objectKey; public ImageCacheFlusher(String bucketName, String objectKey) { this.bucketName = bucketName; this.objectKey = objectKey; } public void run() { try { flushCachedImage(bucketName, objectKey); } catch(Exception ex) { LOG.error(ex); } } } private class ImageCacher extends Thread { private String bucketName; private String manifestKey; private String decryptedImageKey; private boolean imageSizeExceeded; private long spaceNeeded; public ImageCacher(String bucketName, String manifestKey, String decryptedImageKey) { this.bucketName = bucketName; this.manifestKey = manifestKey; this.decryptedImageKey = decryptedImageKey; this.imageSizeExceeded = false; } public void setDecryptedImageKey(String key) { this.decryptedImageKey = key; } private long tryToCache(String decryptedImageName, String tarredImageName, String imageName) { Long unencryptedSize = 0L; boolean failed = false; try { if(!imageSizeExceeded) { LOG.info("Unzipping image: " + bucketName + "/" + manifestKey); unzipImage(decryptedImageName, tarredImageName); LOG.info("Untarring image: " + bucketName + "/" + manifestKey); unencryptedSize = untarImage(tarredImageName, imageName); } else { File imageFile = new File(imageName); if(imageFile.exists()) { unencryptedSize = imageFile.length(); } else { LOG.error("Could not find image: " + imageName); imageSizeExceeded = false; return -1L; } } Long oldCacheSize = 0L; EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); List<ImageCacheInfo> imageCacheInfos = db.query(new ImageCacheInfo()); for(ImageCacheInfo imageCacheInfo: imageCacheInfos) { if(imageCacheInfo.getInCache()) { oldCacheSize += imageCacheInfo.getSize(); } } db.commit(); if((oldCacheSize + unencryptedSize) > (WalrusInfo.getWalrusInfo().getStorageMaxCacheSizeInMB() * WalrusProperties.M)) { LOG.error("Maximum image cache size exceeded when decrypting " + bucketName + "/" + manifestKey); failed = true; imageSizeExceeded = true; spaceNeeded = unencryptedSize; } } catch(Exception ex) { LOG.warn(ex); //try to evict an entry and try again failed = true; } if(failed) { if(!imageSizeExceeded) { try { storageManager.deleteAbsoluteObject(tarredImageName); storageManager.deleteAbsoluteObject(imageName); } catch (Exception exception) { LOG.error(exception); } } return -1L; } LOG.info("Cached image: " + bucketName + "/" + manifestKey + " size: " + String.valueOf(unencryptedSize)); return unencryptedSize; } private void notifyWaiters() { WalrusMonitor monitor = imageMessenger.getMonitor(bucketName + "/" + manifestKey); synchronized (monitor) { monitor.notifyAll(); } imageMessenger.removeMonitor(bucketName + "/" + manifestKey); imageCachers.remove(bucketName + manifestKey); } public void run() { //update status //wake up any waiting consumers String decryptedImageName = storageManager.getObjectPath(bucketName, decryptedImageKey); String imageName = decryptedImageName.substring(0, decryptedImageName.lastIndexOf(".tgz")); String tarredImageName = imageName + (".tar"); String imageKey = decryptedImageKey.substring(0, decryptedImageKey.lastIndexOf(".tgz")); Long unencryptedSize; int numberOfRetries = 0; while((unencryptedSize = tryToCache(decryptedImageName, tarredImageName, imageName)) < 0) { try { Thread.sleep(WalrusProperties.IMAGE_CACHE_RETRY_TIMEOUT); } catch(InterruptedException ex) { notifyWaiters(); return; } WalrusProperties.IMAGE_CACHE_RETRY_TIMEOUT = 2* WalrusProperties.IMAGE_CACHE_RETRY_TIMEOUT; if(numberOfRetries++ >= WalrusProperties.IMAGE_CACHE_RETRY_LIMIT) { notifyWaiters(); return; } EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(); searchImageCacheInfo.setInCache(true); List<ImageCacheInfo> imageCacheInfos = db.query(searchImageCacheInfo); if(imageCacheInfos.size() == 0) { LOG.error("No cached images found to flush. Unable to cache image. Please check the error log and the image cache size."); db.rollback(); notifyWaiters(); return; } Collections.sort(imageCacheInfos); db.commit(); try { if(spaceNeeded > 0) { ArrayList<ImageCacheInfo> imagesToFlush = new ArrayList<ImageCacheInfo>(); long tryToFree = spaceNeeded; for(ImageCacheInfo imageCacheInfo : imageCacheInfos) { if(tryToFree <= 0) break; long imageSize = imageCacheInfo.getSize(); tryToFree -= imageSize; imagesToFlush.add(imageCacheInfo); } if(imagesToFlush.size() == 0) { LOG.error("Unable to flush existing images. Sorry."); notifyWaiters(); return; } for(ImageCacheInfo imageCacheInfo : imagesToFlush) { flushCachedImage(imageCacheInfo.getBucketName(), imageCacheInfo.getManifestName()); } } else { LOG.error("Unable to cache image. Unable to flush existing images."); notifyWaiters(); return; } } catch(Exception ex) { LOG.error(ex); LOG.error("Unable to flush previously cached image. Please increase your image cache size"); notifyWaiters(); } } try { storageManager.deleteAbsoluteObject(decryptedImageName); storageManager.deleteAbsoluteObject(tarredImageName); EntityWrapper<ImageCacheInfo>db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); List<ImageCacheInfo> foundImageCacheInfos = db.query(searchImageCacheInfo); if(foundImageCacheInfos.size() > 0) { ImageCacheInfo foundImageCacheInfo = foundImageCacheInfos.get(0); foundImageCacheInfo.setImageName(imageKey); foundImageCacheInfo.setInCache(true); foundImageCacheInfo.setSize(unencryptedSize); db.commit(); //wake up waiters notifyWaiters(); } else { db.rollback(); LOG.error("Could not expand image" + decryptedImageName); } } catch (Exception ex) { LOG.error(ex); } } } private void unzipImage(String decryptedImageName, String tarredImageName) throws Exception { GZIPInputStream in = new GZIPInputStream(new FileInputStream(new File(decryptedImageName))); File outFile = new File(tarredImageName); ReadableByteChannel inChannel = Channels.newChannel(in); FileOutputStream fileOutputStream = new FileOutputStream(outFile); WritableByteChannel outChannel = fileOutputStream.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(102400); try { while (inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); } } catch(IOException ex) { throw ex; } finally { outChannel.close(); fileOutputStream.close(); inChannel.close(); in.close(); } } private long untarImage(String tarredImageName, String imageName) throws Exception { /*TarInputStream in = new TarInputStream(new FileInputStream(new File(tarredImageName))); File outFile = new File(imageName); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); TarEntry tEntry = in.getNextEntry(); assert(!tEntry.isDirectory()); in.copyEntryContents(out); out.close(); in.close(); return outFile.length();*/ //Workaround because TarInputStream is broken Tar tarrer = new Tar(); tarrer.untar(tarredImageName, imageName); File outFile = new File(imageName); if(outFile.exists()) return outFile.length(); else throw new EucalyptusCloudException("Could not untar image " + imageName); } private class StreamConsumer extends Thread { private InputStream is; private File file; public StreamConsumer(InputStream is) { this.is = is; } public StreamConsumer(InputStream is, File file) { this(is); this.file = file; } public void run() { BufferedOutputStream outStream = null; try { BufferedInputStream inStream = new BufferedInputStream(is); if(file != null) { outStream = new BufferedOutputStream(new FileOutputStream(file)); } byte[] bytes = new byte[WalrusProperties.IO_CHUNK_SIZE]; int bytesRead; while((bytesRead = inStream.read(bytes)) > 0) { if(outStream != null) { outStream.write(bytes, 0, bytesRead); } } if(outStream != null) outStream.close(); } catch (IOException ex) { if(outStream != null) try { outStream.close(); } catch (IOException e) { LOG.error(e); } LOG.error(ex); } } } private class Tar { public void untar(String tarFile, String outFile) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(new String[]{ "/bin/tar", "xfO", tarFile}); StreamConsumer error = new StreamConsumer(proc.getErrorStream()); StreamConsumer output = new StreamConsumer(proc.getInputStream(), new File(outFile)); error.start(); output.start(); int exitVal = proc.waitFor(); output.join(); } catch (Throwable t) { LOG.error(t); } } } private void decryptImage(final String encryptedImageName, final String decryptedImageName, final Cipher cipher) throws Exception { LOG.info("Decrypting image: " + decryptedImageName); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(decryptedImageName))); File inFile = new File(encryptedImageName); BufferedInputStream in = new BufferedInputStream(new FileInputStream(inFile)); int bytesRead = 0; byte[] bytes = new byte[8192]; while((bytesRead = in.read(bytes)) > 0) { byte[] outBytes = cipher.update(bytes, 0, bytesRead); out.write(outBytes); } byte[] outBytes = cipher.doFinal(); out.write(outBytes); in.close(); out.close(); LOG.info("Done decrypting: " + decryptedImageName); } private void assembleParts(final String name, List<String> parts) { FileOutputStream fileOutputStream = null; FileInputStream fileInputStream = null; try { fileOutputStream = new FileOutputStream(new File(name)); FileChannel out = fileOutputStream.getChannel(); for (String partName: parts) { fileInputStream = new FileInputStream(new File(partName)); FileChannel in = fileInputStream.getChannel(); in.transferTo(0, in.size(), out); in.close(); fileInputStream.close(); } out.close(); fileOutputStream.close(); } catch (Exception ex) { LOG.error(ex); } finally { if(fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { LOG.error(e); } } if(fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { LOG.error(e); } } } } public GetDecryptedImageResponseType getDecryptedImage(GetDecryptedImageType request) throws EucalyptusCloudException { GetDecryptedImageResponseType reply = (GetDecryptedImageResponseType) request.getReply(); String bucketName = request.getBucket(); String objectKey = request.getKey(); String userId = request.getUserId(); EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); List<BucketInfo> bucketList = db.query(bucketInfo); if (bucketList.size() > 0) { EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, objectKey); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if(objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if(objectInfo.canRead(userId) || request.isAdministrator() ) { db.commit(); EucaSemaphore semaphore = EucaSemaphoreDirectory.getSemaphore(bucketName + "/" + objectKey); try { semaphore.acquire(); } catch(InterruptedException ex) { throw new EucalyptusCloudException("semaphore could not be acquired"); } EntityWrapper<ImageCacheInfo> db2 = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, objectKey); List<ImageCacheInfo> foundImageCacheInfos = db2.query(searchImageCacheInfo); if(foundImageCacheInfos.size() > 0) { ImageCacheInfo imageCacheInfo = foundImageCacheInfos.get(0); if(imageCacheInfo.getInCache() && (!storageManager.objectExists(bucketName, imageCacheInfo.getImageName()))) { db2.delete(imageCacheInfo); db2.commit(); db2 = WalrusControl.getEntityWrapper(); foundImageCacheInfos = db2.query(searchImageCacheInfo); } } if((foundImageCacheInfos.size() == 0) || (!imageCachers.containsKey(bucketName + objectKey))) { db2.commit(); //issue a cache request LOG.info("Image " + bucketName + "/" + objectKey + " not found in cache. Issuing cache request (might take a while...)"); cacheImage(bucketName, objectKey, userId, request.isAdministrator()); //query db again db2 = WalrusControl.getEntityWrapper(); foundImageCacheInfos = db2.query(searchImageCacheInfo); } ImageCacheInfo foundImageCacheInfo = null; if(foundImageCacheInfos.size() > 0) foundImageCacheInfo = foundImageCacheInfos.get(0); db2.commit(); if((foundImageCacheInfo == null) || (!foundImageCacheInfo.getInCache())) { boolean cached = false; WalrusMonitor monitor = imageMessenger.getMonitor(bucketName + "/" + objectKey); synchronized (monitor) { try { long bytesCached = 0; int number_of_tries = 0; do { LOG.info("Waiting " + WalrusProperties.CACHE_PROGRESS_TIMEOUT + "ms for image to cache (" + number_of_tries + " out of " + WalrusProperties.IMAGE_CACHE_RETRY_LIMIT + ")"); monitor.wait(WalrusProperties.CACHE_PROGRESS_TIMEOUT); if(isCached(bucketName, objectKey)) { cached = true; break; } long newBytesCached = checkCachingProgress(bucketName, objectKey, bytesCached); boolean is_caching = (newBytesCached - bytesCached) > 0 ? true : false; if (!is_caching && (number_of_tries++ >= WalrusProperties.IMAGE_CACHE_RETRY_LIMIT)) break; bytesCached = newBytesCached; if(is_caching) { LOG.info("Bytes cached so far for image " + bucketName + "/" + objectKey + " :" + String.valueOf(bytesCached)); } } while(true); } catch(Exception ex) { LOG.error(ex); semaphore.release(); throw new EucalyptusCloudException("monitor failure"); } } if(!cached) { LOG.error("Tired of waiting to cache image: " + bucketName + "/" + objectKey + " giving up"); semaphore.release(); throw new EucalyptusCloudException("caching failure"); } //caching may have modified the db. repeat the query db2 = WalrusControl.getEntityWrapper(); foundImageCacheInfos = db2.query(searchImageCacheInfo); if(foundImageCacheInfos.size() > 0) { foundImageCacheInfo = foundImageCacheInfos.get(0); foundImageCacheInfo.setUseCount(foundImageCacheInfo.getUseCount() + 1); assert(foundImageCacheInfo.getInCache()); } else { db2.rollback(); semaphore.release(); throw new NoSuchEntityException(objectKey); } db2.commit(); } Long unencryptedSize = foundImageCacheInfo.getSize(); String imageKey = foundImageCacheInfo.getImageName(); reply.setSize(unencryptedSize); reply.setLastModified(DateUtils.format(objectInfo.getLastModified().getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); reply.setEtag(""); DefaultHttpResponse httpResponse = new DefaultHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.OK ); storageManager.sendObject(request, httpResponse, bucketName, imageKey, unencryptedSize, null, DateUtils.format(objectInfo.getLastModified().getTime(), DateUtils.ISO8601_DATETIME_PATTERN + ".000Z"), objectInfo.getContentType(), objectInfo.getContentDisposition(), request.getIsCompressed(), null, null); semaphore.release(); return reply; } else { db.rollback(); throw new AccessDeniedException("Key", objectKey); } } else { db.rollback(); throw new NoSuchEntityException(objectKey); } } else { db.rollback(); throw new NoSuchBucketException(bucketName); } } public CheckImageResponseType checkImage(CheckImageType request) throws EucalyptusCloudException { CheckImageResponseType reply = (CheckImageResponseType) request.getReply(); reply.setSuccess(false); String bucketName = request.getBucket(); String objectKey = request.getKey(); String userId = request.getUserId(); EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); BucketInfo bucket = null; try { bucket = db.getUnique(bucketInfo); } catch(Throwable t) { throw new EucalyptusCloudException("Unable to get bucket", t); } if (bucket != null) { EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, objectKey); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if(objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if(objectInfo.canRead(userId)) { db.commit(); checkManifest(bucketName, objectKey, userId); reply.setSuccess(true); return reply; } else { db.rollback(); throw new AccessDeniedException("Key", objectKey); } } else { db.rollback(); throw new NoSuchEntityException(objectKey); } } else { db.rollback(); throw new NoSuchBucketException(bucketName); } } public CacheImageResponseType cacheImage(CacheImageType request) throws EucalyptusCloudException { CacheImageResponseType reply = (CacheImageResponseType) request.getReply(); reply.setSuccess(false); String bucketName = request.getBucket(); String manifestKey = request.getKey(); String userId = request.getUserId(); EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); List<BucketInfo> bucketList = db.query(bucketInfo); if (bucketList.size() > 0) { EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, manifestKey); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if(objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if(objectInfo.canRead(userId)) { EntityWrapper<ImageCacheInfo> db2 = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); List<ImageCacheInfo> foundImageCacheInfos = db2.query(searchImageCacheInfo); db2.commit(); if((foundImageCacheInfos.size() == 0) || (!imageCachers.containsKey(bucketName + manifestKey))) { cacheImage(bucketName, manifestKey, userId, request.isAdministrator()); reply.setSuccess(true); } db.commit( ); return reply; } else { db.rollback(); throw new AccessDeniedException("Key", manifestKey); } } else { db.rollback( ); throw new NoSuchEntityException(manifestKey); } } else { db.rollback( ); throw new NoSuchBucketException(bucketName); } } public FlushCachedImageResponseType flushCachedImage(FlushCachedImageType request) throws EucalyptusCloudException { FlushCachedImageResponseType reply = (FlushCachedImageResponseType) request.getReply(); String bucketName = request.getBucket(); String manifestKey = request.getKey(); EntityWrapper<ImageCacheInfo> db = WalrusControl.getEntityWrapper(); ImageCacheInfo searchImageCacheInfo = new ImageCacheInfo(bucketName, manifestKey); List<ImageCacheInfo> foundImageCacheInfos = db.query(searchImageCacheInfo); if(foundImageCacheInfos.size() > 0) { ImageCacheInfo foundImageCacheInfo = foundImageCacheInfos.get(0); if(foundImageCacheInfo.getInCache() && (imageCachers.get(bucketName + manifestKey) == null)) { //check that there are no operations in progress and then flush cache and delete image file db.commit(); ImageCacheFlusher imageCacheFlusher = new ImageCacheFlusher(bucketName, manifestKey); imageCacheFlusher.start(); } else { db.rollback(); throw new EucalyptusCloudException("not in cache"); } } else { db.rollback(); throw new NoSuchEntityException(bucketName + manifestKey); } return reply; } public ValidateImageResponseType validateImage(ValidateImageType request) throws EucalyptusCloudException { ValidateImageResponseType reply = (ValidateImageResponseType) request.getReply(); String bucketName = request.getBucket(); String manifestKey = request.getKey(); String userId = request.getUserId(); EntityWrapper<BucketInfo> db = WalrusControl.getEntityWrapper(); BucketInfo bucketInfo = new BucketInfo(bucketName); List<BucketInfo> bucketList = db.query(bucketInfo); if (bucketList.size() > 0) { BucketInfo bucket = bucketList.get(0); BucketLogData logData = bucket.getLoggingEnabled() ? request .getLogData() : null; ObjectInfo searchObjectInfo = new ObjectInfo(bucketName, manifestKey); searchObjectInfo.setDeleted(false); EntityWrapper<ObjectInfo> dbObject = db.recast(ObjectInfo.class); List<ObjectInfo> objectInfos = dbObject.query(searchObjectInfo); if (objectInfos.size() > 0) { ObjectInfo objectInfo = objectInfos.get(0); if (objectInfo.canRead(userId)) { //validate manifest validateManifest(bucketName, manifestKey, userId); db.commit(); } else { db.rollback(); throw new AccessDeniedException("Key", manifestKey, logData); } } else { db.rollback(); throw new NoSuchEntityException(manifestKey, logData); } } else { db.rollback(); throw new NoSuchBucketException(bucketName); } return reply; } }