Informatique
Monday, 14 May 2018
|
Écrit par
Grégory Soutadé

Fenêtre principale de KissCount

Enfin ! Après avoir retravaillé l'empaquetement, la compilation et la documentation, voici la version 0.7 de KissCount ! Cela fait un an et demi depuis la dernière version (qui ne comportait que peu de correctifs). En réalité, cette mouture était prête depuis 6 mois, mais j'étais occupé à mettre en place Pannous.

Et pour une version, c'est une belle version, avec en figure de proue la migration vers Qt5 (qui a principalement motivé son développement), ainsi qu'un nouvel exécutable pour Windows ! Là aussi, il y avait un gros retard, puisque le dernier binaire en date était la 0.4 de ... 2013. Cette fois-ci, elle est compilée nativement depuis Visual Studio/Windows 10, alors qu'auparavant, j'utilisais mingw en cross compilation depuis Linux.

À ma grande surprise, la migration de Qt4 vers Qt5 s'est faite tout en douceur avec très peu de changements nécessaires. Cela a surtout été l'occasion de se débarrasser de libkdcharts au profit de la bibliothèque de dessin intégrée à Qt (même si je ne suis pas pleinement satisfait du rendu des graphiques circulaires). Le panneau principal a subi une légère modification, puisque le calendrier migre en bas à gauche, ce qui permet de gagner de la place et d'être plus cohérent.

Autre fonctionnalité intéressante : lorsqu'un compte descend en dessous d'une certaine limite (200€ par défaut) ou en dessous de 0, les jours du calendrier sont colorés (en jaune (configurable) et rouge). Également, plutôt que de "cacher" les comptes clôturés, j'ai intégré une date de début et de fin, plus pratique (je sais, c'est une fonctionnalité de base chez la concurrence...). Finalement, tout un tas de petits bugs ont été corrigés.

Bref, une bien belle version pour inaugurer la nouvelle liste de diffusion kisscount-announce@soutade.fr

Thursday, 26 April 2018
|
Écrit par
Grégory Soutadé

La toute récente sortie de Pannous a été l'occasion de créer un nouveau sous-domaine pour héberger le service. Service qui comporte une partie d'authentification, donc obligation de passer par une communication sécurisée (SSL/TLS). Autrefois, la chose était plus aisée, puisque je pouvais générer mes propres certificats et notamment des certificats "wildcards", donc valides pour tous les sous-domaines.

Sauf que je suis passé à Let's Encrypt. Il a donc fallu attendre la sortie de la version 2 du protocole (qui a eu du retard) afin de bénéficier de cette fonctionnalité. Surtout, qu'au passage, le paquet Debian (backport) de certbot a été cassé, ce qui m'a forcé à revenir à une version encore plus ancienne.

Bref, les choses sont maintenants stables et déployées sur les serveurs respectifs. Petit problème néanmoins, la génération d'un certificat wildcard par Let's Encrypt requiert l'ajout d'une entrée DNS (comme challenge). Fatalité, le DNS de Gandi a lui aussi évolué pour passer en version 5. Avec pour principal avantage une mise à jour immédiate des entrées DNS (là où il fallait plusieurs minutes/heures auparavant). Autre nouveauté : l'API Gandi change de format. On oublie l'ancien XML-RPC (ce qui était pratique avec des bindings Python déjà tout faits), pour passer au REST (un peu moins formel).

Mélangeons tout ça pour obtenir un joli cocktail, dont la recette nous est donnée par Sébastien Blaisot qui, pour nous simplifier la vie, a créé des scripts de génération de certificats wildcards via Let's Encrypt. Le code est disponible sur GitHub et supporte le logiciel bind (en local), l'API OVH et la nouvelle API Gandi. Il ne reste plus qu'à cloner le dépôt et lancer la commande magique :

cd certbot-dns-01-authenticators/gandi-livedns
certbot certonly --manual --server https://acme-v02.api.letsencrypt.org/directory\
 --manual-auth-hook $PWD/auth.py --manual-cleanup-hook $PWD/cleanup.py -d '*.soutade.fr'

Et voilà un joli certificat tout frais !

Du coup, je me suis grandement inspiré de son code pour mettre à jour mon script de DNS fallback (serveur de secours via redirection DNS). Avec, en prime, un passage en Python 3 ! À terme, il faudra que j'ajoute le support IPv6.

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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/>.

import requests
import json
import re

# Config
domain="soutade.fr"
API_KEY = "YOUR-KEY"
livedns_api = "https://dns.api.gandi.net/api/v5/"
dyndns_url = 'http://checkip.dyndns.com/'
headers = {
    'X-Api-Key': API_KEY,
}
A_RECORD_NAME="@" # Record of A type

# Get current IP
current_ip = ''
response = requests.get(dyndns_url)
if response.ok:
    pattern = re.compile('[^:]*(\d+\.\d+\.\d+\.\d+)')
    result = pattern.search(response.text, 0)
    if result == None:
        print("No IP found")
        exit(1) 
    else:
        current_ip = result.group(0).strip()
else:
    print("Connexion error")
    response.raise_for_status()
    exit(1)

print('Your Current IP is %s' % (current_ip))

# Retrieve domains address
response = requests.get(livedns_api + "domains", headers=headers)

if (response.ok):
    domains = response.json()
else:
    response.raise_for_status()
    exit(1)

domain_index = next((index for (index, d) in enumerate(domains) if d["fqdn"] == domain), None)

if domain_index == None:
    # domain not found
    print("The requested domain " + certbot_domain + " was not found in this gandi account")
    exit(1)

domain_records_href = domains[domain_index]["domain_records_href"]

# Get recorded IP
response = requests.get(domain_records_href + "/" + A_RECORD_NAME + "/A", headers=headers)

if (response.ok):
    record = response.json()
else:
    print("Failed to look for recorded IP")
    response.raise_for_status()
    exit(1)

print('Old IP : %s' % (record['rrset_values'][0]))

if current_ip != record['rrset_values'][0]:
    record['rrset_values'][0] = current_ip

    # Put updated IP
    response = requests.put(domain_records_href + "/" + A_RECORD_NAME + "/A", headers=headers, json=record)

    if (response.ok):
        print("IP updated")
    else:
        print("something went wrong")
        response.raise_for_status()
        exit(1)

    exit(34) # IP updated return !!

exit(0)

Une version téléchargable est disponible ici.

Thursday, 26 April 2018
|
Écrit par
Grégory Soutadé

Logo Pannous

Il y a quelques temps déjà, Mozilla a décidé d'abandonner son format et ses API pour les extensions de son navigateur (Firefox) afin de tout migrer vers les WebExtensions. Ces dernières reprennent en très grande partie les caractéristiques de son concurrent Chrome. Cet événement, à priori négatif, m'a permis d'améliorer la sécurité de mon extension gPass et de factoriser grandement le code. Malheureusement, la migration de la version 0.7 (ancien format) à la version 0.8 n'est pas automatique (contrairement à Chrome) et beaucoup d'utilisateurs se sont perdus en chemin. C'est à ce moment-là que je me suis rendu compte qu'il me manquait un lien avec les utilisateurs de manière générale.

Il est vrai que ma forge inDefero n'est plus maintenue depuis maintenant 8 ans, qu'il n'y a pas de fonctionnalité "sociale" ou de "distribution" pour prévenir d'éventuelles nouveautés. Pour autant, je n'ai pas envie de passer sur une autre plateforme (notamment Github) et je ne trouve aucun remplaçant aussi ergonomique. La plupart des candidats ne se contentant que d'imiter la plateforme propriétaire majeure. L'idée, toute simple, est donc de créer des listes de diffusion projets (mailing list ou announce list).

La référence dans le domaine est GNU mailman. Je pourrais y faire les mêmes reproches que pour les forges : compliqué à configurer, moche, lourd. Alors que mes besoins sont assez légers. Du coup, je me suis lancé dans l'écriture d'un petit moteur de liste de diffusion. À priori, rien de compliqué : un petit script pour la gestion des mails et une gestion assez simples des listes/groupes/utilisateurs (ajout, modification, suppressions, droits). Le tout en PHP : simple, rapide et avec tous les modules nécessaires. En effet, j'ai décidé d'abandonner mon Django favoris, car, s'il aurait pu répondre à tous mes besoins, il est extrêmement lent pour mon petit serveur.

Au début, j'ai récupéré un peu de code d'un ancien projet avec un framework perso (simpliste). Mais en complexifiant un tout petit peu le projet, je me suis dit qu'il était inutile de ré inventer la roue. J'ai donc fait le tour des frameworks PHP (des plus lourds et connus aux plus légers). C'est assez impressionnant de voir que certains font des centaines de méga pour une application de moins de 2MO... Après comparaison, il s'avère qu'ils adoptent tous la même philosophie HMVC et l'on peut passer de l'un à l'autre assez rapidement. Après avoir longtemps lorgné sur CakePHP, j'ai finalement décidé de me lancer en FuelPHP. J'y retrouve toutes les fonctionnalités que j'attends, en plus d'être léger et rapide.

Bref, tout ça pour annoncer la création de Pannous pour PHP Annoucement System. L'objectif est d'être simple à installer, configurer, utiliser. On y retrouve deux fonctionnalités inédites :

  • Plusieurs niveaux de droits pour l'émission d'un mail sur la liste (écrivains, lecteurs, utilisateurs, tout le monde), avec la possibilité de vérifier la signature GPG.
  • API Rest pour l'inscription/désinscription d'une liste (avec génération automatique de code HTML/AJAX).

Pour le reste, c'est du classique : gestion des utilisateurs, des groupes, des listes, inscription/désinscription en ligne ou par mail...

Le projet est disponible sur ma Forge sous licence GPL v3.

Naturellement, j'ai créé une liste pour Pannous : pannous-announce@soutade.fr

Monday, 08 January 2018
|
Écrit par
Grégory Soutadé

Logo Let's encrypt

Au revoir 2017, bonjour 2018 ! Alors que l'actualité brûlante du moment tourne autour de la faille de sécurité trouvées sur différents processeurs supportant l'exécution spéculative (faille non triviale à exploiter), j'ai pour ma part choisi de passer à Let's Encrypt. Il s'agit d'une organisation à but non lucratif dont l'objectif est de promouvoir l'utilisation de connexions sécurisées (SSL/TLS) sur internet. Pour ce faire, elle propose la génération gratuite (et scriptée) de certificats pour le protocole HTTPS. Elle trouve de plus en plus les faveurs des webmasters, surtout quand on sait qu'un certificat coûte entre 15€ et plusieurs milliers d'euros par an (avec des garanties, tout ça, tout ça).

Leurs certificats sont déployés depuis plus de deux ans maintenant et il faut dire que ça marche plutôt bien ! Auparavant, j'utilisais ma propre autorité de certification, ce qui permet de contrôler finement les paramètres du certificat final, vérifier que l'on n'est pas attaqué... Mais il faut reconnaître que pour la navigation courante et surtout pour gPass, avoir un certificat reconnu de base est devenu une nécessitée.

Surtout que let's encrypt fourni l'utilitaire certbot (disponible dans les paquets Debian) qui s'occupe de (re-)générer, valider, configurer automatiquement tout ce qu'il faut. Il n'y a qu'à renseigner une adresse mail valide ainsi que la liste des domaines concernés.

J'ai eu deux erreurs lors de cette procédure. La première :

Domain: denote.soutade.fr
Type:   unauthorized
Detail: Incorrect validation certificate for tls-sni-01 challenge.
Requested
605840d8f5902f3f5f9b465e90fefda9.5e9055daac5e4439a0d768344e093378.acme.invalid
from 89.95.86.199:443. Received 1 certificate(s), first certificate
had names
"58d2ce91621ca3bc9dad2ae778ba8110.6f005c32f7a4345e14b2120f34d7e6c7.acme.invalid,
dummy"

Et la seconde concernant le nom de domaine teamazurevasion.fr que j'héberge également sur le même serveur (le DNS pointe vers soutade.fr, ça ne plaît pas trop au robot).

Domain: teamazurevasion.fr
Type:   unknownHost
Detail: No valid IP addresses found for teamazurevasion.fr

Il faut dire que ma configuration est un peu particulière puisque j'ai un premier serveur web/proxy (tournant sous nginx) qui s'occupe des sites statiques ainsi que de la liaison sécurisée (TLS). Il communique par socket avec un second serveur (Apache) pour toute la partie dynamique (PHP, Django). La solution consiste donc à stopper le service nginx et utiliser le mode standalone (une fois le port 443 libéré) pour (re-)générer mes certificats :

service nginx stop
certbot certonly --standalone --expand -d soutade.fr -d www.soutade.fr -d blog.soutade.fr
-d demo-gpass.soutade.fr -d denote.soutade.fr -d dynastie.soutade.fr -d gpass.soutade.fr
-d gpass-demo.soutade.fr -d iwla.soutade.fr -d music.soutade.fr
service nginx start

Une petite entrée de plus dans /etc/crontab et voilà, je n'ai plus à me soucier de la gestion de mes certificats ! Bon, j'aurais également pu déployer des scripts dans /etc/letsencrypt/renewal-hooks/.

J'attendais avec impatiente l'arrivée des wildcards (*.nom.de.domaine) qui devaient être intégrés à partir du 4 janvier, mais étant donné que certbot ne génère qu'un seul fichier certificat (embarquant tous les noms de domaines), j'ai finalement sauté le pas. Bien sûr, un wildcard sera plus pratique pour tout ce qui n'est pas configuré explicitement au niveau de mes serveurs webs. Activation prévue fin février, attendons de voir.

Friday, 15 December 2017
|
Écrit par
Grégory Soutadé

Logo gPass

Mise à jour mineure de gPass suite à la "violation" des règles d'utilisation de Google : il manquait les informations concernant la gestion des données utilisateurs (vie privée/privacy policy). J'en profite pour pousser deux autres changements :

  • Léger changement dans l'algorithme de génération des wildcards côté client (*.nomde.domain), qui ne fonctionnait pas pour des noms de domaines courts (<= 3 caractères)
  • Côté serveur, l'affichage descend automatiquement lors de l'ajout d'une nouvelle entrée (pratique quand on a beaucoup de mots de passes)