package com.eucalyptus.cluster; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URI; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.List; import java.util.NoSuchElementException; import org.apache.log4j.Logger; import com.eucalyptus.auth.Authentication; import com.eucalyptus.auth.ClusterCredentials; import com.eucalyptus.auth.Groups; import com.eucalyptus.auth.SystemCredentialProvider; import com.eucalyptus.auth.X509Cert; import com.eucalyptus.auth.crypto.Certs; import com.eucalyptus.auth.crypto.Hmacs; import com.eucalyptus.auth.principal.Authorization; import com.eucalyptus.auth.principal.AvailabilityZonePermission; import com.eucalyptus.auth.principal.Group; import com.eucalyptus.auth.util.PEMFiles; import com.eucalyptus.bootstrap.Component; import com.eucalyptus.component.Components; import com.eucalyptus.component.DatabaseServiceBuilder; import com.eucalyptus.component.DiscoverableServiceBuilder; import com.eucalyptus.component.ServiceConfiguration; import com.eucalyptus.component.ServiceConfigurations; import com.eucalyptus.component.ServiceRegistrationException; import com.eucalyptus.config.ClusterConfiguration; import com.eucalyptus.config.Configuration; import com.eucalyptus.config.Handles; import com.eucalyptus.config.RemoteConfiguration; import com.eucalyptus.entities.EntityWrapper; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; import com.eucalyptus.system.SubDirectory; import com.eucalyptus.util.EucalyptusCloudException; import edu.ucsb.eucalyptus.msgs.DeregisterClusterType; import edu.ucsb.eucalyptus.msgs.DescribeClustersType; import edu.ucsb.eucalyptus.msgs.RegisterClusterType; @DiscoverableServiceBuilder( com.eucalyptus.bootstrap.Component.cluster ) @Handles( { RegisterClusterType.class, DeregisterClusterType.class, DescribeClustersType.class } ) public class ClusterBuilder extends DatabaseServiceBuilder<ClusterConfiguration> { private static Logger LOG = Logger.getLogger( ClusterBuilder.class ); @Override public ClusterConfiguration newInstance( ) { return new ClusterConfiguration( ); } @Override public ClusterConfiguration newInstance( String name, String host, Integer port ) { return new ClusterConfiguration( name, host, port ); } @Override public com.eucalyptus.component.Component getComponent( ) { return Components.lookup( Component.cluster ); } @Override public Boolean checkAdd( String name, String host, Integer port ) throws ServiceRegistrationException { if ( !testClusterCredentialsDirectory( name ) ) { throw new ServiceRegistrationException( "Cluster registration failed because the key directory cannot be created." ); } else { return super.checkAdd( name, host, port ); } } @Override public ClusterConfiguration add( String name, String host, Integer port ) throws ServiceRegistrationException { ClusterConfiguration config = this.newInstance( name, host, port ); removeCredentials( config ); try { /** generate the Component keys **/ addCredentials( config ); addAuthorizations( config ); ServiceConfigurations.getInstance( ).store( config ); } catch ( EucalyptusCloudException e ) { throw new ServiceRegistrationException( e.getMessage( ), e ); } return config; } @Override public void fireStart( ServiceConfiguration config ) throws ServiceRegistrationException { EventRecord.here( ClusterBuilder.class, EventType.COMPONENT_SERVICE_START, config.getComponent( ).name( ), config.getName( ), config.getUri( ) ).info( ); try { if ( Components.lookup( Components.delegate.eucalyptus ).isLocal( ) ) { try { Clusters.start( ( ClusterConfiguration ) config ); } catch ( EucalyptusCloudException ex ) { LOG.error( ex, ex ); throw new ServiceRegistrationException( "Registration failed: " + ex.getMessage( ), ex ); } super.fireStart( config ); } } catch ( NoSuchElementException ex ) { LOG.error( ex, ex ); } } @Override public Boolean checkRemove( String name ) throws ServiceRegistrationException { try { Configuration.getStorageControllerConfiguration( name ); throw new ServiceRegistrationException( "Cannot deregister a cluster controller when there is a storage controller registered." ); } catch ( EucalyptusCloudException e ) { return true; } } @Override public ClusterConfiguration remove( ServiceConfiguration removeConfig ) throws ServiceRegistrationException { ClusterConfiguration config = ( ClusterConfiguration ) removeConfig; removeAuthorizations( config ); removeKeys( config ); removeCredentials( config ); ServiceConfigurations.getInstance( ).remove( config ); return ( ClusterConfiguration ) config; } @Override public void fireStop( ServiceConfiguration config ) throws ServiceRegistrationException { Clusters.stop( config.getName( ) ); super.fireStop( config ); } @Override public ServiceConfiguration toConfiguration( URI uri ) throws ServiceRegistrationException { return new RemoteConfiguration( this.getComponent( ).getPeer( ), uri ); } private static void addAuthorizations( ClusterConfiguration config ) { Groups.DEFAULT.addAuthorization( new AvailabilityZonePermission( config.getName( ) ) ); } private static void addCredentials( ClusterConfiguration config ) throws ServiceRegistrationException { String directory = SubDirectory.KEYS.toString( ) + File.separator + config.getName( ); File keyDir = new File( directory ); LOG.info( "creating keys in " + directory ); if ( !keyDir.mkdir( ) && !keyDir.exists( ) ) { throw new ServiceRegistrationException( "Failed to create cluster key directory: " + keyDir.getAbsolutePath( ) ); } else if ( !keyDir.canWrite( ) ) { throw new ServiceRegistrationException( "Cluster key directory is not writeable: " + keyDir.getAbsolutePath( ) ); } KeyPair clusterKp = Certs.generateKeyPair( ); X509Certificate clusterX509 = Certs.generateServiceCertificate( clusterKp, "cc-" + config.getName( ) ); KeyPair nodeKp = Certs.generateKeyPair( ); X509Certificate nodeX509 = Certs.generateServiceCertificate( nodeKp, "nc-" + config.getName( ) ); FileWriter out = null; try { PEMFiles.write( directory + File.separator + "cluster-pk.pem", clusterKp.getPrivate( ) ); PEMFiles.write( directory + File.separator + "cluster-cert.pem", clusterX509 ); PEMFiles.write( directory + File.separator + "node-pk.pem", nodeKp.getPrivate( ) ); PEMFiles.write( directory + File.separator + "node-cert.pem", nodeX509 ); X509Certificate systemX509 = SystemCredentialProvider.getCredentialProvider( Component.eucalyptus ).getCertificate( ); String hexSig = Hmacs.generateSystemToken( "vtunpass".getBytes( ) ); PEMFiles.write( directory + File.separator + "cloud-cert.pem", systemX509 ); out = new FileWriter( directory + File.separator + "vtunpass" ); out.write( hexSig ); out.flush( ); out.close( ); } catch ( IOException ex ) { LOG.error( ex, ex ); throw new ServiceRegistrationException( "Failed to store cluster keys: " + ex.getMessage( ), ex ); } finally { if ( out != null ) { try { out.close( ); } catch ( IOException e ) { LOG.error( e ); } } } EntityWrapper<ClusterCredentials> credDb = Authentication.getEntityWrapper( ); try { ClusterCredentials componentCredentials = new ClusterCredentials( config.getName( ) ); try { componentCredentials = credDb.getUnique( componentCredentials ); componentCredentials.setClusterCertificate( X509Cert.fromCertificate( clusterX509 ) ); componentCredentials.setNodeCertificate( X509Cert.fromCertificate( nodeX509 ) ); credDb.merge( componentCredentials ); } catch ( Exception ex ) { componentCredentials.setClusterCertificate( X509Cert.fromCertificate( clusterX509 ) ); componentCredentials.setNodeCertificate( X509Cert.fromCertificate( nodeX509 ) ); credDb.add( componentCredentials ); } credDb.commit( ); } catch ( Exception e ) { LOG.error( e, e ); credDb.rollback( ); } } private static void removeAuthorizations( ServiceConfiguration config ) { for ( Group g : Groups.listAllGroups( ) ) { for ( Authorization auth : g.getAuthorizations( ) ) { if ( auth instanceof AvailabilityZonePermission && config.getName( ).equals( auth.getValue( ) ) ) { g.removeAuthorization( auth ); } } } } private static void removeCredentials( ClusterConfiguration config ) { EntityWrapper<ClusterCredentials> credDb = EntityWrapper.get( ClusterCredentials.class ); try { for ( ClusterCredentials ccert : credDb.query( new ClusterCredentials( ) ) ) { LOG.debug( "Checking cluster certificate: " + ccert.getClusterName( ) + "\n" + X509Cert.toCertificate( ccert.getClusterCertificate( ) ) + "\n" + X509Cert.toCertificate( ccert.getNodeCertificate( ) ) ); if( config.getName( ).equals( ccert.getClusterName( ) ) ) { credDb.recast( X509Cert.class ).delete( ccert.getClusterCertificate( ) ); credDb.recast( X509Cert.class ).delete( ccert.getNodeCertificate( ) ); credDb.delete( ccert ); LOG.debug( "Deleting cluster certificate: " + ccert.getClusterName( ) + "\n" + X509Cert.toCertificate( ccert.getClusterCertificate( ) ) + "\n" + X509Cert.toCertificate( ccert.getNodeCertificate( ) ) ); } } credDb.commit( ); } catch ( Exception e ) { LOG.error( e, e ); credDb.rollback( ); } } private static synchronized void removeKeys( ServiceConfiguration config ) { String directory = SubDirectory.KEYS.toString( ) + File.separator + config.getName( ); File keyDir = new File( directory ); if ( keyDir.exists( ) ) { for ( File f : keyDir.listFiles( ) ) { if ( f.delete( ) ) { LOG.info( "Removing cluster key file: " + f.getAbsolutePath( ) ); } else { LOG.info( "Failed to remove cluster key file: " + f.getAbsolutePath( ) ); } } if ( keyDir.delete( ) ) { LOG.info( "Removing cluster key directory: " + keyDir.getAbsolutePath( ) ); } else { LOG.info( "Failed to remove cluster key directory: " + keyDir.getAbsolutePath( ) ); } } } static boolean testClusterCredentialsDirectory( String name ) { String directory = SubDirectory.KEYS.toString( ) + File.separator + name; File keyDir = new File( directory ); if ( !keyDir.exists( ) ) { try { return keyDir.mkdir( ) && keyDir.canWrite( ); } catch ( Exception e ) { return false; } } else { return keyDir.canWrite( ); } } }