Déploiement et configuration réseau (firewall) d'une instance CloudStack avec Apache préinstallé

Ikoula

CloudStack est un logiciel de cloud computing open source pour la création, la gestion et le déploiement de services de cloud d'infrastructure. Il utilise des hyperviseurs existants tels que KVM, vSphere, XenServer et / XCP pour la virtualisation. En plus de sa propre API, CloudStack prend également en charge les Amazon Web Services. Voici une série de tests effectués par Ikoula sur ce logiciel.

N'hésitez pas à commenter cet article ! Commentez

- Titre : Déploiement et configuration réseau (firewall) d'une instance CloudStack avec Apache préinstallé

- Auteur : Ikoula

- Parution : 7 octobre 2013

- Licence : Copyright ® 2013 Ikoula. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Déploiement de l'API :

- II. Remerciements Developpez

I - Déploiement de l'API :

<?php

// Liste complète des appels possibles : http://download.cloud.com/releases/3.0.6/api_3.0.6/TOC_User.html

################################
# Paramètres généraux de l'API #
################################

// Renseignez ici votre clef API ainsi que votre clef secrète
// Nous conseillons de déporter cette information au sein d'un autre fichier PHP dont on restreindra l'accès
define("APIKEY","&lt;VOTRE CLEF API>");
define("SECRETKEY","&lt;VOTRE CLEF SECRETE>");
//On définit l'URL d'appel de l'API (ou 'EndPoint')
define("ENDPOINT","https://cloudstack.ikoula.com/client/api");

#############################################################
# Paramètres utilisateur de configuration d(es) instance(s) #
#############################################################

//Première instance :
/* UUID(s) du réseau auquel sera connectée votre instance.
Utilisez la requête API 'listNetworks' ou l'interface pour lister les réseaux existants et déterminer le réseau voulu
*/
$vm01['conf']['networkid'] = "&lt;ID DU RESEAU QUE VOUS SOUHAITEZ UTILISER>";
/* UUID de l'offre de service (configuration matérielle) voulue.
Utilisez la requête API 'listServiceOfferings' pour lister les offres existantes.
Actuellement :
- m1.small : c6b89fea-1242-4f54-b15e-9d8ec8a0b7e8
- m1.medium : 8dae8be9-5dae-4f81-89d1-b171f25ef3fd
- m1.large : d2b2e7b9-4ffa-419e-9ef1-6d413f08deab
- m1.extralarge : 1412009f-0e89-4cfc-a681-1cda0631094b
*/
$vm01['conf']['serviceofferingid'] = "&lt;ID DE L'OFFRE DE SERVICE A UTILISER>";
/* UUID du modèle de systeme d'exploitation.
Vous pouvez aussi utiliser la requête API 'listTemplates' pour lister les modèles existants.
Dans notre exemple nous allons nous baser sur le template 'Debian 7 - Minimal - 64bits'
*/
$vm01['conf']['templateid'] = "718952be-d4f5-408c-991b-f211ee4f420b";
/* Nom de votre choix pour l'instance (Nom de la machine et d'affichage dans l'interface)
Caractères alphanumériques uniquement. Ce nom doit être unique au sein de votre réseau. */
$vm01['conf']['hostname'] = "&lt;NOM DE VOTRE INSTANCE>";
/* Données utilisateur a passer au processus de déploiement de l'instance.
Ce paramètre nous sert à passer l'URL du script de déploiement du module "Apache" à "Puppet".
Il est vous est tout à fait possible de créer votre propre script Puppet et de le rendre disponible en HTTP. */
$vm01['conf']['userdata'] = "http://puppetrepository.ikoula.com/apache/install.sh";
/* ------------------------------ NE PLUS RIEN MODIFIER APRES CETTE LIGNE ----------------------------------- */
$json_response = null;

################################################
# Récupération de l'ID de zone pour ce network #
################################################

// Requête API
$args['command'] = "listNetworks";
// On reprend l'ID de réseau renseigné plus tôt
$args['id'] = $vm01['conf']['networkid'];
// Execution de la requête
sendRequest($args, $json_response);
// Stockage de l'ID de zone pour ce réseau
$vm01['conf']['zoneid'] = $json_response['listnetworksresponse']["network"][0]['zoneid'];
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

######################################################
# Récupération de l'id de l'adresse IP de ce network #
######################################################

// Requête API
$args['command'] = "listPublicIpAddresses";
$args['associatednetworkid'] = $vm01['conf']['networkid'];
// Execution de la requête
sendRequest($args, $json_response);
// Stockage de l'ID de l'adresse IP publique du réseau
$vm01['conf']['publicIPid'] = $json_response['listpublicipaddressesresponse']["publicipaddress"][0]['id'];
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

####################################
# Création de la première instance #
####################################

// Requête API
$args['command'] = "deployVirtualMachine";
$args['zoneid'] = $vm01['conf']['zoneid'];
$args['serviceofferingid'] = $vm01['conf']['serviceofferingid'];
$args['templateid'] = $vm01['conf']['templateid'];
$args['networkids'] = $vm01['conf']['networkid'];
$args['name'] = $vm01['conf']['hostname']; //Hostname
$args['displayname'] = $args['name']; //Nom d'affichage
$args['userdata'] = base64_encode($vm01['conf']['userdata']); // Données utilisateur
//Type de retour : JSON ou XML (par défaut : XML)
$args['response'] = "json";
//On réinitialise les variables avant l'appel API
// Initialisation du client API
sendRequest($args, $json_response);
//On vérifie la presence d'un Job
if(preg_match("/^[0-9a-f\-]+$/", $json_response['deployvirtualmachineresponse']['jobid']) > 0)
{
$jobs[] = $json_response['deployvirtualmachineresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
//On mémorise la correspondance name/id de l'instance au sein d'un tableau
$vm01['conf']['id'] = $json_response['deployvirtualmachineresponse']['id'];
//On utilise la fonction de vérification des jobs asynchrones
if(!checkJobs($jobs))
exit;
/*-------------------------------- Création des règles de sortie --------------------------------*/
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

################
# Ping sortant #
################

$args['command'] = "createEgressFirewallRule";
$args['networkid'] = $vm01['conf']['networkid'];
// Définition du protocole a filtrer ici ICMP (utiliser pour les ping), les protocole sont ICMP/UDP/TCP
$args['protocol'] = "ICMP";
// Définition du masque réseau qui nous permet d'autoriser certainne IP,
// ici nous acceptons "le ping" vers toute les IPs
$args['cidrlist'] = "0.0.0.0/0";
// On définit le type ICMP le code 8 et une demande d'ECHO (echo-request) cela permet le ping
$args['icmptype'] = 8;
// On définit le code ICMP le code 0 doit être défini à 0 pour l'utilisation du type 8 pour l'ICMP
$args['icmpcode'] = 0;
// Execution de la requête
sendRequest($args, $json_response);
// Traitement de la requête
if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
{
$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
if(!checkJobs($jobs))
exit;
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

##########################
# Protocole HTTP sortant #
##########################

$args['command'] = "createEgressFirewallRule";
$args['networkid'] = $vm01['conf']['networkid'];
// Définition du protocole a filtrer ici TCP (utiliser par le protocole HTTP), les protocole sont ICMP/UDP/TCP
$args['protocol'] = "TCP";
// Définition du masque réseau qui nous permet d'autoriser certainne IP,
// ici nous accepton la connection vers toute les IP
$args['cidrlist'] = "0.0.0.0/0";
// Le port de début (port 80 port standart pour le HTTP)
$args['startport'] = 80;
// Le port de fin (port 80 port standart pour le HTTP)
$args['endport'] = 80;
// Execution de la requête
sendRequest($args, $json_response);
// Traitement de la requête
if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
{
$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

###########################
# Protocole HTTPS sortant #
###########################

// Requête API
$args['command'] = "createEgressFirewallRule";
$args['networkid'] = $vm01['conf']['networkid'];
// Définition du protocole a filtrer ici TCP (utiliser par le protocole HTTPS), les protocole sont ICMP/UDP/TCP
$args['protocol'] = "TCP";
// Définition du masque réseau qui nous permet d'autoriser certainne IP,
// ici nous accepton la connection vers toute les IP
$args['cidrlist'] = "0.0.0.0/0";
// Le port de début (port 443 port standart pour le HTTPs)
$args['startport'] = 443;
// Le port de fin (port 443 port standart pour le HTTPs)
$args['endport'] = 443;
// Execution de la requête
sendRequest($args, $json_response);
// Traitement de la requête
if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
{
$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

#########################
# Protocole DNS sortant #
#########################

// Requête API
$args['command'] = "createEgressFirewallRule";
$args['networkid'] = $vm01['conf']['networkid'];
// Définition du protocole a filtrer ici UDP (utiliser par le protocole DNS), les protocole sont ICMP/UDP/TCP
$args['protocol'] = "UDP";
// Définition du masque réseau qui nous permet d'autoriser certainne IP,
// ici nous accepton la connection vers toute les IP
$args['cidrlist'] = "0.0.0.0/0";
// Le port de début (port 443 port standart pour le HTTPs)
$args['startport'] = 53;
// Le port de fin (port 443 port standart pour le HTTPs)
$args['endport'] = 53;
// Execution de la requête
sendRequest($args, $json_response);
// Traitement de la requête
if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
{
$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

########################################
# Ouverture du port public pour le SSH #
########################################

// Requête API
$args['command'] = "createFirewallRule";
$args['ipaddressid'] = $vm01['conf']['publicIPid'];
// Définition du protocole a filtrer ici TCP (utiliser par le protocole SSH), les protocole sont ICMP/UDP/TCP
$args['protocol'] = "TCP";
// Définition du masque réseau qui nous permet d'autoriser certainne IP,
// ici nous accepton la connection vers toute les IP
$args['cidrlist'] = "0.0.0.0/0";
// Le port de début (port 2222 port qui sera plus tard rediriger vers le port 22(ssh) de votre VM)
$args['startport'] = 2222;
// Le port de fin (port 2222 port qui sera plus tard rediriger vers le port 22(ssh) de votre VM)
$args['endport'] = 2222;
// Execution de la requête
sendRequest($args, $json_response);
// Traitement de la requête
if(preg_match("/^[0-9a-f\-]+$/", $json_response['createfirewallruleresponse']['jobid']) > 0)
{
$jobs[] = $json_response['createfirewallruleresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);

###################################
# Redirection de port pour le SSH #
###################################

// Requête API
$args['command'] = "createPortForwardingRule";
$args['ipaddressid'] = $vm01['conf']['publicIPid'];
$args['virtualmachineid'] = $vm01['conf']['id'];
// Définition du protocole a filtrer ici TCP (utiliser par le protocole SSH), les protocole sont ICMP/UDP/TCP
$args['protocol'] = "TCP";
// Le Port public sur lequels vous allez initialiser la connection et définie plus tôt comme ouvert
$args['publicport'] = 2222;
// Le SSH de votre VM
$args['privateport'] = 22;
// Execution de la requête
sendRequest($args, $json_response);
// Traitement de la requête
if(preg_match("/^[0-9a-f\-]+$/", $json_response['createportforwardingruleresponse']['jobid']) > 0)
{
$jobs[] = $json_response['createportforwardingruleresponse']['jobid'];
}
else{
echo "ID de job non trouvé.\n";
}
// On réinitialise les variables utilisées pour l'envoi de requêtes API
unset($args);
if(!checkJobs($jobs))
exit;
/*-----------------------------------------------------------------------------------------------------------*/

#############
# Fonctions #
#############

//Fonction de gestion d'erreur(s) API
function apiErrorCheck($json_response)
{
if(is_array($json_response))
{
$key = array_keys($json_response);
if(isset($json_response['errorcode']))
{
echo "ERREUR : ".$json_response['errorcode']." - ".$json_response['errortext']."\n";
exit;
}
if(isset($json_response['errorcode']) || (isset($key[0]) &amp;&amp; isset($json_response[$key[0]]['errorcode'])))
{
echo "ERREUR : ".$json_response[$key[0]]['errorcode']." - ".$json_response[$key[0]]['errortext']."\n";
exit;
}
}
else
{
echo "ERREUR : PARAMETRE INVALIDE";
exit;
}
}
//Fonction d'envoi de requête à l'API
function sendRequest($args, &amp;$json_response)
{
$json_response = null;
// Clef API
$args['apikey'] = APIKEY;
$args['response'] = "json";
//On classe les paramètres
ksort($args);
// On construit la requête HTTP basée sur les paramètres contenus dans $args
$query = http_build_query($args);
// On s'assure de bien remplacer toutes les occurences de '+' par des ' '
$query = str_replace("+", " ", $query);
//On utilise la clef secrète et un algorithme HMAC SHA-1 sur la requête pour encoder la signature
$hash = hash_hmac("SHA1", strtolower($query), SECRETKEY, true);
$base64encoded = base64_encode($hash);
$signature = urlencode($base64encoded);
// Construction de la requête finale sous la forme 'URL API + Requête API et paramètres + Signature'
$query .= "&amp;signature=" . $signature;
// $jobs = null;
// Initialisation du client API
try
{
//Construction de la requête
$httpRequest = new HttpRequest();
$httpRequest->setMethod(HTTP_METH_POST);
$httpRequest->setUrl(ENDPOINT . "?" . $query);
// Envoi de la requête au serveur :
$httpRequest->send();
// Récupération du retour de l'API
$response = $httpRequest->getResponseData();
// retour de la réponse
$json_response = json_decode($response['body'], true);
apiErrorCheck($json_response);
}
catch (Exception $e)
{
echo "Probleme lors de l'envoi de la requête. ERREUR=".$e->getMessage();
exit;
}
}
//Fonction de vérification des jobs asynchrones
function checkJobs($jobs)
{
$json_response = null;
$error_msg = "";
if(is_array($jobs) &amp;&amp; count($jobs) > 0)
{
// La tâche est asynchrone, on doit donc régulièrement vérifier les tâches avec une sécurité
$secu = 0;
// On indexe les tâches
$ij = 0;
// Tant qu'il y a des tâches asynchrones non-terminées dans la pile de vérification, on boucle et on vérifie le statut
while(count($jobs) > 0 &amp;&amp; $secu &lt; 100)
{
try
{
//On interroge le statut de la tâche asynchrone
// http://download.cloud.com/releases/3.0.6/api_3.0.6/root_admin/queryAsyncJobResult.html
$args['apikey'] = APIKEY;
$args['command'] = "queryAsyncJobResult";
$args['jobid'] = $jobs[$ij];
$args['response'] = "json";
$json_response = null;
sendRequest($args, $json_response);
if(is_array($json_response['queryasyncjobresultresponse']))
{
// Si OK&#8230;
if($json_response['queryasyncjobresultresponse']['jobstatus'] == 1)
{
// &#8230;On retire simplement la tâche du tableau a surveiller
//return("JOB OK\n");
array_splice($jobs, $ij, 1);
}
// Sinon&#8230;
elseif($json_response['queryasyncjobresultresponse']['jobstatus'] == 2)
{
//&#8230;On mémorise l'erreur et on retire la tâche du tableau à surveiller
//return("JOB ERREUR\n");
array_splice($jobs, $ij, 1);
$error_msg .= "ERREUR ! RESULT_CODE=".$json_response['queryasyncjobresultresponse']['jobresultcode'];
}
// Cette tâche est encore en cours, on passe à la suivante et on temporise
elseif($json_response['queryasyncjobresultresponse']['jobstatus'] == 0)
{
// Tâche suivante
$ij++;
// Temporisation entre chaque interrogation pour ne pas charger inutilement l'API
sleep(5);
}
}
}
catch(Exception $e)
{
$error_msg .= "EXCEPTION Lors de la verification la tache asynchrone. JOB_UUID:".$jobs[$ij]." ERREUR=".$e->getMessage()." \n";
}
// Si l'index arrive en bout de tableau, on le réinitialise
if($ij == count($jobs))
{
$ij = 0;
$secu++;
}
}
if($error_msg)
{
echo "ERRORS:".$error_msg."\n";
return false;
}
return true;
}
echo "No job\n";
return false;
}
?>

Cet article vous montre la simplicité d'utilisation de l'applicatif Puppet qui permet l'installation et la configuration de services sur des instances GNU/Linux.

Nous couplons donc les possibilités des deux APIs (CloudStack et Puppet) afin que CloudStack transmette, lors du déploiement de votre instance, l'adresse d'un script qui sera exécuté sur votre instance.

Ce script a pour fonction de rapatrier fichiers du module Puppet correspondant et de les passer à Puppet pour installation.

Ce dernier est d'ailleurs consultable ici :

http://puppetrepository.ikoula.com/apache/install.sh

Comme vous pourrez le constater si vous avez eu la curiosité de cliquer sur ce lien, le script est assez basique puisqu'il indique seulement à l'installation de Puppet présente dans votre instance d'installer le module « apache » qui a été rapatrié dans un répertoire « /etc/puppet/ikoula ».

Il vous est tout à fait possible de créer vos propres scripts via des documentations que nous fournirons à l'avenir puisque vous avez la main sur l'adresse du script que vous renseignez lors du déploiement de l'instance via l'API.

II - Remerciements Developpez

L'équipe Cloud Computing tient à remercier Ikoula pour la rédaction de ce tutoriel.

Nos remerciements à Wiztricks pour sa relecture technique et Zoom61 pour sa gabarisation.

N'hésitez pas à commenter cet article ! Commentez