PROJET AUTOBLOG


Gordon

source: Gordon

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Rebirth

lundi 15 février 2016 à 00:00

Planquez (un peu) Proxmox

lundi 14 juillet 2014 à 00:00

Proxmox est un outil intéressant, simple à prendre en main, offrant le moyen de gérer un serveur de virtualisation. Je l’utilise depuis pas mal de temps, avec des VM qemu-kvm comme des conteneurs openvz.

Concrètement, il s’agit d’une distribution GNU/Linux basée sur Debian embarquant les solutions de virtualisation/conteneurs suscitées, et une jolie interface web s’appuyant sur la lib JS ExtJS pour faciliter l’administration de tout ça. Le problème, c’est que cette interface est publique, accessible simplement via une URL, et il est possible de se connecter en root directement dessus (via un module pam). Or, je ne souhaite pas que l’on puisse avoir la main totale sur un serveur avec un simple mot de passe, fut-il d’une robustesse redoutable. Une authentification par mot de passe, c’est trop faible.

Apache

Du coup, ma solution est de reconfigurer le serveur web de Proxmox afin de passer par un tunnel SSH en tant que seul moyen d’accéder à la console. Le serveur web en question est un Apache, et il y a 2 fichiers à modifier pour sécuriser cette partie : pour les trouver, il suffit de rechercher les occurrences de « listen » dans le dossier d’Apache :

# grep -Rl Listen /etc/apache2/
/etc/apache2/sites-enabled/pve.conf
/etc/apache2/sites-available/pve.conf
/etc/apache2/ports.conf

Le fichier /etc/apache2/sites-enabled/pve.conf est une stricte copie de celui dans sites-available, donc la modification sera à répliquer dans les deux fichiers.

C’est la directive « Listen » d’Apache qui nous intéresse. On en trouve 2 dans ports.conf, et un dans pve.conf. Elle indique simplement sur quelle interface on désire faire écouter Apache. Si on précise seulement le port, on écoute sur toutes les interfaces. Mais il est heureusement possible de préciser l’adresse sur laquelle on souhaite écouter via « Listen <address>:<port> ». Concrètement, on va changer ces fichiers pour n’écouter que sur l’adresse locale (127.0.0.1).

Listen 127.0.0.1:80

N’oubliez pas de remplacer le port par celui de la ligne en question. Redémarrez le serveur (« service apache2 restart ») et constatez que l’interface web n’est plus accessible via son adresse publique.

SSH

Si Apache n’écoute plus que sur l’adresse localhost, il vous faut être sur cette machine pour accéder à l’interface. Mais je doute que lynx se comporte très bien avec une interface web pleine de JS, alors il nous faut une autre solution : le tunnel SSH, dont je parlais plus haut. Le principe est de se servir du serveur en tant que proxy encapsulé dans du SSH (ainsi, pas besoin d’ajouter un daemon sur la machine). La requête HTTP telle que vous la tapez sera donc réellement lancée depuis le point de sortie (le serveur). Et si vous faites pointer votre navigateur sur l’adresse 127.0.0.1, c’est bel et bien sur le serveur que vous vous retrouverez.

Mais pour que cette solution soit un tant soit peu efficace, il faut forcer l’utilisation de clés SSH pour se connecter au serveur, à cause de la problématique évoquée plus haut. Ma solution perso est de n’autoriser la connexion SSH qu’avec une clé, et uniquement sur un utilisateur n’ayant aucun droit, si ce n’est celui d’utiliser su pour passer en root. Il faudra donc posséder la bonne clé SSH (et sa passphrase) ainsi que le pass root pour pouvoir se connecter au serveur. Avant de modifier la configuration du serveur, il vaut mieux installer la clé SSH. Si vous n’en avez pas sur votre poste, créez-en une (à taper sur votre poste local) :

$ ssh-keygen

Je vous laisse le soin de choisir les options de votre clé. Par principe, utilisez le maximum de bits possibles selon l’algorithme choisi. Choisissez soigneusement votre passphrase, de sorte à la retenir sans négliger sa robustesse.

Il nous faut maintenant créer un utilisateur sur le serveur, sur lequel on se connectera :

# useradd -m poney # choisissez un nom pas trop évident
# passwd poney # choisissez un mot de passe temporaire

Nous spécifions un mot de passe pour cet utilisateur, car il faudra s’y connecter une première fois pour y stocker la clé. On supprimera le mot de passe tout à l’heure.

Ensuite, utilisez ssh-copy-id pour ajouter votre clé sur le serveur (toujours à taper sur votre poste local) :

$ ssh-copy-id poney@<adresse du serveur>

Si besoin, spécifiez via l’option -i le fichier de clé à envoyer au serveur. Le mot de passe du compte poney vous est demandé.

Ensuite, nous sommes prêts à modifier la configuration du serveur. Voici donc les lignes à modifier dans /etc/ssh/sshd_config :

Port 9347 # on modifie le port par défaut, pour éviter les scanners idiots, choisissez-en un inutilisé au hasard
PermitRootLogin no
PasswordAuthentication no # on ne permet pas la connexion par un mot de passe

Redémarrez le serveur via service ssh restart, ne vous déconnectez surtout pas, au cas où votre configuration ait rendu toute nouvelle connexion impossible, puis tentez de vous connecter depuis votre utilisateur fraîchement créé (par le biais de sa clé) :

$ ssh -p 9347 poney@<adresse du serveur>

Si tout se passe bien, ce ne sera plus le mot de passe du compte poney qui sera demandé, mais la passphrase de votre clé SSH (possiblement dans une fenêtre graphique, selon votre configuration). Si vous accédez au shell de poney après ça, c’est que tout est bon. Vous pouvez maintenant accéder au root en tapant « su - » (le tiret final permet de réinitialiser les variables d’environnement, pour éviter que d’éventuelles saloperies ayant infecté poney ne soient transmises à root), suivi de votre mot de passe root.

Respirez, on est presque à la fin.

Maintenant que l’on sait se connecter au serveur via un utilisateur tiers et via une clé SSH uniquement, on peut supprimer le mot de passe de cet utilisateur :

# passwd -d poney

La partie serveur est maintenant un peu plus sécurisée qu’avant. Il reste une dernière chose à faire pour pouvoir accéder de nouveau à l’interface web.

Poste client

Avant d’oublier, nous allons utiliser le fichier de configuration ~/.ssh/config sur notre poste client pour faciliter la syntaxe de la connexion :

Host mon-serveur # remplacez par le nom que vous souhaitez
HostName <ip du serveur>
User poney
Port 9347

Ceci vous permettra de vous connecter par cette simple commande :

$ ssh mon-serveur

Maintenant, nous allons enfin configurer le tunnel SSH. Rien de bien complexe, c’est intégré dans OpenSSH :

$ ssh -ND 1234 mon-serveur # utilisez un port de votre choix

La connexion SSH se fait, et un proxy SOCKS5 est mis en place sur le port 1234 de votre adresse locale. Celui-ci sort sur le serveur. Il suffit alors de configurer votre navigateur pour utiliser ce proxy. Sous Firefox, par exemple, la configuration se trouve dans Édition → Préférences → Avancé → Réseau → Connexion → Paramètres. Renseignez « 127.0.0.1 » comme hôte SOCKS, en mode SOCKS v5, avec le port choisi plus haut. Surtout, supprimez les exceptions dans le champ du dessous, car sinon vous ne pourrez pas accéder à l’interface Proxmox (étant donné qu’elle sera sur l’adresse 127.0.0.1).

Configuration du proxy
dans Firefox

Maintenant, faites pointer votre navigateur sur l’adresse 127.0.0.1 Si tout va bien, vous serez redirigé sur le port 8006 en SSL, car c’est la configuration par défaut de Proxmox, et vous pourrez accéder à l’interface comme avant.

Lorsque vous aurez fini ce que vous aviez à faire sur l’interface, reconfigurez votre navigateur pour utiliser votre ancienne configuration de proxy, déconnectez votre tunnel SSH (en tapant simplement « exit » ou en appuyant sur ctrl-D). Si vous voulez y accéder de nouveau :

  1. lancez le tunnel SSH (ssh -ND 1234 nom-serveur)
  2. configurez les paramètres de proxy de votre navigateur
  3. visitez http://127.0.0.1 dans votre navigateur

Planquez (un peu) Proxmox

lundi 14 juillet 2014 à 00:00

Listes en compréhension en Python

mercredi 5 mars 2014 à 00:00

J’aime principalement deux choses dans le langage Python : la redoutable simplicité de sa syntaxe, et l’incroyable puissance des listes en compréhension, permettant d’effectuer des traitements en une seule ligne imbuvable. Oui, c’est parfaitement contraire au premier point. Je vais donc revenir sur ces listes en compréhensions.

De quoi parle-t-on ?

Les listes en compréhension sont une syntaxe présente dans le langage Python (entre autres) permettant de filtrer un itérable (comme une liste). En gros, cela permet l’écriture d’une boucle for dont la finalité est de créer une liste. Un exemple sera plus parlant.

resultat = []
for i in range(10):
    resultat.append(i*2)

Cette syntaxe classique utilise 3 lignes pour générer la simple liste [0,2,4,6,8,10,12,14,16,18,20]. Voyons maintenant comment écrire cela autrement :

resultat = [i*2 for i in range(10)]

Voila. Rien de plus. Nous arrivons au même résultat avec une écriture bien plus concise. Il est possible de compléter l’exemple précédent :

resultat = []
for i in range(10):
    if(i % 2 == 0):
        resultat.append(i)

On itère i de 0 à 9, et on insère i dans resultat si celui-ci est pair (c’est à dire si le résultat de sa division par 2 est nul).

Voyons maintenant la version en liste en compréhension :

resultat = [i for i in range(10) if i % 2 == 0]

On peut donc, grâce à la version verbeuse de l’expression, isoler les différentes parties :

La puissance des listes en compréhension est incroyable. Pensez que l’itérable source de votre liste en compréhension peut lui aussi être une liste en compréhension !

Expressions génératrices

Si vous ne connaissez pas les générateurs en Python, il s’agit de structures itérables dont la valeur est calculée au moment où on tente d’y accéder, et non pas à l’assignation. Ce qui permet d’itérer sur de très gros volumes de données, mais également d’itérer à l’infini sur une valeur.

>>> def sq(n):
...     print('sq(%d)' % d) # on affiche quelque chose à chaque exécution
...     return n**2
...
>>> l = [sq(i) for i in range(10)]
sq(0)
sq(1)
sq(2)
sq(3)
sq(4)
sq(5)
sq(6)
sq(7)
sq(8)
sq(9)

Comme on le constate, avec une simple liste en compréhension, la fonction sq() est appelée à l’assignation de la liste, car les valeurs sont calculées à ce moment. Ce n’est pas le cas des expressions génératrices.

>>> g = (sq(i) for i in range(10))

Rien n’est affiché. Notre fonction sq() n’est donc pas appelée. Elle le sera à chaque fois qu’on cherchera à accéder à un élément du générateur.

>>> for i in g:
...     print(i)
... 
sq(0)
0
sq(1)
1
sq(2)
4
sq(3)
9
sq(4)
16
sq(5)
25
sq(6)
36
sq(7)
49
sq(8)
64
sq(9)
81

Les lignes « sq(×) » sont le signe que notre fonction sq() est exécutée à ce moment. Et donc, en cas de données lourdes, on ne charge pas tout en mémoire instantanément.

La seule chose qui distingue une expression génératrice d’une liste en compréhension, syntaxiquement parlant, est simplement l’usage de parenthèses autour de l’expression au lieu de crochets.

Sets en compréhension

Enfin, et parce que je préfère évoquer toutes les possibilités de cette syntaxe, sachez qu’il est possible de générer un set (c’est à dire une liste dédoublonnée) à partir d’une liste en compréhension. Il suffit pour cela d’utiliser les accolades au lieu de crochets autour de l’expression.

>>> s = [n % 5 for n in range(10)] # liste en compréhension
>>> s
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
>>> s = {n % 5 for n in range(10)} # set en compréhension, sans doublon
>>> s
{0, 1, 2, 3, 4}

Un exemple ?

La raison profonde pour laquelle j’ai voulu écrire cet article est le besoin récent que j’ai eu de convertir une chaîne binaire en texte, par conversion des octets en nombres décimaux, puis correspondance dans la table ascii. Malgré l’existence de nombreux convertisseurs en ligne (j’en ai moi-même écrit), je me suis dit qu’écrire un convertisseur en une ligne serait amusant, le tout sous les yeux d’une amie. Et donc, voici :

>>> s = '01010000011010010110111001101011011010010110010100100000010100000110100101100101001000000110100101110011001000000111010001101000011001010010000001100010011001010111001101110100'
>>> print(''.join([chr(int(b, 2)) for b in [s[i:i+8] for i in range(0, len(s), 8)]]))
Pinkie Pie is the best

Voilà.

Bon, ok, je vous fais la version longue et commentée :

s = '01010000011010010110111001101011011010010110010100100000010100000110100101100101001000000110100101110011001000000111010001101000011001010010000001100010011001010111001101110100'
conversion = [] # on stocke le résultat dans un tableau, qu’on convertira
                # ensuite en chaîne

# commençons par découper notre chaîne en octets (8 bits)
octets = []
# on doit itérer (taille de la chaîne / 8) arrondi au supérieur (au cas où)
for i in range( 0, len(s), 8 ):
    octets.append(s[i:i+8]) # vivent les slices d’itérable : on découpe
                            # à partir de i caractères jusqu’à 8 de
                            # plus au maximum
# on a maintenant nos octets séparés. Il ne reste plus qu’à les convertir en
# décimaux, puis récupérer la valeur de la table ascii correspondante
for octet in octets:
    octet_dec = int(octet, 2) # pour convertir à partir de la base 2
    conversion.append( chr( octet_dec ) )

print( ''.join( conversion ) ) # ENFIN !

Vous ne trouvez pas que la première version est plus, disons, succinte ?

[edit] Rogdham m’a suggéré une amélioration du convertisseur binaire

Listes en compréhension en Python

mercredi 5 mars 2014 à 00:00
Error happened! 0 - Call to a member function query() on null In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:200 http://www.ecirtam.net/autoblogs/autoblogs/gordonre_8434367e33ef48dfabf42619ed4c63e864c73ee7/?2 #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(414): VroumVroum_Config->setDisabled() #1 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #2 /var/www/ecirtam.net/autoblogs/autoblogs/gordonre_8434367e33ef48dfabf42619ed4c63e864c73ee7/index.php(1): require_once('...') #3 {main}