/************************************************************************* * Copyright 2009-2015 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; version 3 of the License. * * This program 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., 6755 Hollister Ave., Goleta * CA 93117, 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 com.eucalyptus.images; import java.io.ByteArrayInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.xml.parsers.DocumentBuilder; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.compute.common.internal.images.ImageInfo; import com.eucalyptus.objectstorage.client.EucaS3Client; import com.eucalyptus.objectstorage.client.EucaS3ClientFactory; import org.apache.log4j.Logger; import org.bouncycastle.util.encoders.Base64; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.eucalyptus.auth.Accounts; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.principal.User; import com.eucalyptus.compute.ClientComputeException; import com.eucalyptus.compute.common.CloudMetadatas; import com.eucalyptus.compute.common.ImageMetadata; import com.eucalyptus.compute.common.ImageMetadata.DeviceMappingType; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.RestrictedTypes; import com.eucalyptus.util.XMLParser; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; public class ImageManifests { private static Logger LOG = Logger.getLogger( ImageManifests.class ); static String requestManifestData( String bucketName, String objectName ) throws EucalyptusCloudException { try { try ( final EucaS3Client s3Client = EucaS3ClientFactory.getEucaS3ClientForUser( Accounts.lookupSystemAccountByAlias( AccountIdentifiers.AWS_EXEC_READ_SYSTEM_ACCOUNT ), (int)TimeUnit.MINUTES.toSeconds( 15 )) ) { return s3Client.getObjectContent( bucketName, objectName, ImageConfiguration.getInstance( ).getMaxManifestSizeBytes( ) ); } } catch ( Exception e ) { LOG.error("Can't read manifest due to: " + e); throw new EucalyptusCloudException( "Failed to read manifest file: " + bucketName + "/" + objectName, e ); } } public static class ManifestDeviceMapping { ManifestDeviceMapping( DeviceMappingType type, String virtualName, String deviceName ) { super( ); this.type = type; this.virtualName = virtualName; this.deviceName = deviceName; } DeviceMappingType type; String virtualName; String deviceName; } public static class ImageManifest { private final String imageLocation; private final ImageMetadata.Architecture architecture; private final String kernelId; private final String ramdiskId; private final ImageMetadata.Type imageType; private final ImageMetadata.Platform platform; private final ImageMetadata.VirtualizationType virtualizationType; private final String signature; private final String checksum; private final String checksumType; private final String manifest; private final Document inputSource; private final String name; private final Long size; private final Long bundledSize; private final List<String> ancestors = Lists.newArrayList( ); private XPath xpath; private Function<String, String> xpathHelper; private String encryptedKey; private String encryptedIV; private String userId; private List<ManifestDeviceMapping> deviceMappings = Lists.newArrayList( ); ImageManifest( @Nonnull final String imageLocation, @Nullable final User user ) throws EucalyptusCloudException { ManifestLocation mLoc = new ManifestLocation( imageLocation ); this.imageLocation = mLoc.cleanLocation; String bucketName = mLoc.bucketName; String manifestKey = mLoc.manifestKey; final String manifestName = manifestKey.replaceAll( ".*/", "" ); //GRZE:TODO: restore this ACL check // if ( !ImageManifests.verifyBucketAcl( bucketName ) ) { // throw new EucalyptusCloudException( "Image registration failed: you must own the bucket containing the image." ); // } this.xpath = XPathFactory.newInstance( ).newXPath( ); this.xpathHelper = new Function<String, String>( ) { @Override public String apply( String input ) { try { return ( String ) ImageManifest.this.xpath.evaluate( input, ImageManifest.this.inputSource, XPathConstants.STRING ); } catch ( XPathExpressionException ex ) { return null; } } }; this.encryptedKey = this.xpathHelper.apply( "//ec2_encrypted_key" ); this.encryptedIV = this.xpathHelper.apply( "//ec2_encrypted_iv" ); Predicate<ImageMetadata.Type> checkIdType = new Predicate<ImageMetadata.Type>( ) { @Override public boolean apply( ImageMetadata.Type input ) { final String type = ImageManifest.this.xpathHelper.apply( "/manifest/image/type/text()" ); if ( type != null && type.equals( input.name( ) ) ) { return true; } String value = ImageManifest.this.xpathHelper.apply( input.getManifestPath( ) ); return "yes".equals( value ) || "true".equals( value ) || manifestName.startsWith( input.getNamePrefix() ); } }; if ( checkIdType.apply( ImageMetadata.Type.kernel ) && user != null && !user.isSystemAdmin( ) ) { throw new EucalyptusCloudException( "Only administrators can register kernel images." ); } else if ( checkIdType.apply( ImageMetadata.Type.ramdisk ) && user != null && !user.isSystemAdmin( ) ) { throw new EucalyptusCloudException( "Only administrators can register ramdisk images." ); } this.manifest = ImageManifests.requestManifestData( bucketName, manifestKey ); try { DocumentBuilder builder = XMLParser.getDocBuilder( ); this.inputSource = builder.parse( new ByteArrayInputStream( this.manifest.getBytes( ) ) ); } catch ( Exception e ) { throw new EucalyptusCloudException( "Failed to read manifest file: " + bucketName + "/" + manifestKey, e ); } String temp; this.name = ( ( temp = this.xpathHelper.apply( "/manifest/image/name/text()" ) ) != null ) ? temp : manifestName.replace( ".manifest.xml", "" ); this.checksum = ( ( temp = this.xpathHelper.apply( "/manifest/image/digest/text()" ) ) != null ) ? temp : "0000000000000000000000000000000000000000"; this.checksumType = ( ( temp = this.xpathHelper.apply( "/manifest/image/digest/@algorithm" ) ) != null ) ? temp : "SHA1"; this.signature = ( ( temp = this.xpathHelper.apply( "//signature" ) ) != null ) ? temp : null; this.userId = ( ( temp = this.xpathHelper.apply( "//user" ) ) != null ) ? temp : null; String typeInManifest = this.xpathHelper.apply( ImageMetadata.TYPE_MANIFEST_XPATH ); this.size = ( ( temp = this.xpathHelper.apply( "/manifest/image/size/text()" ) ) != null ) ? Long.parseLong( temp ) : -1l; this.bundledSize = ( ( temp = this.xpathHelper.apply( "/manifest/image/bundled_size/text()" ) ) != null ) ? Long.parseLong( temp ) : -1l; String arch = this.xpathHelper.apply( "/manifest/machine_configuration/architecture/text()" ); this.architecture = ImageMetadata.Architecture.valueOf( ( ( arch == null ) ? "i386" : arch ) ); try { NodeList ancestorNodes = ( NodeList ) xpath.evaluate( "/manifest/image/ancestry/ancestor_ami_id/text()", inputSource, XPathConstants.NODESET ); if ( ancestorNodes != null ) { for ( int i = 0; i < ancestorNodes.getLength( ); i++ ) { for ( String ancestorId : ancestorNodes.item( i ).getNodeValue( ).split( "," ) ) { this.ancestors.add( ancestorId ); } } } } catch ( XPathExpressionException ex ) { LOG.error( ex, ex ); } try { NodeList devMapList = ( NodeList ) this.xpath.evaluate( "/manifest/machine_configuration/block_device_mapping/mapping", inputSource, XPathConstants.NODESET ); for ( int i = 0; i < devMapList.getLength( ); i++ ) { Node node = devMapList.item( i ); NodeList children = node.getChildNodes( ); String virtualName = null; String device = null; for ( int j = 0; j < children.getLength( ); j++ ) { Node childNode = children.item( j ); String nodeType = childNode.getNodeName( ); if ( "virtual".equals( nodeType ) && childNode.getTextContent( ) != null ) { virtualName = childNode.getTextContent( ); } else if ( "device".equals( nodeType ) && childNode.getTextContent( ) != null ) { device = childNode.getTextContent( ); } } if ( virtualName != null && device != null ) { if ( "ami".equals( virtualName ) ) { this.deviceMappings.add( new ManifestDeviceMapping( DeviceMappingType.ami, virtualName, device ) );; } else if ( "root".equals( virtualName ) ) { this.deviceMappings.add( new ManifestDeviceMapping( DeviceMappingType.root, virtualName, device ) ); } else if ( "swap".equals( virtualName ) ) { this.deviceMappings.add( new ManifestDeviceMapping( DeviceMappingType.swap, virtualName, device ) ); } else if ( virtualName.startsWith( "ephemeral" ) ) { this.deviceMappings.add( new ManifestDeviceMapping( DeviceMappingType.ephemeral, virtualName, device ) ); } } } } catch ( XPathExpressionException ex ) { LOG.error( ex, ex ); } if ( checkIdType.apply( ImageMetadata.Type.kernel ) ) { this.imageType = ImageMetadata.Type.kernel; this.platform = ImageMetadata.Platform.linux; this.virtualizationType = ImageMetadata.VirtualizationType.paravirtualized; this.kernelId = null; this.ramdiskId = null; } else if ( checkIdType.apply( ImageMetadata.Type.ramdisk ) ) { this.imageType = ImageMetadata.Type.ramdisk; this.platform = ImageMetadata.Platform.linux; this.virtualizationType = ImageMetadata.VirtualizationType.paravirtualized; this.kernelId = null; this.ramdiskId = null; } else { String kId = this.xpathHelper.apply( ImageMetadata.Type.kernel.getManifestPath( ) ); String rId = this.xpathHelper.apply( ImageMetadata.Type.ramdisk.getManifestPath( ) ); this.imageType = ImageMetadata.Type.machine; if ( !manifestName.startsWith( ImageMetadata.Platform.windows.toString( ) ) && !( kId != null && ImageMetadata.Platform.windows.name( ).equals( kId ) ) ) { this.platform = ImageMetadata.Platform.linux; this.virtualizationType = ImageMetadata.VirtualizationType.paravirtualized; if ( CloudMetadatas.isKernelImageIdentifier( kId ) ) { //TODO EUCA-3109 //ImageManifests.checkPrivileges( this.kernelId ); this.kernelId = kId; } else { this.kernelId = null; } if ( CloudMetadatas.isRamdiskImageIdentifier( rId ) ) { //TODO EUCA-3109 //ImageManifests.checkPrivileges( this.ramdiskId ); this.ramdiskId = rId; } else { this.ramdiskId = null; } } else { this.platform = ImageMetadata.Platform.windows; this.virtualizationType = ImageMetadata.VirtualizationType.hvm; this.kernelId = null; this.ramdiskId = null; } } } public String getSignature( ) { return this.signature; } public ImageMetadata.Platform getPlatform( ) { return this.platform; } public ImageMetadata.Architecture getArchitecture( ) { return this.architecture; } public String getKernelId( ) { return this.kernelId; } public List<ManifestDeviceMapping> getDeviceMappings() { return this.deviceMappings; } public String getAmi() { try { ManifestDeviceMapping root = Iterables.find(this.deviceMappings, new Predicate<ManifestDeviceMapping>() { @Override public boolean apply(ManifestDeviceMapping man) { return man.type == DeviceMappingType.ami; }}); return root.deviceName; } catch (NoSuchElementException ex) { return ""; } } public String getRoot() { try { ManifestDeviceMapping root = Iterables.find(this.deviceMappings, new Predicate<ManifestDeviceMapping>() { @Override public boolean apply(ManifestDeviceMapping man) { return man.type == DeviceMappingType.root; }}); return root.deviceName; } catch (NoSuchElementException ex) { return ""; } } public String getRamdiskId( ) { return this.ramdiskId; } public ImageMetadata.Type getImageType( ) { return this.imageType; } public String getImageLocation( ) { return this.imageLocation; } public String getManifest( ) { return this.manifest; } public String getName( ) { return this.name; } public String getAccountId( ) { return this.userId; } public Long getSize( ) { return this.size; } public Long getBundledSize( ) { return this.bundledSize; } public String getChecksum( ) { return this.checksum; } public String getChecksumType( ) { return this.checksumType; } public ImageMetadata.VirtualizationType getVirtualizationType( ) { return this.virtualizationType; } public static class ManifestLocation { public final String bucketName; public final String manifestKey; public final String cleanLocation; public ManifestLocation(String imageLocation) throws EucalyptusCloudException { cleanLocation = imageLocation.replaceAll( "^/*", "" ); int index = cleanLocation.indexOf( '/' ); if ( index < 2 || index + 1 >= cleanLocation.length( ) ) { throw new EucalyptusCloudException( "Invalid image location: " + imageLocation ); } bucketName = cleanLocation.substring( 0, index ); manifestKey = cleanLocation.substring( index + 1 ); } } } public static String getManifestHash( String manifestLocation ) throws EucalyptusCloudException { ImageManifest.ManifestLocation mLoc = new ImageManifest.ManifestLocation( manifestLocation ); String manifest = ImageManifests.requestManifestData( mLoc.bucketName, mLoc.manifestKey ); return calculateManifestHash( manifest ); } public static String calculateManifestHash( String content ) throws EucalyptusCloudException { try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(content.getBytes()); return Base64.toBase64String(messageDigest.digest()); } catch (NoSuchAlgorithmException e) { throw new EucalyptusCloudException("Can't load SHA-256 algorithm", e); } } public static ImageManifest lookup( String imageLocation ) throws EucalyptusCloudException { return new ImageManifest( imageLocation, null ); } /** * Lookup an ImageManifest, verifying permissions for the given user */ public static ImageManifest lookup( String imageLocation, User owner) throws EucalyptusCloudException { final ImageManifest manifest = new ImageManifest( imageLocation, owner ); try{ final String ownerAcctId = owner.getAccountNumber(); if(ownerAcctId.equals(manifest.getAccountId())) return manifest; }catch(final AuthException ex){ throw new ClientComputeException("AuthFailure","Manifest is not accessible"); } throw new ClientComputeException("AuthFailure","Manifest is not accessible"); } static void checkPrivileges( String diskId ) throws EucalyptusCloudException { Context ctx = Contexts.lookup( ); if ( diskId != null ) { ImageInfo disk = null; try { disk = Images.lookupImage( diskId ); } catch ( Exception ex ) { LOG.error( ex, ex ); throw new EucalyptusCloudException( "Referenced image id is invalid: " + diskId, ex ); } if ( !RestrictedTypes.filterPrivileged( ).apply( disk ) ) { throw new EucalyptusCloudException( "Access to " + disk.getImageType( ).toString( ) + " image " + diskId + " is denied for " + ctx.getUser( ).getName( ) ); } } } }