Planet Asrall

April 21, 2019

Romain Dessort

Using molecule with GitLab CI

This article follows this one where I talk about verifying and testing your Ansible roles with molecule.

As I wrote in this first article, I also configured GitLab CI to automatically run molecule tests after each push to the repository. A good documentation to start with is the official GitLab CI/CD documentation.

Here is our .gitlab-ci.yml (latest version) used on our ansible repository hosting all our roles:

---
image: quay.io/ansible/molecule:latest
services:
  - docker:dind

stages:
  - tests

before_script:
  - docker -v
  - python -V
  - ansible --version
  - molecule --version

molecule-role-common:
  stage: tests
  tags:
    - docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    PY_COLORS: 1
  script:
    - cd roles/common/
    - molecule test
  only:
    changes:
      - roles/common/**/*

molecule-role-monitoring-tools:
  stage: tests
  tags:
    - docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    PY_COLORS: 1
  script:
    - cd roles/monitoring-tools/
    - molecule test
  only:
    changes:
      - roles/monitoring-tools/**/*
[‌]

Our repository is hosted on gitlab.com and even if we can connect our own GitLab Runners to gitlab.com instance, we decided to use the provided gitlab.com's shared runners to get started quickly.

Those use Docker to run the jobs. So we instantiate the official molecule image (from Red Hat's registry):

image: quay.io/ansible/molecule:latest

And since molecule also needs to spawn Docker containers, we need to start another container: docker:dind (Docker in Docker). This is done with:

services:
  - docker:dind

And by exporting the DOCKER_HOST variable to the jobs, so that molecule will be able to connect to the Docker daemon to manage its containers:

variables:
  DOCKER_HOST: "tcp://docker:2375"

Then we define one stage, called tests:

stages:
  - tests

Each jobs related to this stage will have the stage: tests key in their definition.

The before_script block is here for debugging purpose (if the molecule tests succeed locally bt fail on the runners, it may be because the tests are run with a different version of molecule).

Last, we define as many jobs as we have roles to test with molecule. Here, tags is important since it is used by GitLab CI to select the runner the job will be executed on. We need a runner with Docker, thus:

tags:
  - docker

With script keyword we specify each command the runner should run. Pretty self-explanatory:

script:
  - cd roles/monitoring-tools/
  - molecule test

And only adds kind of a condition to the job. We don't want to run all jobs if, for instance, someone updates the README file. The job should run only if a commit introduces a change to a file in the role itself. So we restrict the execution to a change under the role's hierarchy only with:

only:
  changes:
    - roles/monitoring-tools/**/*

For instance, this pipeline, triggered by the commit afe5929c, has spawned only one job, the molecule-role-firewall test, since only the firewall role was modified. Whereas this commit (which impacted multiple roles) triggered 7 jobs (and yes, one has failed).

If a job fails, the commiter will receive a notification by e-mail with the commit and job ID. We can check the job's output on GitLab, to see why it has failed.

As an ending note, to test your .gitlab-ci.yml without pushing, you can install the gitlab-runner package and run it on your repository (you still need to commit your changes since `gitlab-runner will checkout the repository):

$ gitlab-runner exec docker <your job name>

April 21, 2019 01:40 AM

April 20, 2019

Romain Dessort

Containerize ZNC with systemd-nspawn

On my previous server, I had an IRC client (weechat) running in a screen session. I recently migrated this setup to a ZNC bouncer running on my server + weechat running on my laptop, mainly to avoid network lags through SSH and to have a better desktop integration (notifications, etc.).

My server hosts my e-mails, important and confidential documents (among other things) and I always thought that running an always connected IRC client on the same machine was a bad idea.

So in the same time I got rid of my weechat+screen to migrate to ZNC, I had a look at systemd-nspawn, also known as systemd containers. It's actually pretty much like LXC containers, but managed with the systemd logic in mind.

Here is a quick tutorial of how I containerized ZNC into a systemd container on a Debian stretch system:

  1. First, install systemd-container and debootstrap packages:

    # apt install systemd-container debootstrap
    
  2. Create directories used by systemd-container (which aren't create on install):

    # mkdir /var/lib/machines/ /etc/systemd/nspawn/
    

    /var/lib/machines/ will host the containers' hierarchy and containers unit files will be stored into /etc/systemd/nspawn/.

  3. Install a Debian system + znc package using debootstrap into /var/lib/machines/znc/:

    # cd /var/lib/machines/
    # debootstrap --include znc stretch znc
    
  4. To make the container start on boot, you have to enable the corresponding systemd-nspawn instance and the machines target (which will start all enabled instances of systemd-nspawn unit:

    # systemctl enable systemd-nspawn@znc machines.target
    
  5. At this stage, your container is ready. You can start your container (run its init process) with:

    # systemd-nspawn -D /var/lib/machines/znc/
    

    Or execute a command in the container:

    # systemd-nspawn -D /var/lib/machines/znc/ hostname
    

    systemd-nspawn comes with the machinectl command which allows you to easily manage your containers:

    # machinectl [list|list-images|start|stop|status|…]
    

    See its man page for all supported sub-commands and options.

  6. Now, let's configure some few extra stuff to run ZNC. Create a dedicated user in the container:

    # systemd-nspawn -D /var/lib/machines/znc/ useradd -u 1002 -g 1002 -m znc
    
  7. And customize the container's options by putting a config file into /etc/systemd/nspawn/:

    [Exec]
    # Don't start the init process inside the container, instead execute
    # `znc`
    Boot=off
    Parameters=/usr/bin/znc --foreground
    
    
    # Drop all default capabilities: the container will run with no
    # capabilities, ZNC doesn't need any
    DropCapability=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_FSETID CAP_IPC_OWNER CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETGID CAP_SETFCAP CAP_SETPCAP CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_PTRACE CAP_SYS_TTY_CONFIG CAP_SYS_RESOURCE CAP_SYS_BOOT CAP_AUDIT_WRITE CAP_AUDIT_CONTROL
    
    
    # Start `znc` as znc
    User=znc
    
    
    # Those 2 options aren't supported by the version of systemd shiped
    # with Debian stretch. Ephemeral=on makes the container discard on
    # shutdown any modification made during its runtime (Docker style) and
    # NoNewPrivileges ensures that the code executed inside the container
    # won't be able to gain greater privileges (with setuid bit for instance)
    #Ephemeral=on
    #NoNewPrivileges=on
    
    
    # Use private user namespace (equivalent to LXC's unprivilegied mode)
    PrivateUsers=on
    
    
    [Files] 
    # Mount my .znc configuration directory onto the container (with write
    # access) and the znc.pem containing the private key, certificates
    # chain and DH param (read-only)
    Bind=/home/romain/.znc/:/home/znc/.znc/
    BindReadOnly=/var/lib/acme/live/irc.univers-libre.net/combined:/home/znc/.znc/znc.pem
    
    
    [Network]
    # Use the same network stack as the host
    VirtualEthernet=no
    

    This file will automatically be read by systemd-nspawn before starting the container. All these options can be specified as commandline parameters to systemd-nspawn as well.

Resources:

April 20, 2019 04:03 AM

April 16, 2019

Romain Dessort

Ancien réseau de ski à Saint-Donat

Bien qu'assez éloigné des autres pistes nordiques de la vallée de la Rivière du Nord, on peut retrouver aussi des vielles pistes abandonnées aussi loin que Saint-Donat. Le massif de montagne entre le lac Ouareau, Saint-Donat et l'Interval hébergeait visiblement beaucoup de pistes de ski. La plus connue et celle qui a le mieux survécue à l'épreuve du temps est la Kaaikop qui monte au Mont Kaaikop depuis la 329 en passant par l'auberge de l'Interval. Un petit réseau de pistes toujours entretenues existe aussi autour de cette auberge mais je ne le connais pas vraiment.

Mais plus au nord-est, j'avais découvert en y allant l'été plusieurs sentiers bien envahis mais assez larges pour être des pistes de ski (lui, lui et lui notamment. Sur certains, il restait même quelques panneaux avec le numéro de la piste, des « attention pente raide » et des « sens unique », preuve que ça devait même être un réseau damé et tracé. Certaines balises portaient le nom La Réserve, station de ski alpin des environs, c'est donc possible qu'à une époque ce centre avait un réseau de ski nordique qu'il a ensuite complètement abandonné au profil du ski alpin…

Aujourd'hui, seules quelques pistes sont encore entretenues officiellement par le club de plein air de Saint-Donat qu'on peut voir sur leur carte.

J'y suis retourné la semaine passée, après la dernière neige de la saison avec 2 autres collègues de ski, dans le but de rejoindre le Mont Kaaikop depuis Le Grand R via une des pistes abandonnées. Je la connaissais en partie pour l'avoir faite l'été, mais je l'avais perdue à un moment donnée. En début d'hiver, j'avais repéré une intersection sur un autre sentier où c'était très probable qu'elle arrive là, je voulais donc confirmer ça. Finalement on a manqué une intersection et pas pris le sentier que je voulais… mais on en a découvert un autre ! À peu près parallèle à celui que je voulais retrouvé. J'ai aussi remarqué le départ d'un autre sentier (encore un) qui partait plein est.

Bref il reste beaucoup à explorer dans ce secteur encore, j'ai rajouté plus de fixme que complété des sentiers dans OpenStreetMap…

Par contre j'ai pu mettre la main sur un vieux plan des piste de Saint-Donat datant de 1981, édité à l'époque par le club de ski de fond des Panaches (qui n'existe plus). Le secteur dont je parle ici est celui du Lac Bouillon. Mais on voit qu'il y avait des pistes pas mal partout autour de Saint-Donat.

Certains des pistes que j'ai trouvé apparaissent effectivement sur le plan, mais d'autres non, probablement plus récentes.

April 16, 2019 06:26 PM

April 12, 2019

Romain Dessort

Yoghourt maison

Des fois il y a des trucs tellement simple à faire qu'on peut se demander pourquoi le monde ne les font plus eux-mêmes. Les yoghourts par exemple.

Un couple qui m'avait hébergé en Colombie-Britannique faisait son propre yaourt et m'avait montré comment faire. Du coup maintenant j'ai pris l'habitude de faire le mien (et d'en remanger aussi, parce que ça faisait longtemps que je n'en achetais plus).

Les avantages que je vois à faire son propre yaourt :

  • moins de déchet, surtout s'il est possible de trouver du lait en bouteille consignée (même si au Canada c'est moins pire, on ne trouve pas des pots de yoghourt individuels, seulement des pots de 0,5 ou 1 L)
  • plus économique (1 L de lait est généralement moins cher que 1 L de yoghourt)
  • le plaisir de faire son propre yoghourt
  • permet de conserver du lait plus longtemps si votre bouteille est due pour bientôt

La marche à suivre est assez simple, en fait il n'y a quasiment rien à faire. Je peux résumer ça à :

  • vider une bouteille de lait dans une casserole et le faire bouillir
  • le laisser tiédir et mélanger avec le fond de votre yoghourt précédent
  • transvaser dans des pots et les conserver au chaud le plus longtemps possible, puis mettre au frigo

Mais ça mérite quand même quelques explications de plus :

Au départ il faut trouver des bactéries lactiques actives, soit en pharmacie soit en supermarché, sous forme de « Yogourt Starter ». D'après des tests, utiliser des yaourts existants du commerce ne marche pas, même des yaourt dit contenant des ferments actifs, un bon test pour valider (ou pas) ce qui est écrit sur les pots de yaourt…

Ensuite il faut du lait (entier tant qu'à faire, mais allégé marche aussi).

Commencez par faire bouillir le lait pour le stériliser (en le surveillant car ça monte vite, prévoyez une grande casserole) et laissez le refroidir tranquillement jusqu'à ce qu'il soit tiède (trempez un doigt dedans, ça doit être chaud mais pas brulant).

Stérilisation du lait

À ce moment là ajoutez les bactéries actives et mélangez bien puis transvaser dans des pots type Mason (ou n'importe quels pots de récup en fait).

Le but maintenant est de garder les pots tièdes (normalement autour de 40 °C mais on est pas à 5 °C près) pendant 4 ou 5h minimum, le temps que les bactéries travaillent. C'est à ce moment là qu'il faut faire preuve d'un peu de créativité. Personnellement je les mets chacun systématiquement dans un gros bas de laine pour mieux garder leur chaleur. Ensuite, l'hiver je les pose sur un radiateur puis je les recouvre d'une polaire. Sinon ils vont dans le four encore chaud, derrière une vitre au soleil, sous la couverture et je dors avec (oui oui !)… Vous pouvez aussi récupérer des petites résistances chauffantes et les mettre dans un vêtement, puis les pots dedans. Bref plein de possibilités.

Yogourt prêt à fermenter dans un bas de laine

Je les laisse comme ça une nuit puis je les mets au frigo ensuite pour stopper le processus (plus les bactéries travailleront, plus le yaourt sera ferme mais acide).

Sinon il y a bien sûr des… trucs qui s'appellent des yaourtières (qui devraient être efficaces), spécialement conçus pour encombrer vos placards de cuisine, puis vos (nos) poubelles quand, tôt ou tard, ils ne marcheront plus.

Le yaourt dans les pots se conservent un ou deux mois au frigo sans problème. Un fois un pot ouvert, je peux le conserver plusieurs semaines. Probablement plus en fait, mais ça m'ait jamais arrivé d'avoir un yoghourt qui a viré mauvais.

Yogourt maison Miam !

Et enfin, pour refaire une autre batch, au lieu des bactéries sèches du départ, il suffit de prendre le fond du pot qui vous reste et le mélanger au nouveau lait tiédi (genre l'équivalent de 3 ou 4 cuillères à soupe, au pif). Au bout d'un moment, si le yoghourt a du mal à prendre, c'est que votre culture de bactéries s'épuise et il suffit de repartir avec de nouvelles.

April 12, 2019 09:40 PM

April 05, 2019

Romain Dessort

Automatically generate a diagram of your infrastructure from Ansible inventory

Another post about Ansible. This time I will talk about generating a representation of your infrastructure, for documentation purpose, using your Ansible inventory and gathered facts.

I had to update the infrastructure diagram of the Services FACiLes project, it was deprecated and, inevitably, will still be in a few months because we keep adding new containers and services. That's a common issue in tech documentation.

Moreover, manually editing a graph isn't a thing I usually like to do, especially when elements, relations and additional text can more easily be declared in a text file using a definition language. I found for instance this tool (not tested) which allows you to describe what you want to graph using a YAML file and then use graphviz to draw the resulting diagram. But, wait, I already have this kind of structured data, it's in my Ansible inventory file.

I then thought about writing an equivalent tool to draw a diagram representing Ansible hosts and groups declared in the inventory and their facts and variables. Of course, someone else already had this idea, and this is called ansible-inventory-grapher. This python tool produces graphviz code that you can directly pipe to graphviz to generate a png or svg file.

As an example, here is how the graph of our infrastructure looks like:

infrastructure graph generated from Ansible inventory

ansible-inventory-grapher doesn't show value of variables but you can use this fork( merge request pending) to have them on your graph. You should also enable fact caching in your ansible.cfg and gather your server's facts before running ansible-inventory-grapher. Else you will only have variables declared in host_vars/ and group_vars/.

The default jinja2 template used to generate the code suitable for graphviz prints all variables and facts, but I was only interested by some of them (those passed to the --visible-vars actually) so I slighty modified the template as follow:

--- doc/ansible-inventory-graph.default.j2
+++ doc/ansible-inventory-graph.j2
@@ -8,7 +8,7 @@
   <tr><td><b>
   <font face="Times New Roman, Bold" point-size="16">{{ node.name}}</font>
   </b></td></tr>
-{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{{var}}{% if var in visible_vars %} = {{node.vars[var]}}{% endif %}<br/>{%endfor %}</font></td></tr>{% endif %}
+{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{% if var in visible_vars %}{{var}} = {% if node.vars[var] is iterable and node.vars[var] is not string %}{{ node.vars[var]|map(attribute='name')|join(', ') }}{% else %}{{node.vars[var]}}{% endif %}<br/>{% endif %}{% else %}<br />{%endfor %}</font></td></tr>{% endif %}
 </table>
 >]
 {% else %}
@@ -17,7 +17,7 @@
   <tr><td><b>
   <font face="Times New Roman, Bold" point-size="16">{{ node.name}}</font>
   </b></td></tr>
-{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{{var}}{% if var in visible_vars %} = {{node.vars[var]}}{% endif %}<br/>{%endfor %}</font></td></tr>{% endif %}
+{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{% if var in visible_vars %}{{var}} = {% if node.vars[var] is iterable %}{{ node.vars[var]|map(attribute='name')|join(', ') }}{% else %}{{node.vars[var]}}{% endif %}<br/>{% endif %}{% else %}<br />{% endfor %}</font></td></tr>{% endif %}
 </table>
 >]
 {% endif %}{% endfor %}
@@ -26,3 +26,5 @@
   {{ edge.source|labelescape }} -> {{ edge.target|labelescape }};
 {% endfor %}
 }

Finally, I wrote a simple Makefile to easily update the graph when inventory is updated:

# Clear out default implicit rules and variables.
MAKEFLAGS += --no-builtin-rules --no-builtin-variables

.PHONY: inventory-graph

inventory-graph: doc/ansible-inventory-graph.png

doc/ansible-inventory-graph.png: inventory/hosts $(wildcard inventory/group_vars/*.yml) $(wildcard inventory/host_vars/*.yml)
    ansible -i inventory/ -m setup all >/dev/null
    ~/src/ansible-inventory-grapher/bin/ansible-inventory-grapher -i inventory/ -t doc/ansible-inventory-graph.j2 -a "rankdir=LR;" \
    --visible-vars knot_zones \
    --visible-vars backup_server \
    --visible-vars munin_master \
    --visible-vars icinga2_master \
    --visible-vars icinga2_parent \
    --visible-vars backup_hosts \
    --visible-vars icinga2_monitored_sites \
    --visible-vars db_type \
    --visible-vars hosted_by \
    --visible-vars libvirt_domains \
    --visible-vars lxc_containers \
    --visible-vars ansible_architecture \
    --visible-vars ansible_default_ipv4.address \
    --visible-vars ansible_default_ipv6.address \
    --visible-vars ansible_distribution \
    --visible-vars ansible_distribution_version \
    --visible-vars ansible_memtotal_mb \
    --visible-vars ansible_processor_vcpus \
    --visible-vars ansible_virtualization_role \
    --visible-vars ansible_virtualization_type \
    all | dot -T png >$@

That way, when we add a new machine or make any change in our infra, all we need to do is make inventory-graph and commit the resulting graph. The image file is linked in our documentation in our private wiki, thus we are ensured that it is always up to date!

April 05, 2019 09:57 PM

March 31, 2019

Romain Dessort

Ansible QA and testing with molecule

Winter is already over in the Laurentians, and for a few weeks now ski conditions are so horribles that it's not fun anymore and I'm sick of applying and cleaning klister on my skis. We will soon have rain and mud and flooding everywhere. April is definitely the worst month in Quebec despite sunny and longer days. But the good thing is that I suddenly have a huge amount of time left for other things. Like system administration and coding.

Thus I picked up a task from my todo list that I always wanted to do: trying molecule over our Ansible roles and playbook at FACiL. The infrastructure running behind the Services FACiLes project is setup and maintained 100% with Ansible. All our roles and playbooks are public and hosted on Gitlab.

molecule is a Python tool developed by the same team as Ansible and aims running different kind of tests over your ansible roles: linting, role execution on multiple targets and scenarios, ensure idempotence over multiple executions and running unit tests on the target.

I am not going to write a full tutorial on molecule, a lot of them already exist on Internet, though not always up to date since molecule is still a young project and in heavy development. The official documentation is a bit rough to get started and to understand the scope of molecule. A good tutorial I found for being the most up to date and accurate.

Instead, in this post I'm going to explain how I use it to test our roles, with some tips I didn't find in the official documentation. I also played with Gitlab CI so molecule tests are run on each git push, but it will be for another post.

molecule's files

We start by creating a molecule directory in our role:

$ molecule init scenario --verifier-name goss --driver-name docker --role-name <role>

It will create a directory named molecule/ in your role's directory. You can have multiple scenarios for your role, stored under molecule/ directory. By default, molecule init will create the scenario default:

$ tree molecule/
molecule/
└── default
    ├── Dockerfile.j2
    ├── INSTALL.rst
    ├── molecule.yml
    ├── playbook.yml
    ├── tests
    │   └── test_default.yml
    └── verify.yml

Each file above are just here to help you to get started and can be edited as you want. Options we passed to the molecule init command are just here to alter these files. I usually prefer copying the molecule/ directory from an existing role (which is totally acceptable) since I have done some extra customization on it.

molecule.yml

molecule.yml is the only molecule configuration file. Here is the molecule.yml we use in our roles :

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: molecule-monitoring-tools
    image: geerlingguy/docker-debian9-ansible:latest
    command: ${MOLECULE_DOCKER_COMMAND:-""}
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
provisioner:
  name: ansible
  lint:
    name: ansible-lint
  options:
    skip-tags: molecule-converge-notest
  log: true
scenario:
  name: default

molecule is designed to be very extensible. For instance, it uses the galaxy dependency resolver (dependencies are defined in the meta/main.yml file of your role) and ansible as a provisioner (this is the only one supported for now but we can imagine molecule will be able to use other configuration management tools).

We use yamllint as the linter for all YAML file of the role (including molecule's own files!) and ansible-lint for ansible files.

For some reasons, we don't want to run some Ansible tasks when testing the role. molecule let us pass any ansible-playbook option with the dict provisioner:options. Here we excluded tasks which have the molecule-converge-notest tag on them.

And finally we tell molecule to run the role in a Docker container. molecule supports a large range of drivers, including LXC containers, Vagrant VMs and common public cloud providers. Even if we use LXC containers on production, I choose to use the Docker driver with molecule because, first it's more easy to setup on a personal machine, and second because this is the only supported option if we want to run molecule tests on Gitlab's shared runners (using Docker in Docker), I will talk about it later in a second post.

Most of our roles don't work inside a minimal Docker container, so we use geerlingguy/docker-debian9-ansible Docker image. The image provides a more complete Debian system (with systemd and common tools). It is maintained by Jeff Geerling, who wrote a lot of Ansible roles and uses it to test his own roles.

By default, molecule spawns a container named instance. It's a bad idea since I ended up with conflicts when I tried to launch tests on multiple roles at the same time, each of them was executed in the same container. Thus I edited platforms:name key so that it bears the role's name.

Dockerfile.j2

This file is only here if you want to build your Docker container on each test. You can use any Docker image (defined in platforms:image) and this Dockerfile will install requirements such as python, sudo, bash and ca-certificates package to run Ansible.

Since the image we use already contains those dependencies, we instruct molecule not to build another Docker image (platforms:pre_build_image). this way, we also speed up tests.

INSTALL.rst

INSTALL.rst is an informal file containing instructions to others contributors and requirements to run molecule tests. For instance, we need to have the Docker engine and docker-py installed on our machine.

playbook.yml

After molecule.yml, this is the second most important file. Once molecule has spawned the container, it will run this playbook.

Here is its content:

---
- name: Converge
  hosts: all
  roles:
    - role: <role>

Pretty simple, it applies onto the spun up container. But if your role requires some variables to be set, or dependencies that aren't declared in your role's meta/main.yml file, you can add them here. For instance:

---
- name: Converge
  hosts: all
  roles:
    - role: <other role>
    - role: <role>
  vars:
    foo: "bar"

verify.yml and tests/test_default.yml

Finally, those two files are for unit tests. verify.yml installs Goss (the verifier tool) in the container and run tests it finds in the tests/ directory. Although it's a very interesting subject, I didn't dig too much into unit tests for now.

Running molecule

First, to enable bash completion:

$ eval "$(_MOLECULE_COMPLETE=source molecule)"

Running molecule without argument will show you the list of available sub-commands. I'm not going to explain all of them, and I don't even sure what all these sub-commands do, I still learning about molecule.

Run linting tools on your role:

$ molecule lint

Test your role into a freshly created container (by executing playbook.yml on it):

$ molecule converge

molecule converge will automatically lint your code prior to run it.

At this time you can either login to the container to manually check what has been done:

$ molecule login

Or replay molecule converge if you obtained errors and have fixed them in your role.

molecule converge allows us to pass almost any ansible-playbook option. For instance, you may want to debug tasks with a specific tag:

$ molecule converge -- -vvv --tags <tag>

To destroy the running container so you can start with a new one:

$ molecule destroy

If you just want to start a container but don't execute anything in it (for instance if you want to known the state of a file in a brand new install):

$ molecule prepare

To run idempotence check (molecule will simply run the playbook.yml a second time and check that no tasks report a changed state):

$ molecule idempotence

All these previous commands are useful while you are developing a role. Once you have done or did only minors edits (you are pretty confident that you didn't break anything), it's handy to run all the test suite at once:

$ molecule test

It will lint your code, start a new container, apply playbook.yml, verify idempotence, run unit tests and finally destroy the container.

March 31, 2019 06:30 PM

March 29, 2019

Romain Dessort

[Ansible] sudo password from your password manager or agent

Make Ansible retrieve the sudo password from my password manager. For a long time, I was wondering how to solve this issue. Ansible is my main tool to manage my own servers. All the infrastructure of the Services FACiLes project I contribute to (a future CHATONS is built with Ansible automation in mind, and even at my previous work, Ansible is used on a daily basis.

A thing that really annoyed me was that each time I run a playbook, I have to give Ansible my sudo password. Besides that, Ansible prompts for the password at an early stage of execution, even before trying to include all files or running a syntax check on them. Thus it isn't uncommon that Ansible fails immediately after because of a mistake I made in a playbook, and I have fix it, rerun the command, reenter my password… and so on. Which is very frustrated.

I know some workarounds exist to avoid dealing with the interactive sudo password prompt: allowing Ansible to be executed without password on the servers (almost equivalent to allowing any command to be executed without password…, which is not an acceptable answer), storing your password into ansible_sudo_pass variable in a cleartext file somewhere (yes you can encrypt it with ansible-vault but then you have to give the vault password each time you run your playbook), trying to mess with except

The simple and elegant way of doing it for me would be that Ansible gets the password from an agent, like gpg-agent, or execute an arbitrary command to get it. After a lot of research and experimentation, I found a pretty nice and clever solution on Stackoverflow (far at the bottom, it should be upvoted more than that IMHO). The idea is to combine --extra-var ansible option (to which a file descriptor can be passed, while prefixing it with @) and <() bash operator to convert a command's stdout to a file virtual file descriptor. We end up with the following command:

$ ansible-playbook -e@<(echo "ansible_sudo_pass: $(pass Sysadmin/xxxx)") playbook.yml

This way my sudo password is retrieved from my pass's password manager (which uses gpg-agent) and I'm not bothered anymore with Ansible's interactive prompt \o/.

The only drawback is that I didn't find a way to set this into the ansible.cfg or somewhere else, except writing a bash alias or a small wrapper.

March 29, 2019 09:47 PM

March 26, 2019

Pierre Boesch

Debian : Installer la dernière version de Firefox

Debian propose dans ses paquets la version Firefox ESR qui peut être adapté à certains cas d'usage mais pour d'autres, il est frustrant de ne pas pouvoir bénéficier des dernières features. Sur wheezy, et pendant un temps sur jessie, il était possible d'utiliser un dépôt de l'équipe Debian Mozilla pour installer la dernière version de Firefox et Icedove. Finalement, il y a une méthode simple qui consiste à récupérer et extraire une archive contenant le binaire Firefox. C'est d'ailleurs la méthode décrite dans la documentation officielle (chapitre Installing outside of a package manager).

Idée générale

L'idée générale de l'opération se compose de 3 points :

  • télécharger l'archive de la dernière version ,
  • extraire dans un dossier (par exemple /opt),
  • exécuter le binaire firefox.

Pas plus compliqué que sur Windows ;-). En plus de ça, les mises à jours se font automatiquement.

Dernière information, les profils dans ($HOME/.mozilla/firefox) seront évidemment utilisés.

Que vous utilisiez la méthode manuelle ou automatique, comme expliqué juste au-dessus, l'opération n'est à faire qu'une seule fois : lorsqu'une mise à jour est disponible elle sera téléchargée automatiquement, il ne restera qu'un restart du navigateur à effectuer.

Installation pas à pas

Je reprends rapidement ce que décris la documentation et finirai par un playbook ansible qui automatiser ça.

Avec la cli

Mozilla a la bonne idée de tagger une archive latest qui pointe vers la dernière release stable de Firefox.

wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=fr" -O /tmp/firefox.tar.bz2
tar xjf /tmp/firefox.tar.bz2
sudo mv /tmp/firefox /opt
pkill firefox
/opt/firefox/firefox

Si la dernière commande ouvre Firefox, c'est gagné. Sinon peut-être qu'il manque des librairies ?

Tout ça c'est bien mais devoir ouvrir un terminal pour lancer Firefox ça devient vite chiant. Avec un window manager type i3 le problème est vite réglé mais sur un DE Gnome-like ?

Application Firefox

Chaque application graphique dispose d'un fichier .desktop dans /usr/share/applications. Sur Debian, comme Firefox ESR est pré-installé, le boulot est à moitié mâché.

On duplique le fichier pour notre propre version de Firefox :

sudo cp /usr/share/applications/firefox-esr.desktop /usr/share/applications/firefox.desktop

Les paramètres à modifier dans ce nouveau fichier sont : Name, Exec et Icon.

- Name=Firefox ESR
+ Name=Firefox

- Exec=/usr/lib/firefox-esr/firefox-esr %u
+ Exec=/opt/firefox/firefox %u

- Icon=firefox-esr
+ Icon=/opt/firefox/browser/chrome/icons/default/default128.png

L'application "Firefox" doit être à présent accessible par le DE : typiquement dans le menu "Recherche". Il est donc possible de définir cette nouvelle application dans "Preferred Applications" pour le Web.

Automatisation

Parce que j'utilise ansible pour configurer la base de mon poste de travail, je me suis crée une task automatisant les étapes ci-dessus. Les tasks font parties de mon rôle "base" mais celui-ci n'étant pas (encore ?) publique, j'ai mis à disposition sur mon github, un playbook reprenant l'idée générale. Le playbook se trouve ici : https://github.com/pboesch/ansible-firefox-deb.

Je pars du principe que ansible est installé :

git clone https://github.com/pboesch/ansible-firefox-deb
cd ansible-firefox-deb
ansible-playbook playbooks/localhost.yml -K

Un extrait :

- name: Install latest stable Firefox and latest beta Thunderbird
  hosts: localhost
  become: True

  tasks:
    - name: download and extract latest firefox
      unarchive:
        src: "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=fr"
        dest: /opt
        remote_src: yes

La playbook installera aussi la dernière version bêta de Thunderbird.

by Pierre Boesch at March 26, 2019 11:00 PM

March 14, 2019

Romain Dessort

Les pistes de Saint-Hippolyte

Qui aurait penser qu'il y avait des anciennes pistes de ski nordique vers Saint-Hippolyte ? C'est un coin semble-t-il assez peu connu à part par les locaux qui fréquentent ces pistes là. En effet aucune ressource sur Internet n'en parle et aucune carte de ce secteur qu'on peut se procurer (à ma connaissance). Mais il y avait bel et bien un dense réseau tout autour de Prévost (Shawbridge), puisque c'est là où habitait Herman S. Johannsen. Le secteur de Prévost, Piedmont et Saint-Hippolyte est protégé par la Réserve Naturelle du Parc des Falaises contre (notamment) le développement immobilier, ce qui est une très bonne chose. Mais c'est aussi un secteur littéralement envahi par les raquetteurs, ce qui ne donne jamais trop envie d'aller l'explorer à ski. Pourtant au delà d'une certaine distance des points d'accès, les sentiers deviennent moins tapés par les raquettes et on peut retrouver de la belle neige.

Je m'y suis rendu pour la première fois avec des membres du CMC qui connaissaient un peu le coin, puis à quelques jours près, Barclay s'y était aussi aventuré (en deux fois). J'y suis donc retourné seul avec pour mission de cartographier le plus de pistes possible.

J'ai ainsi pu ajouter un bon bout de la Fd (Flight Delight), la plupart de la 6X, qui continuait même plus au nord en contournant le lac Desjardins, la partie centrale de la Johannsen (que j'aimerai vraiment pouvoir relier au complet, j'ai en tête de partir de Mont-Rolland et d'aller jusqu'à Prévost en la suivant), et enrichie un peu la WN, Wizzard, la 8 et quelques autres.

Cela n'a pas été une tâche facile, surtout dans le secteur de Prévost, non pas que les pistes ne sont plus visibles, mais au contraire, certaines pistes ont été reroutées, donc on peut tomber sur les anciens tracés avec quelques vieilles balises rouillées qui ont bravées l'épreuve du temps, les nouveaux sentiers qui reprennent les mêmes noms mais qui ne correspondent plus du tout au tracé original et les sentiers informels créés par les randonneurs eux-mêmes, sans compter que plusieurs pistes fusionnent ensemble sur une certaine distance avant de se reséparer. Bref, quoi cartographier dans OpenStreetMap n'a pas été chose facile et je ne suis pas certain que chaque sentier y est correctement nommé, mais c'est déjà beaucoup mieux qu'avant.

J'ai aussi suivi la MOC (McGill Outing Club) aussi loin que possible avant d'arriver dans un terrain privé. Elle n'a pas beaucoup d'intérêt (peu de défis et pas très jolie, elle à l'air d'un vieux chemin de coupe) et est pas mal envahie aussi, mais elle sert d'artère centrale aux autres pistes du secteur. Et j'ai trouvé une piste mystère (elle aussi pas mal envahie) qui remonte de la MOC vers le Mont Olympia, juste balisée avec des disques de plastiques jaune.

C'est dans le secteur de Saint-Hippolyte que j'ai trouvé une précieuse aide dans ma tâche : 2 cartes différentes, présentes à 2 intersections :

Carte du Parc des Falaises, secteur Saint-Hippolyte Carte de la Réserve Ogilvy, Saint-Hippolyte

La première n'est pas très intéressante car elle ne montre que quelques sentiers, l'autre est déjà plus complète et m'a bien servie pour m'aider à y voir plus clair dans ce réseau, même si elle n'est pas tout à fait complète non plus. Ces 2 cartes ne semblent pas être trouvable sur Internet, donc à conserver précieusement !

J'en profite aussi pour laisser un lien vers une carte éditée par Plein-Air-Saint-Hippolyte, organisme qui ne semble plus exister depuis une dizaine d'année, qui montre encore d'autres sentiers de ski, cette fois au nord de Saint-Hippolyte (merci Archive.org !). Je ne suis pas du tout allé explorer ce coin là par contre.

Un autre article de Barclay tout récent à propos de ce secteur, après qu'il a vu les nouveaux sentiers cartographiés dans OpenStreetMap (j'ai l'impression qu'on joue au chat et à la souris à chaque fois !).

March 14, 2019 10:20 PM

March 13, 2019

Romain Dessort

Le nord de la Western

La Western est une autre ancienne piste de ski dans les Laurentides qui, comme son nom de laisse penser, se trouve dans la partie ouest de la région, à l'ouest de la vallée de la rivière du Nord et l'ancienne voie du CP Railway. La partie probablement la plus connue se trouve dans le réseau de Morin-Heights et apparait sur la carte de leur réseau pour une dizaine de kilomètres, partant des pistes de fond tracées jusqu'au lac Théodore au Nord. C'est la seule partie présente sur une carte. Mais la Western va en fait bien au delà !

Au sud, je l'ai trouvée aussi bas qu'à Sainte-Anne-des-Lacs, au niveau de la Loken, où j'ai été surpris d'y trouver une vieille balise de la Western. Je suppose qu'elle monte plein nord vers Morin-Heights, mais au vu du développement résidentiel de Saint-Sauveur qui se trouve entre les 2, je doute malheureusement qu'il en reste grand chose et, si c'est encore possible de passer, ça risque d'être beaucoup sur la route. Je ne suis pas allé explorer ce secteur.

Section du lac Théodore à la Fleur-de-Lys

Par contre beaucoup plus au nord, je l'avais encore croisée l'année passée en remontant la Fleur-de-Lys de Sainte-Adèle à Sainte-Agathe-des-Monts, peu avant que la Fleur-de-Lys connecte la Canadienne. Dernièrement je suis donc retourné à cette intersection pour suivre la Western le plus loin possible. D'abord vers le sud, jusqu'au lac Théodore. À l'endroit de l'intersection avec la Fleur-de-Lys, la Western qui part vers le sud n'est pas forcement évidente à retrouver, il faut en fait suivre un chemin privé vers le sud sur une cinquantaine de mètres. Ensuite de ça le tracé est très bien visible et balisé jusqu'au Lac Théodore, aucune difficulté particulière à le suivre. Il y a peu de dénivelé mais j'ai trouvé le sentier très joli, notamment le long du lac Normand, à travers une belle forêt de cèdre.

Balises de la Western Balises de la Western

On arrive au Lac Théodore en marchant/skiant un bout sur l'avenue des Pivouanes, qui débouche sur une mini-baie privée sur le lac. Pour rejoindre la Western du réseau de Morin-Heights, il suffit de mettre cap plein sud sur lac pour retomber dessus sur l'autre rive.

Après avoir reviré de bord une fois rendu au lac Théodore, j'ai tenté d'explorer la section au nord de l'intersection à la Fleur-de-Lys. La piste était un peu plus envahie par les branches et moins évidente à suivre. J'ai pu la skier jusqu'à un lac sans nom au nord de la Colline Beauvais. Mais j'ai perdu son tracé après. J'ai appris par contre jusqu'où elle va : elle doit traverser le lac Paquin, traverser le chemin du 7ème Rang et arriver tout au bout du Chemin des Pins à Sainte-Agathe.

Direction Sainte-Agathe On peut deviner un « Direction Sainte-Agathe »

Section de la Fleur-de-Lys à Sainte-Agathe

Bon, je n'ai pas eu le temps de publier cet article que je suis déjà retourné sur les traces de la Western ! Je rajoute ici ce qui devait être un second article.

Comme j'ai su que la Western pouvait se prendre tout au bout du Chemin des Pins à Sainte-Agathe, je me suis rendu là lundi dernier. Le Chemin des Pins se termine dans un camp de vacances, complètement abandonné pour la saison hivernale. Après avoir tourné un peu rond dans le camp, j'ai fini par trouver la piste ; elle file assez proche de la route avant de partir plein est. Son extrémité nord se trouve en fait 500 m plus loin puisqu'elle joint directement la Fournelle, piste tracée du centre de ski de fond du Camping Sainte-Agathe.

Après ça, la piste contourne la Colline aux Framboises par le sud puis file vers le sud-est en coupant les 8ème et 7ème rangs, jusqu'à arriver au lac Paquin. Un peu avant de croiser le 8ème rang, on se retrouve face à une descente toute droite assez impressionnante, digne d'une pente de ski alpin (je n'en ai jamais vu des aussi raides et longues ailleurs) ! Heureusement elle n'est pas trop étroite et comme j'ouvrais dans 40 cm de neige lourde, j'ai pu faire quelques beaux virages de telemark assez facilement (en fait un des rares moments durant cette journée où j'ai pu profiter de la glisse). Avec des conditions de neiges plus difficile par contre, je ne pense pas être capable de la descendre…

Pente de la Western La photo ne rend pas du tout de la pente.

On arrive au chemin du 7ème rang par la Rue du Continental, puis il faut marcher 50 m sur le rang vers l'est avant de pouvoir embarquer sur le lac (avant de traverser la décharge du lac). Sur le lac, je n'avais pas la moindre idée d'où aller, donc j'ai longé la rive est qui offre de belle falaises et cascades de glace. Mais il faut en réalité mettre le cap plutôt vers le sud-sud-ouest pour retomber sur le chemin Gascon Est. En réalité on peut tout aussi bien marcher/skier sur le chemin du 7ème rang pour arriver sur le chemin Gascon Est, ce sera certainement plus rapide si il n'y a aucune trace sur le lac et que les chemins ne sont pas totalement déneigés pour permettre de ski dessus.

La piste reprend depuis le chemin (facile à trouver, il y a une balise) et continu plein sud en passant à l'ouest d'une petite colline, jusqu'aux lignes électrique qu'il faut traverser. La piste traverse de bout-en-bout un lac sans nom, celui-là où je m'étais rendu la fois précédente. Après ça, la Western continue sud-ouest jusqu'à la Fleur-de-Lys.

Globalement, cette section de la Western reste assez bien balisée même si, sans connaitre son tracé approximatif, elle reste difficile à suivre à certains endroits où le balisage est un peu plus aléatoire (érablières clairsemées, traversées de lacs et intersections avec des chemins et rangs).

J'ai continué jusqu'à Sainte-Adèle en suivant la Fleur-de-Lys jusqu'au bout, très belle piste que j'aime beaucoup aussi, mais celle là je l'avais déjà cartographiée l'année passée.

Fleur-de-Lys Ancienne balise de la Fleur-de-Lys.

La Western est maintenant complète pour sa portion au nord de Morin-Heights dans OpenStreetMap.

March 13, 2019 10:34 PM

March 01, 2019

Romain Dessort

Automatically backup your Android phone with udev

I wrote a simple script and udev rule a few years ago to automatically backup my phone (running Replicant OS, a free, FSF-approved Android distribution) to my computer when I plug it to a USB port. I just published those scripts after some clean-up on a git repository and wrote instructions on how to set it up.

I know there are plenty of Android applications to backup my data and upload them somewhere via Internet. But my phone is rarely connected, the Wi-Fi is usually turned off and I have no data plan. Instead I do plug my phone to my computer on a regular basis to recharge it, so it seemed obvious to automatically trigger a backup of my phone via USB.

udev can be used to detect events, like plugging-in a device, and trigger actions based on these events. In my case, the udev rule looks like this:

ACTION=="add", SUBSYSTEMS=="usb", ENV{ID_SERIAL_SHORT}=="XXXXXXXXXXXXXXXX", TAG+="systemd", ENV{SYSTEMD_USER_WANTS}+="android-backup@$env{ID_SERIAL_SHORT}.service"

Where ID_SERIAL_SHORT is used to uniquely identify my phone. The rule triggers the android-backup@.service systemd unit (in user mode, no need to run it as root).

The systemd unit is very simple, there is no need to specify an [Install] section, targets or anything like that since it is triggered directly by udev:

[Service]
Type=oneshot
ExecStart=%h/.local/bin/android-backup.sh %I

So the unit simply calls the following shell script:

#!/bin/sh

export DISPLAY=0:0
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
BACKUP_DIR=~/replicant-backup/
RSYNC_EXCLUDE=~/.config/android-backup.exclude

notify-send "Android Backup" "Starting Android backup"

(
set -e

mkdir -p "$BACKUP_DIR"
adb wait-for-device 2>&1 |(grep -Ev "^(\* daemon not running; starting now at|* daemon started successfully$)" || true)
adb root
adb wait-for-device
adb shell rsync --daemon --no-detach --config=/system/etc/rsyncd.conf &
adb forward tcp:6010 tcp:873
sleep 2
rsync -av --chmod u+rwX --delete --delete-excluded --exclude-from "$RSYNC_EXCLUDE" rsync://localhost:6010/root/ $BACKUP_DIR
adb forward --remove tcp:6010
adb shell pkill rsync
)

if [ $? -ne 0 ]; then
    notify-send "Android Backup" "Backup terminated with errors. See journalctl --user -eu android-backup for details"
else
    notify-send "Android Backup" "Backup terminated successfully"

In this script, I first start adb (as root so it can have access to the whole system) and start a rsync daemon on the OS running on the phone and forward its TCP port through adb. I can then start a normal rsync from my computer to fetch all the files and directories. The destination directory (on my computer) is included in my daily backup, thus I automatically have incremental copies of my phone's data.

March 01, 2019 06:20 PM

February 16, 2019

Alexandre Bailly

USB Gadget - ethernet à travers USB

Cet article est aussi présent dans mes pages de documentation.
https://docs.nah.re/raspberry_pi/usb-otg-g-ether.html


Avant-propos

Ceci est un petit tutoriel en français, sur l'intégration de g_ether sur un Raspberry PI Zero.

Un minimum de connaissances de GNU/Linux et de la ligne de commande est nécessaire.

Photo d'un Raspberry PI Zero, dans son boîter blanc.

Un Raspberry PI Zero.

Installation de Raspbian

Premier détail : je n'ai pas copié une raspbian lite ou full sur la microSD, parce qu'on n'a pas du tout la même définition de léger.

Du coup, je suis parti sur raspberrypi-ua-netinst https://github.com/FooDeas/raspberrypi-ua-netinst

L'avantage d'une installation en unattended + netinst, c'est que l'on définit les paramètres dans un fichier de configuration, on branche un câble réseau (via un adaptateur micro USB OTG + une carte ethernet USB), on démarre et on laisse tourner.

Une fois l'installation terminée, je me suis connecté sur le Raspberry PI via SSH (toujours via le réseau).

Intégration de g_ether

Maintenant, on peut s'attaquer à l'intégration du gadget USB Ethernet (g_ether).

Se connecter avec le compte root (peu importe la méthode on va modifier des fichiers et installer des trucs)

Ouvrir /boot/config.txt. Ajouter à la fin du fichier, après [all]

#USB G_Ethernet
dtoverlay=dwc2

Si vous lisez des vidéos avec omxplayer et que celui-ci râle avec le message

COMXAudio::Decode timeout

Ajoutez dans le même fichier (/boot/config.txt)

gpu_mem=128

Enregistrer et fermer.

Ouvrir /boot/cmdline.txt

ajoutez à la fin de la commande.

modules-load=dwc2,g_ether

N'insérez surtout pas de saut de ligne.

La commande pourrait ressembler à ça :

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty3 elevator=deadline fsck.repair=yes root=/dev/mmcblk0p2 rootfstype=ext4 rootwait modules-load=dwc2,g_ether quiet logo.nologo loglevel=3 vt.global_cursor_default=0

Ici, j'ai configuré pour que tout soit silencieux, avec aucun message sur la sortie vidéo. Ne copiez pas bêtement la commande sans savoir à quoi correspond quiet ou vt.global_cursor_default=0. Ce n'est pas le but de ce tuto rapide, je ne détaillerai pas à quoi ça correspond.

Enregistrer et fermer le fichier.

Configuration du réseau

Pour éviter de se prendre la tête avec l'attribution de l'adresse IP une fois la carte branchée sur un PC (ou MAC) en USB, j'installe un serveur DHCP qui fera cette tâche automatiquement.

Installer le paquet isc-dhcp-server

apt install isc-dhcp-server

Activer le service (activer, ne pas démarrer tout de suite) avec systemd

systemctl enable isc-dhcp-server

On configurera le serveur DHCP juste après. On va d'abord configurer le réseau pour g_ether.

Ouvrir le fichier /etc/network/interfaces. Y ajouter :

# Ethernet over USB
allow-hotplug usb0
iface usb0 inet static
address 10.10.10.10
netmask 255.255.255.0
broadcast 10.10.10.255
gateway 10.10.10.10

Ici, j'ajoute une interface usb0, qui a une adresse IP 10.10.10.10. Cette IPv4 étant dans les plages réservées pour un usage privé, cela ne posera pas de problème ni de conflit. usb0 sera créé lorsque le raspberry pi sera branché en USB.

Enregistrer et fermer.

Ouvrir maintenant /etc/dhcp/dhcpcd.conf

Supprimer tout le contenu et insérer :

default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
authoritative;
log-facility local7;

subnet 10.10.10.0 netmask 255.255.255.0 {
range 10.10.10.11 10.10.10.20;
option routers 10.10.10.10;
option broadcast-address 10.10.10.255;
default-lease-time 600;
max-lease-time 7200;
option domain-name "local";
}

Les cinq premières lignes sont pour le paramétrage du serveur dhcp, les huit lignes en bas correspondent à la configuration du réseau.

Je définis le réseau comme étant sur 10.10.10.0, s'étendant jusqu'à 10.10.10.255 via le masque réseau.

Je définis ensuite la plage allant de 10.10.10.11 à 10.10.10.20 (ce qui me sera suffisant).

J'indique que le routage se fera sur 10.10.10.10, qui correspond à l'adresse du Raspberry PI (que j'ai défini dans /etc/network/interfaces).

Le reste n'est pas vraiment important (durée des sessions dhcp, nom de domaine local).

Enregistrer et fermer.

Ouvrir maintenant /etc/default/isc-dhcp-server

Décommenter les lignes

DHCPDv4_CONF=/etc/dhcp/dhcpd.conf
DHCPDv4_PID=/var/run/dhcpd.pid

Ajouter usb0 dans INTERFACESv4, comme ceci :

INTERFACESv4="usb0"

Enregistrer et fermer.

Tests

Éteindre le Raspberry PI

sudo poweroff

Tout débrancher, puis brancher un câble micro USB (data, pas charge seule) sur le port "USB" du Raspberry (pas sur "pwr IN"), brancher l'autre côté du câble USB sur un PC.

Photo d'un Raspberry PI Zero avec un câble Micro USD branché sur le port USB.

Branchement du câble micro USB.

Après quelques dizaines de secondes, une interface réseau devrait apparaître (sous Windows, il y ait de fortes chances que le pilote rdnis ne s'installe pas automatiquement, il faudra l'installer manuellement via ajouter un pilote, choisir le pilote, type carte réseau, fabricant Microsoft, sélectionner rdnis ou un truc s'en approchant, répondre oui à l'avertissement comme quoi le pilote n'est peut être pas compatible).

Vérifiez si le Raspberry PI est bien reconnu comme une carte réseau.

Sous Windows

ipconfig /all

Sous Mac OS X et autres BSD

ifconfig

Sous GNU/Linux

ip a

Normalement, quelques lignes indiquant

inet 10.10.10.17/24 brd 10.10.10.255 scope global dynamic noprefixroute enp0s19f2u1

ou

inet 10.10.10.17  netmask 255.255.255.0  broadcast 10.10.10.255

ou un truc indiquant qu'il y a une IP commençant par 10.10.10 devrait s'afficher.

Si c'est le cas, taper

ping 10.10.10.10

S'il y a une réponse, cela signifie que cela fonctionne.

PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data.
64 bytes from 10.10.10.10: icmp_seq=1 ttl=64 time=0.504 ms
64 bytes from 10.10.10.10: icmp_seq=2 ttl=64 time=0.543 ms
64 bytes from 10.10.10.10: icmp_seq=3 ttl=64 time=0.539 ms
64 bytes from 10.10.10.10: icmp_seq=4 ttl=64 time=0.542 ms
^C
--- 10.10.10.10 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 111ms
rtt min/avg/max/mdev = 0.504/0.532/0.543/0.016 ms

Il n'y a plus qu'à se connecter dessus.

ssh pi@10.10.10.10

Si vous avez pu vous connecter via ssh à travers la connection USB, félicitations.

Conclusion

Ce n'est pas très compliqué à mettre en place, le problème étant de réunir les différentes informations éparpillées un peu partout sur le net, et souvent en anglais (ce qui ne me pose pas de problème, mais ce n'est pas le cas de tout le monde).

Remerciements

Un gros merci à Albert, pour m'avoir prêté son Raspberry PI Zero.

by nah at February 16, 2019 04:49 PM

February 12, 2019

Romain Dessort

Marathon Canadien de Ski

Le Marathon Canadien de Ski (MCS) est la plus longue épreuve de ski de fond en Amérique du Nord, 160 km (163,7 km exactement), qui se répartissent en 2 jours soit à peu près 80 km par jour. C'est aussi la plus ancienne épreuve en Amérique, il a été tenu pour la première fois en 1967, lors des 100 ans du Canada. Il s'agit plus d'un évènement populaire que d'une compétition, les skieurs ne sont pas chronométrés et sont là plus pour le plaisir de skier et le défi personnel que la performance. L'évènement attire en moyenne 2000 personnes, venant principalement du Québec, de l'Ontario et du nord-est des États-Unis.

La piste reliait à l'origine les villes de Gatineau et La Chute en longeant plus ou moins la rivière des Outaouais. Montebello était le point central et l'étape intermédiaire. À cause de l'enneigement de plus en plus incertain ces dernières années, il part maintenant d'un peu au sud de Saint-Jovitte (Mont-Tremblant) pour se rendre à La Chute, en faisant un crochet par Montebello, qui reste l'étape intermédiaire. La piste est tracée une seule fois par année pour l'évènement et passe en majorité sur des terres privées.

Pourquoi j'en parle ici ? Ça me titillait depuis que j'en ai entendu parlé en arrivant au Québec, mais cette année je m'y suis inscrit et suis très fier d'avoir skier l'intégralité des 160 km en 2 jours ! Je fais un petit retour sur mon expérience.

Il y plusieurs catégories lors de l'inscription, on peut soit faire le demi-marathon, le marathon entier (Coureur des Bois) ou les sections que l'on veux (Randonneur) (le parcours est divisés en 10 sections de 16 km chacune en moyenne). Jusqu'au dernier moment avant mon inscription, j'ai beaucoup hésité dans quelle catégorie m'inscrire. J'étais plus partant pour faire le marathon entier au début, puis à un moment j'ai douté beaucoup, je n'arrivais pas à m'entrainer autant que je voulais (on a eu un décembre sans neige) et j'ai fini par m'inscrire dans la catégorie Randonneur. Comme ça je me suis dit que si je ne skiais pas les 10 étapes, je ne perdais pas le défi. Le prix a joué aussi, car les frais de participation sont assez cher : 300 $ pour les Coureurs des Bois contre 200 $ pour les Randonneurs. Puisque les Randonneurs peuvent quand même skier l'intégralité du marathon, les 100 $ ne se retrouvent que dans la médaille à la fin, ça ne justifiait pas la différence pour moi. Surtout qu'à cela, il fallait ajouter les frais d'hébergement pour vendredi et samedi (150 $ pour dormir par terre dans les salles de classe de l'école de Papineauville et manger à la cantine de l'école) + plus la navette depuis Montréal (100 $), plus toutes les taxes… Bref ça faisait un sacré budget et faisait cher le kilomètre skié. Mais je dois admettre que c'était toute une organisation sur place vu le nombre de participant et c'était assez bien rodé.

La veille du marathon, j'avais encore changé d'avis et je voulais à tout pris skier les 80 km par jour. Sauf qu'une chose que j'ai appris au dernier moment est que les Randonneurs qui veulent skier les 10 sections sont clairement pénalisés par rapport aux Coureurs des Bois : les Coureurs des Bois partent à 5h, le départ des Randonneurs est à 7h30 et il y a un cutoff à 15h30 pour tout le monde au départ de la dernière section de la journée. Ça laisse beaucoup moins de temps aux Randonneurs ! Donc au final, je me retrouve avec un défi supplémentaire, skier les 4 premières sections de chaque jour plus vite que les Coureurs des Bois pour arriver avant le cutoff de 15h30.

Dans ma tête, c'était donc un défi de 66 km à skier en 8h, pauses comprises, puis les 15 km de la dernière section du jour que je pouvais skier à la vitesse d'un piéton si je serai à bout, ça n'aura plus d'importance. J'ai calculé précisément mes heures de passages aux différents checkpoints. Je devais skier à une vitesse minimum de 9 km/h et pouvais m'arrêter maximum 15 minutes à chaque checkpoint pour refaire le plein d'eau, manger et refarter mes skis si besoin. La veille, j'ai appris par cœur mes heures de passage et j'ai mémorisé le plus de détails possible des cartes des sections (traverses de routes, de lacs, grandes côtes, passages dans les champs) de façon à me donner des points de repères une fois sur la piste pour situer mon avancement ; 80 km c'est long et il est facile de se « perdre » dans les chiffres et d'avoir l'impression de ne pas avancer si dès le départ au matin l'objectif est le 80 ème kilomètre. Voir que j'arrive effectivement à telle place alors que j'en suis qu'au 38 km est très motivant et rassurant à la fois. Et je me prépare ensuite à atteindre le prochain repère, peut-être juste 10 km plus loin. En fait, je me fous complètement du tout dernier kilomètre.

De manière générale, je suis (assez involontairement mais maintenant que j'en ai conscience ça devient moins involontaire) pessimiste concernant mes objectifs, ce qui fait que je les surpasse bien au delà et ça devient une source de motivation plus grande. Dans ce cas si concrètement, mes heures de passage étaient vraiment le minimum, le cas vraiment limite (je skie plus vite que 9 km/h), donc à chaque fois j'arrivais toujours avec 5 ou 10 minutes d'avance sur ce que j'avais en tête. Là encore, ça me rassurait et me motivait pour la section suivante, où je me mettais au défi de maintenir mon avance ou faire encore mieux.

Le premier jour, je suis arrivé à 1h05 avant le cutoff, 1h05 d'avance sur ce que j'avais prévu, et 40 minutes d'avance pour le deuxième jour. J'ai fini la première journée à 16h30 et 16h37 le lendemain, en skiant à peu près 8h à 10,5 km/h de moyenne plus 1h de pause au total. Je skie habituellement entre 10 et 11 km/h et avant le marathon, je doutais être capable de maintenir ma vitesse habituelle sur une aussi grande distance. Mais en faisant des stats par section, je ne constate aucune baisse de vitesse en fin d'épreuve (sauf la dernière section du premier jour où je me ralentissais volontairement pour ne pas me bruler inutilement pour le lendemain). Même, je me sentais bizarrement encore plein d'énergie, en tout cas d'un point de vue mental et cardiaque, mes muscles eux commençaient à fatiguer sérieusement.

Autant j'ai skié plus vite que prévu, autant la durée des pauses passait très rapidement. Le fait est que contrairement à la course à pied, c'est bien moins facile de boire et manger en skiant, avec les bâtons, gants et dragonnes ergonomiques, alors que mon eau est coincée quelque part contre mon ventre pour éviter le gel au lieu d'une simple poche d'eau avec un tuyau comme en trail. Donc je me ravitaillais essentiellement aux checkpoints qui étaient d'ailleurs pas mal bien fourni (eau chaude miellée, soupe, chili, bananes, barres de céréales…).

L'autre chose étaient les conditions de ski assez difficiles : il y a eu plusieurs épisodes de pluie puis du regel la semaine précédent l'évènement et la neige était très transformée. Les 2 jours on a eu un grand beau soleil, bien agréable mais n'améliorant pas les conditions, la température oscillait entre -15 à -3 °C suivant l'avancement de la journée. Bref, des conditions très printanières, la glisse était rapide mais le fartage n'était pas évidant. Malgré une base de liant que j'avais appliqué à chaque fois la veille, le fart de jour ne durait pas longtemps avec les conditions abrasive et je refartais tous les 15 à 20 km en moyenne, et dépendamment de l'évolution de la température (après coup, à des moments j'aurai vraiment du m'arrêter et prendre 5 minutes pour ajuster le fartage au lieu de me fatiguer à skier sans plus aucun kick et me disant que sa attendra le prochain checkpoint). Par contre une chose de vraiment bien, des exposants étaient là à chaque checkpoints pour farter les skis ; j'arrivais, je leur laissais mes skis, je mangeais et buvais un bout puis dès qu'ils avait fini avec mes skis je repartais. Ça me faisait gagner pas mal de temps.

Par rapport à la course à pied, c'est bien plus facile, je ne me verrai pas du tout courir pendant 8h par exemple. Déjà il n'y a pas toute la fatigue du aux chocs à chaque impact, c'est que de la glisse, je peux me reposer en descente (si elles sont pas trop raides) et je peux changer de technique pour reposer plus le haut ou le bas du corps quand l'un ou l'autre fatigue. Je faisais même du pas de patin sur les pistes de motoneige pour varier un peu, preuve que ce n'était pas le cardio mais plus les muscles qui fatiguaient.

Au niveau des paysages traversés, c'était très varié, certaines sections étaient très belles, d'autres moins. On a traversé des pinèdes, des érablières et des forêts de cèdres, des terrains de golf, des champs, passé au travers de fermes, skié le long de routes et dans des vallées, traversé des lacs et des rivières, on a skié dans un tunnel sous l'autoroute… Par moment on était sur des sentiers étroits dans le bois, d'autres fois sur des larges pistes de motoneige.

Finalement je ne regrette pas de m'être inscrit en tant que Randonneur, ça a été un défi supplémentaire par rapport au temps, j'ai pu dormir plus longtemps que les Coureurs des Bois qui se levaient à 3h pour partir à la nuit, et aussi, comme peu de Randonneurs faisaient la première section de la journée, on était qu'un petit groupe de 20/30 personnes à partir au lieu des centaines de Coureurs des Bois qui, eux, partaient tous en même temps et créaient des gros bouchons (de ce qu'on m'a raconté).

D'ailleurs dans la catégorie Coureurs des Bois, il y a une catégorie spéciale pour ceux qui dorment dehors. C'est en fait le défi ultime, skier avec tout son matériel de camping d'hiver et dormir au camp aménagé (avec feu de camp et balles de foin) le samedi soir. Moi personnellement, je ne regrette pas du tout de trouver une douche et un endroit chaud et sec après 80 km de ski. Je m'étais dis à un moment que j'essayerai le camping d'hiver au Québec, mais finalement… ça me tente plus trop !

Bref, le Marathon Canadien de Ski aura été pour moi une superbe expérience, j'en garde plein de bon souvenir et je suis pas mal plus confiant et fier de mes capacités en ski de fond. Presque je m'en veux un peu d'avoir douté de mes capacités au moment de l'inscription !

Départ du jour 2 Dernier checkpoint Médaille

February 12, 2019 11:02 PM

February 11, 2019

Luc Didry

Un dernier merci à mes tipeurs 🙂

Pourquoi un « dernier merci » ?

Et bien… tout simple­ment parce que je vais couper mes pages Tipeee et Libe­ra­pay.

Cela fait plus de deux ans que je reçois des dons pour ce que je fais pour le libre, et ça fait chaud au cœur. Vrai­ment. Énor­mé­ment. Et ça m’a beau­coup motivé à déve­lop­per.

Néan­moins, je consi­dère que je n’ai pas besoin de ces dons, vu que je gagne confor­ta­ble­ment ma vie. De plus, j’avoue, j’éprouve une certaine lassi­tude à parfois me forcer à déve­lop­per pour ne pas lais­ser tomber mes dona­trices et dona­teurs.

Je préfère donc couper mes pages de don.

Si vous souhai­tez faire des dons à d’autres, il y a une chouette dépêche LinuxFr qui liste plein d’as­so­cia­tions qui seraient heureuses de rece­voir des dons. Pis y a le projet Fedi­lab (ancien­ne­ment Masta­lab) qui est chouette : d’un client Masto­don, c’est devenu un client pour plein de service de la fédi­verse, c’est vrai­ment un chouette projet et le déve­lop­pe­ment est très soutenu. Pour les dons, ça se passe ou alors y a la collecte pour que le déve­lop­peur puisse se payer un ordi­na­teur plus costaud pour déve­lop­per dans de bonnes condi­tions.

Ne vous inquié­tez pas, je ne vais pas arrê­ter de faire du libre, mais… disons que je pren­drais sans doute plus mon temps 😉

Et donc… Un dernier merci

Le 14 juillet 2016, j’ai lancé mes pages Tipeee et Libe­ra­pay.

La récom­pense de base est l’ap­pa­ri­tion sur une page mensuelle de remer­cie­ments… voici celle de janvier. Merci à :

Et un groooooooooooooos merci à tout ceux qui m’ont soutenu pendant ces plus de deux ans. Merci du fond du cœur ♥️


Voici mon bilan de janvier :

Crédits : Photo par Alex Hu sur Unsplash

Parta­ger

by Luc at February 11, 2019 04:04 PM

February 03, 2019

Pierre Boesch

RocketChat : chargement infini sur Firefox

Problème incompréhensible au premier abord : après s'être identifié sur une instance RocketChat, le chargement tourne en boucle. L'un des premiers réflexes est de tester un autre navigateur (Chromium par exemple) : ça fonctionne. L'instance RocketChat n'est pas à remettre en cause…

Du coup ça confirme un problème avec Firefox. Deuxième réflexe : vider le cache web. Non ? Non. Troisième réflexe : redémarrer le navigateur sans les extensions. Peut-être que ça débloquera la situation chez vous ; il ne vous restera donc qu'à désactiver petit à petit les extensions pour trouver la coupable. De mon côté, ça ne marchait quand même pas.

Du coup il ne reste plus qu'à débugger… On ouvre la console du navigateur en étant dans l'onglet de RocketChat : Ctrl + Shift + K puis reload F5. On avance :

TypeError: localStorage is null

Autant dire qu'on trouve tout de suite de quoi il s'agit : le localstorage de Firefox est désactivé. Pour le réactiver :

  • about:config dans la barre de recherche
  • dom.storage.enabled le passer à True

On reload à nouveau RocketChat : on peut à nouveau profiter de sa lourdeur et voir son CPU être bouffé petit à petit :)

by Pierre Boesch at February 03, 2019 11:00 PM

January 31, 2019

Pierre Boesch

Fixer les warnings après l'upgrade vers Pelican 4

Depuis l'upgrade de Pelican 3.7 vers Pelican 4, lors de la génération du html avec make on voit rapidement des warnings liés à certains paramètres.

Le travail sur la rétro-compatibilité étant bien géré par les développeurs, les warnings ne sont pas du tout gênant voir bloquant mais autant les corriger avant de ne plus avoir le choix. De mon côté je n'avais que deux warnings. On va voir comment les fixer.

Premier warning probablement classique :

WARNING: {filename} used for linking to staticcontent pdf/CV_BOESCH_Pierre.pdf in pages/about.md. Use {static} instead

Pour fixer, on édite la page/article en question et on remplace la syntaxe {filename} lié à un contenu statique par une syntaxe plus explicite : {static}

vim content/pages/about.md

-Mon CV est consultable [en cliquant ici]({filename}/pdf/CV_BOESCH_Pierre.pdf).
+Mon CV est consultable [en cliquant ici]({static}/pdf/CV_BOESCH_Pierre.pdf).

Second warning se corrigeant rapidement :

WARNING: %s usage in CATEGORY_FEED_ATOM is deprecated, use {slug} instead.

On grep pour savoir où cette variable est utilisée :

$ grep -ir CATEGORY_FEED_ATOM .
./pelicanconf.py:CATEGORY_FEED_ATOM = None
./publishconf.py:CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'

On ouvre publishconf.py

vim publishconf.py

-CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'

C'est tout ! Les warnings d'un upgrade de Pelican 3 vers Pelican 4 sont fixés !

Le changelog complet de Pelican 4 se trouve ici. Et comme d'habitude pour obtenir de l'aide c'est par là.

by Pierre Boesch at January 31, 2019 11:00 PM

Fixer les warnings après l'upgrade de Pelican 4

Depuis l'upgrade de Pelican 3.7 vers Pelican 4, lors de la génération du html avec make on voit rapidement des warnings liés à certains paramètres.

Le travail sur la rétro-compatibilité étant bien géré par les développeurs, les warnings ne sont pas du tout gênant voir bloquant mais autant les corriger avant de ne plus avoir le choix. De mon côté je n'avais que deux warnings. On va voir comment les fixer.

Premier warning probablement classique :

WARNING: {filename} used for linking to staticcontent pdf/CV_BOESCH_Pierre.pdf in pages/about.md. Use {static} instead

Pour fixer, on édite la page/article en question et on remplace la syntaxe {filename} lié à un contenu statique par une syntaxe plus explicite : {static}

vim content/pages/about.md

-Mon CV est consultable [en cliquant ici]({filename}/pdf/CV_BOESCH_Pierre.pdf).
+Mon CV est consultable [en cliquant ici]({static}/pdf/CV_BOESCH_Pierre.pdf).

Second warning se corrigeant rapidement :

WARNING: %s usage in CATEGORY_FEED_ATOM is deprecated, use {slug} instead.

On grep pour savoir où cette variable est utilisée :

$ grep -ir CATEGORY_FEED_ATOM .
./pelicanconf.py:CATEGORY_FEED_ATOM = None
./publishconf.py:CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'

On ouvre publishconf.py

vim publishconf.py

-CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'

C'est tout ! Les warnings d'un upgrade de Pelican 3 vers Pelican 4 sont fixés !

Le changelog complet de Pelican 4 se trouve ici. Et comme d'habitude pour obtenir de l'aide c'est par là.

by Pierre Boesch at January 31, 2019 11:00 PM

January 30, 2019

Pierre Boesch

HAProxy : Redirection HTTP en utilisant les maps

Une map est globalement un fichier contenant une structure de keys / values. Si vous avez une liste importante de redirection à faire (un domaine vers un autre, vers URI, etc) il est intéressant de se servir des maps afin de gagner en lisibilité sur la configuration finale de HAProxy. C'est quand même plus sympa d'avoir une ligne de configuration pour 10 domaines que 10 lignes d'acls, non ? :)

Création d'un fichier map

Il faut commencer par créer son premier fichier de map. Personnellement, je les place dans un dossier maps.

mkdir /etc/haproxy/maps

Par convention, j'utilise l'extension .map.

touch /etc/haproxy/maps/domain.map

Maintenant qu'on a notre fichier, il ne reste plus qu'à le remplir…

Les exemples qui vont suivre n'existent pas mais n'hésitez pas à consulter les sites en question ;-)

Redirection d'un domaine vers un second

Prenons le cas le plus classique : redirection de domain.tld vers www.domain.tld

vim /etc/haproxy/maps/domain.map

Admettons que l'on veuille rediriger :

On reprend notre fichier de map crée précédemment et on fait une liste key/value

/etc/haproxy/maps/domain.map

toucher.rectal.digital    www.toucher.rectal.digital
chiffrer.info             www.chiffrer.info

Puis on termine par créer la règle dans la configuration de HAProxy : /etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect code 301 location https://%[hdr(host),map(/etc/haproxy/maps/domain.map)]%HU if { hdr(host),map(/etc/haproxy/maps/domain.map) -m found }

On dit simplement à HAProxy : si tu trouves le domaine appelé (key) alors redirection (301) vers le domaine que je souhaite (value) en gardant l'URI d'origine (%HU).

Avec cette configuration ça ne marchera que pour un domaine (%hdr(host)). Et si l'on veut rediriger une URI vers un domaine en particulier (#SEO) ?

Redirection d'une URI vers un domaine spécifique

Si l'on souhaite cette fois redirigier :

  • www.chiffrer.info/faq vers faq.chiffrer.info
  • toucher.rectal.digital/crypter vers chiffrer.info/onditpascrypter

En application : /etc/haproxy/maps/path.map

www.chiffrer.info/faq              https://faq.chiffrer.info
toucher.rectal.digital/crypter     https://chiffrer.info/onditpascrypter

/etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect location %[base,map(/etc/haproxy/maps/path.map)] code 301 if { base,map(/etc/haproxy/maps/path.map) -m found }

Même explication que précédemment. Si l'on test la première redirection mise en place :

$ curl -I www.chiffrer.info/faq
HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://faq.chiffrer.info

Redirection à base de regex

Avec les regex on va pouvoir travailler sur des redirections plus complexes.

Si l'on reprend en exemple les deux domaines précédents et que l'on veuille rediriger A vers B en récupérant un ID aléatoire dans l'URI ?

Prenons un cas classique :

www.chiffrer.info/faq?id=1337 vers faq.chiffre.info/1337

La map doit ressembler à ça

^www.chiffrer.info/faq?id=[0-9]+$      faq.chiffre.info/\1

Globalement la regex va matcher le numéro d'id et l'ajouter à la back reference (\1).

Pour ce cas précis, il faut disposer de haproxy 1.7 minimum qui introduit la map map_regm.

Toujours dans le frontend :

http-request redirect location %[base,map_regm(/etc/haproxy/maps/reg.map)] code 301 if { base,map_regm(/etc/haproxy/maps/reg.map) -m found }

Cet exemple n'est pas testé mais ça devrait marcher ;-)

Ressources

by Pierre Boesch at January 30, 2019 11:00 PM

Haproxy - Redirection en utilisant les maps

Une map est globalement un fichier contenant une structure de keys / values. Si vous avez une liste importante de redirection à faire (un domaine vers un autre, vers URI, etc) il est intéressant de se servir des maps afin de gagner en lisibilité sur la configuration finale de haproxy. C'est quand même plus sympa d'avoir une ligne de configuration pour 10 domaines que 10 lignes d'acls, non ? :)

Création d'un fichier map

Il faut commencer par créer son premier fichier de map. Personnellement, je les place dans un dossier maps.

mkdir /etc/haproxy/maps

Par convention, j'utilise l'extension .map.

touch /etc/haproxy/maps/domain.map

Maintenant qu'on a notre fichier, il ne reste plus qu'à le remplir…

Les exemples qui vont suivre n'existent pas mais n'hésitez pas à consulter les sites en question ;-)

Redirection d'un domaine vers un second

Prenons le cas le plus classique : redirection de domain.tld vers www.domain.tld

vim /etc/haproxy/maps/domain.map

Admettons que l'on veuille rediriger :

On reprend notre fichier de map crée précédemment et on fait une liste key/value

/etc/haproxy/maps/domain.map

toucher.rectal.digital    www.toucher.rectal.digital
chiffrer.info             www.chiffrer.info

Puis on termine par créer la règle dans la configuration de haproxy : /etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect code 301 location https://%[hdr(host),map(/etc/haproxy/maps/domain.map)]%HU if { hdr(host),map(/etc/haproxy/maps/domain.map) -m found }

On dit simplement à haproxy : si tu trouves le domaine appelé (key) alors redirection (301) vers le domaine que je souhaite (value) en gardant l'URI d'origine (%HU).

Avec cette configuration ça ne marchera que pour un domaine (%hdr(host)). Et si l'on veut rediriger une URI vers un domaine en particulier (#SEO) ?

Redirection d'une URI vers un domaine spécifique

Si l'on souhaite cette fois redirigier :

  • www.chiffrer.info/faq vers faq.chiffrer.info
  • toucher.rectal.digital/crypter vers chiffrer.info/onditpascrypter

En application : /etc/haproxy/maps/path.map

www.chiffrer.info/faq              https://faq.chiffrer.info
toucher.rectal.digital/crypter     https://chiffrer.info/onditpascrypter

/etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect location %[base,map(/etc/haproxy/maps/path.map)] code 301 if { base,map(/etc/haproxy/maps/path.map) -m found }

Même explication que précédemment. Si l'on test la première redirection mise en place :

$ curl -I www.chiffrer.info/faq
HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://faq.chiffrer.info

Redirection à base de regex

Avec les regex on va pouvoir travailler sur des redirections plus complexes.

Si l'on reprend en exemple les deux domaines précédents et que l'on veuille rediriger A vers B en récupérant un ID aléatoire dans l'URI ?

Prenons un cas classique :

www.chiffrer.info/faq?id=1337 vers faq.chiffre.info/1337

La map doit ressembler à ça

^www.chiffrer.info/faq?id=[0-9]+$      faq.chiffre.info/\1

Globalement la regex va matcher le numéro d'id et l'ajouter à la back reference (\1).

Pour ce cas précis, il faut disposer de haproxy 1.7 minimum qui introduit la map map_regm.

Toujours dans le frontend :

http-request redirect location %[base,map_regm(/etc/haproxy/maps/reg.map)] code 301 if { base,map_regm(/etc/haproxy/maps/reg.map) -m found }

Cet exemple n'est pas testé mais ça devrait marcher ;-)

Ressources

by Pierre Boesch at January 30, 2019 11:00 PM

January 24, 2019

Romain Dessort

List changed configuration files and restore the maintainer's version

Recently I had to clean one of my server (a virtual machine hosted by Tetaneutral) to make it as close to a fresh install as possible, but without reinstalling it or migrate it somewhere else. It is an old server and it has been installed by hand. Since I now use Ansible to manage my other servers, I wanted to include it to the inventory and apply the same configuration on it.

On Archlinux, I used to use this command which list me all configuration files (in /etc) which have diverged from the version shipped by the package:

$ pacman -Qii | awk '/^MODIFIED/ {print $2}'

I didn't find a simple equivalent way to do it in Debian with dpkg tools, however I found debsums, an extra package that does the job pretty well:

$ debsums -se

I then can restore the package's version by reinstalling the corresponding package with --force-confask option:

# apt install --reinstall -o Dpkg::Options::="--force-confask"

Here is an oneliner to automate the process (careful, it doesn't ask for any confirmation!):

# debsums -se 2>&1 |awk '{print $4}' |xargs -n 1 dpkg -S |awk -F: '{print $1}' |sort -u |xargs apt install --reinstall -o Dpkg::Options::="--force-confask" -o Dpkg::Options::="--force-confnew"

Since xargs doesn't work well with interactive commands, I force the choice by passing both --force-confask and --force-confnew options.

As a conclusion, keep in mind the configuration files added in *.d/ directories won't be listed nor removed, so you still have to clean these directories manually.

January 24, 2019 01:43 AM

January 20, 2019

Florent Peterschmitt

Namasté Népal - #4.2

Article précédent

La suite des photos de montagne, le 23 avril 2017 ^^

photo photo photo

Notre pote singe blanc de la vielle ? Nah, il y en avait tout de même plusieurs ^^

photo photo

La personne en bas à droite c’était mon guide. On parlait tout deux suffisemment d’anglais pour pouvoir discuter de choses et d’autres, de la situation du pays, de sa vie etc…

photo photo photo photo photo photo

On commence à sortir de la forêt où la température était écrasante et l’absence de circulation d’air rendait la marche franchement difficile.

Maintenant c’est tout l’inverse : le vent souffle et il est froid ! Je me suis sévèrement enrhumé à ce moment là d’ailleurs… avec fièvre et tout le tintouin.

photo photo photo

photo photo photo photo photo photo

Sur les prochaines photos, on observe ce qu’il reste de la montagne qui s’est effondrée lors du tremblement de terre du 25 avril 2015 dans le pays.

Un village d’environ 200 habitants a été rasé et beaucoup y sont morts.

photo photo photo

Le village a été reconstruit plus haut et c’est là qu’on passera la nuit.

photo

Article suivant

by Florent Peterschmitt at January 20, 2019 08:30 PM

January 14, 2019

Luc Didry

Instal­ler un serveur Bitwar­den_rs

Avoir des mots de passe solides, c’est bien (et même indis­pen­sable), mais s’en souve­nir… c’est dur. On serait tenté de tout enre­gis­tré dans son navi­ga­teur, avec un mot de passe maître pour stocker les mots de passe de façon chif­frée mais… Fire­fox par exemple a un système de chif­fre­ment par mot de passe maître tout pourri.

Il existe un paquet de gestion­naire de mot de passe libre. Évacuons tous ceux basé sur KeePass ou ses succes­seurs : pour utili­ser ses mots de passe sur un autre PC ou sur son télé­phone, il faut synchro­ni­ser le fichier conte­nant les mots de passe et s’il y a un souci de synchro et que le fichier se retrouve corrom­pu… paf, pastèque !.

J’ai essayé Pass­bolt ou encore l’ap­pli­ca­tion Pass­man de Next­cloud mais quand j’uti­li­sais la sécu­rité maxi­male qu’ils propo­sent… mon Fire­fox plan­tait (oui, quand on me propose des réglages de parano, je les utilise). Sans comp­ter qu’ils ne proposent pas d’ap­pli Android.

Et puis, j’ai testé Bitwar­den. Et c’est pas mal du tout, mais… c’est du C# et ça utilise une base de données SQL Server. Deux trucs Micro­soft.

Bon, passant outre mon aver­sion, j’ai testé. Dans du Docker (quand je vous dis que je suis passé outre mon aver­sion !). C’était pas mal du tout.

Mais en voulant tester le partage de mot de passe (dans l’hy­po­thèse d’une utili­sa­tion chez Frama­soft), j’ai rencon­tré une limi­ta­tion : pour créer une orga­ni­sa­tion (un groupe de personnes qui se partagent des mots de passe) de plus de deux personnes, il faut payer, même quand on héberge soi-même le serveur Bitwar­den. Pourquoi pas. C’est un busi­ness model comme un autre (mais bon, grmpf).

Par contre, les tarifs, c’est un très gros grmpf : 3 dollars par utili­sa­teur et par mois. Pour Frama­soft, cela ferait 3$ * 35 personnes * 12 mois = 1 260$ par an. Y a un autre plan qui pour­rait conve­nir, qui nous revien­drait à 780$ par an. C’est quand même pas négli­geable (surtout pour une asso­cia­tion qui ne vit quasi­ment que de vos dons) alors qu’on héber­ge­rait nous-même le service.

J’avais laissé tombé quand ces derniers jours, une implé­men­ta­tion en Rust de Bitwar­den est passée dans ma time­line Masto­don : https://github.com/dani-garcia/bitwar­den_rs. Et celle-ci n’a pas la limi­ta­tion sur les équipes (mais il encou­rage à donner des sous au projet upstream).

J’ai d’abord testé avec le contai­ner Docker fourni par le déve­lop­peur puis j’ai tenté la compi­la­tion pour voir si ça passait, et ça passe crème !

La grosse diffé­rence, outre la non-limi­ta­tion des orga­ni­sa­tions, c’est que la base de données est, pour l’ins­tant, SQLite. Ce qui peut avoir des consé­quences sur les perfor­mances lorsque la base est très solli­ci­tée.

Ceci dit, Frama­drop tourne toujours avec une base SQLite et ça fonc­tionne bien, et mon instance Lutim, ainsi que Frama­pic ont long­temps utilisé SQLite.

Bref, voyons comment compi­ler et instal­ler cette version de Bitwar­den en Rust.

ATTENTION : allez plutôt voir le tuto­riel sur https://wiki.fiat-tux.fr/admin:logi­ciels:bitwar­den_rs, c’est plus simple pour moi de le main­te­nir sur mon wiki.

Compi­la­tion

On va avoir besoin des back­ports Debian pour instal­ler npm (pour compi­ler l’in­ter­face web) :

echo "deb http://ftp.debian.org/debian stretch-backports main" | sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update

Instal­la­tion des dépen­dances :

sudo apt install pkg-config libssl-dev
sudo apt install -t stretch-backports npm

Instal­la­tion de rustup, qui nous four­nira le compi­la­teur Rust :

curl https://sh.rustup.rs -sSf > rustup.sh

On n’exé­cute pas direct un script tiré du web ! On regarde d’abord s’il ne va pas faire de salo­pe­ries :

vi rustup.sh

On le rend exécu­table :

chmod +x rustup.sh

On installe le compi­la­teur Rust (il sera dans notre $HOME) :

./rustup.sh --default-host x86_64-unknown-linux-gnu --default-toolchain nightly

On source un fichier qui nous permet de l’ap­pe­ler

source $HOME/.cargo/env

Gagnons du temps en clonant le projet bitwar­den_rs et l’in­ter­face web (qu’il faut compi­ler aussi) en même temps .

git clone https://github.com/dani-garcia/bitwarden_rs
git clone https://github.com/bitwarden/web.git web-vault

Compi­la­tion de Bitwar­den_rs :

cd bitwarden_rs
cargo build --release

Le résul­tat de la compi­la­tion est dans bitwarden_rs/target/release/.

Compi­la­tion de l’in­ter­face web :

cd ../web-vault
# On se positionne sur le dernier tag en date
git checkout "$(git tag | tail -n1)"
# Un petit patch pour que ça fonctionne avec notre installation
wget https://raw.githubusercontent.com/dani-garcia/bw_web_builds/master/patches/v2.8.0.patch
# On vérifie le patch
cat v2.8.0.patch
git apply v2.8.0.patch
npm run sub:init
npm install
npm run dist

ATTENTION : on m’a dit que la compi­la­tion de l’in­ter­face web prenait 1,5Gio de RAM, assu­rez-vous que vous en avez assez de libre.

Et on copie l’in­ter­face web dans le dossier où attend le résul­tat de la compi­la­tion de bitwar­den_rs :

cp -a build/ ../bitwarden_rs/target/release/web-vault/

Instal­la­tion

On va instal­ler Bitwar­den_rs dans /opt/bitwarden et on le fera tour­ner avec l’uti­li­sa­teur www-data :

cd ..
sudo rsync -a --info=progress2 bitwarden_rs/target/release/ /opt/bitwarden/
chown -R www-data: /opt/bitwarden

Puis on va créer un service systemd, /etc/systemd/system/bitwarden.service :

[Unit]
Description=Bitwarden Server (Rust Edition)
Documentation=https://github.com/dani-garcia/bitwarden_rs
After=network.target

[Service]
# The user/group bitwarden_rs is run under. the working directory (see below) should allow write and read access to this user/group
User=www-data
Group=www-data
# The location of the .env file for configuration
EnvironmentFile=/etc/bitwarden_rs.env
# The location of the compiled binary
ExecStart=/opt/bitwarden/bitwarden_rs
# Set reasonable connection and process limits
LimitNOFILE=1048576
LimitNPROC=64
# Isolate bitwarden_rs from the rest of the system
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
# Only allow writes to the following directory and set it to the working directory (user and password data are stored here)
WorkingDirectory=/opt/bitwarden/
ReadWriteDirectories=/opt/bitwarden/

[Install]
WantedBy=multi-user.target

Pour l’in­ter­face d’ad­mi­nis­tra­tion, on va créer un token avec :

openssl rand -base64 48

La confi­gu­ra­tion se fait via des variables d’en­vi­ron­ne­ment qu’on va mettre dans /etc/bitwarden_rs.env :

SIGNUPS_ALLOWED=false
WEBSOCKET_ENABLED=true
ADMIN_TOKEN=Un token généré avec `openssl rand -base64 48`
ROCKET_ADDRESS=127.0.0.1
WEBSOCKET_ADDRESS=127.0.0.1
SMTP_HOST=127.0.0.1
SMTP_FROM=bitwarden@example.org
SMTP_PORT=25
SMTP_SSL=false

Vous remarque­rez que je dis à Bitwar­den d’en­voyer les mails via le serveur SMTP local. À vous de faire en sorte qu’il fonc­tionne. Allez voir le wiki du projet pour voir quelles variables vous pour­riez ajou­ter, enle­ver, modi­fier…

Puis :

sudo systemctl daemon-reload
sudo systemctl enable bitwarden
sudo systemctl start bitwarden
sudo systemctl status bitwarden

Nginx

On installe Nginx s’il n’est pas déjà installé :

sudo apt install nginx

Confi­gu­ra­tion du virtual­host :

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name bitwarden.example.org;

    access_log /var/log/nginx/bitwarden.access.log;
    error_log /var/log/nginx/bitwarden.error.log;

    ssl_certificate      /etc/letsencrypt/live/bitwarden.example.org/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/bitwarden.example.org/privkey.pem;

    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:5m;

    ssl_prefer_server_ciphers On;
    ssl_protocols TLSv1.2;
    ssl_ciphers 'EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA';

    ssl_dhparam /etc/ssl/private/dhparam4096.pem;
    add_header Strict-Transport-Security max-age=15768000; # six months
    gzip off;

    if ($https != 'on') {
        rewrite ^/(.*)$ https://bitwarden.example.org/$1 permanent;
    }

    root /var/www/html;

    # Allow large attachments
    client_max_body_size 128M;

    location ^~ '/.well-known/acme-challenge' {
        default_type "text/plain";
        root /var/www/certbot;
    }

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:8000;
    }

    location /notifications/hub {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:3012;
    }

    location /notifications/hub/negotiate {
        proxy_pass http://127.0.0.1:8000;
    }
}

Pour créer /etc/ssl/private/dhparam4096.pem :

sudo openssl dhparam -out /etc/ssl/private/dhparam4096.pem 4096

Pour le certi­fi­cat Let’s Encrypt, on commente le brol rela­tif à ssl puis :

sudo nginx -t && sudo nginx -s reload
sudo apt install certbot
sudo mkdir /var/www/certbot/
certbot certonly --rsa-key-size 4096 --webroot -w /var/www/certbot/ --agree-tos --text --renew-hook "/usr/sbin/nginx -s reload" -d bitwarden.example.org

Une fois qu’on a le certi­fi­cat, on décom­mente le brol ssl puis :

sudo nginx -t && sudo nginx -s reload

Sauve­garde

Créer le script de sauve­garde /opt/backup_bitwarden.sh :

#!/bin/bash
function bwbackup {
    DATE=$(date '+%a%H')

    # Database
    if [[ ! -d /opt/backup_bitwarden/sqlite-backup/ ]]
    then
        mkdir -p /opt/backup_bitwarden/sqlite-backup/
    fi
    echo ".backup /opt/backup_bitwarden/sqlite-backup/db.${DATE}.sqlite3" | sqlite3 /opt/bitwarden/data/db.sqlite3 2>> /opt/backup_bitwarden/backup.log
    if [[ "$?" -ne "0" ]]
    then
        echo "Something went wrong with bitwarden database backup, please see /opt/backup_bitwarden/backup.log on verity" | mail -s "Bitwarden database backup" youraddress@mail.example.org
        bwbackup
    fi

    # Files
    if [[ ! -d /opt/backup_bitwarden/files-backup/ ]]
    then
        mkdir -p /opt/backup_bitwarden/files-backup/
    fi
    rsync -a --delete --exclude db.sqlite3 /opt/bitwarden/data/ /opt/backup_bitwarden/files-backup/$DATE/ 2>> /opt/backup_bitwarden/backup.log
    if [[ "$?" -ne "0" ]]
    then
        echo "Something went wrong with bitwarden files backup, please see /opt/backup_bitwarden/backup.log on verity" | mail -s "Bitwarden files backup" youraddress@mail.example.org
        bwbackup
    fi
}
bwbackup

Puis :

sudo chmod +x /opt/backup_bitwarden.sh
sudo mkdir /opt/backup_bitwarden
sudo chown www-data: /opt/backup_bitwarden
sudo apt install sqlite3

Puis, dans le cron de l’uti­li­sa­teur www-data :

42 4 * * * /opt/backup_bitwarden.sh

Logs

J’aime bien avoir mes logs dans un dossier dédié pour ce genre de service.

Dans /etc/rsyslog.d/bitwarden.conf :

if $programname == 'bitwarden_rs' then /var/log/bitwarden/bitwarden.log
if $programname == 'bitwarden_rs' then ~

Dans /etc/logrotate.d/bitwarden :

/var/log/bitwarden/bitwarden.log
{
        rotate 52
        dateext
        weekly
        missingok
        notifempty
        compress
        sharedscripts
        postrotate
                invoke-rc.d rsyslog rotate > /dev/null
        endscript
}

Puis :

sudo mkdir /var/log/bitwarden
sudo chown root:adm /var/log/bitwarden
sudo service rsyslog restart

Fail2­ban

Un fail2­ban qui surveille les logs, ça permet de bloquer les petits malins qui font du brute­force

Dans /etc/fail2ban/filter.d/bitwarden.conf :

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <HOST>\. Username:.*$
ignoreregex =

Dans /etc/fail2ban/jail.d/bitwarden.local :

[bitwarden]
enabled = true
port = 80,443
filter = bitwarden
action = iptables-allports[name=bitwarden]
logpath = /var/log/bitwarden/bitwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Pour la page d’ad­min, dans /etc/fail2ban/filter.d/bitwarden-admin.conf :

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*Unauthorized Error: Invalid admin token\. IP: <HOST>.*$
ignoreregex =

Dans /etc/fail2ban/jail.d/bitwarden-admin.local :

[bitwarden-admin]
enabled = true
port = 80,443
filter = bitwarden-admin
action = iptables-allports[name=bitwarden]
logpath = /var/log/bitwarden/bitwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Fina­le­ment :

sudo service fail2ban restart

Conclu­sion

Voilà, vous devriez avoir un serveur Bitwar­den_rs fonc­tion­nel. Plus qu’à aller sur l’in­ter­face web que vous venez de mettre en place ou télé­char­ger les clients et à les utili­ser !

Pour impor­ter vos mots de passe de Fire­fox, il faut passer par une appli­ca­tion pour les expor­ter, puis aller dans les outils de votre client (ou de l’in­ter­face web).

EDIT 15/01/2019 Rempla­ce­ment de la mention de l’ap­pli­ca­tion Next­cloud Pass­words par l’ap­pli­ca­tion Pass­man (je me suis trompé).

EDIT 17/01/2019 Rempla­ce­ment de npm run dist par npm run dist:selfhost dans la compi­la­tion de l’in­ter­face web. Cela désac­tive Google Analy­tics (voir https://github.com/bitwar­den/web/issues/243#issue­comment-412852726)

EDIT 18/01/2019 Rempla­ce­ment de npm run dist:selfhost par npm run dist dans la compi­la­tion de l’in­ter­face web. Google Analy­tics est déjà désac­tivé par le patch. Il semble­rait que j’ai vu passer des requêtes vers Google Analy­tics à cause du cache de mon navi­ga­teur qui gardait une version non patchée.

EDIT 18/01/2019 Ajout d’un aver­tis­se­ment sur la consom­ma­tion de RAM lors de la compi­la­tion de l’in­ter­face web.

Crédits : Photo par Matt Artz sur Unsplash

Parta­ger

by Luc at January 14, 2019 07:02 PM

January 13, 2019

Florent Peterschmitt

Namasté Népal - #4.1

Récapitulatif

Dans l’article précédent j’étais donc à Pokhara et je sortais d’une grotte remplie de chauves-souris. Une fois ressorti j’avais prévu de passer encore une journée sur place, après-demain sera donc le retour vers Kathmandu pour commencer le trekking de la vallée de Langtang.

Le départ est à Syapru Besi, un village à fond pour les touristes venant marcher dans les montagnes. Rien de très étonnant à vrai dire.

L’arrivée était prévue à 4500 mètres d’altitudes, je me suis arrêté à environ 3000 mètres, avec de la fièvre (oui, je suis retombé malade…) et le souffle coupé. Comme quoi il faut s’entraîner avant, oui bien être fumeur : d’après mon guide les fumeurs, même sans expérience de la montagne arrivent à aller au bout. Ce qui est compréhensible vu qu’un fumeur a régulièrement un manque d’oxygène dans les poumons !

J’ai découpé le trek en 4 articles à venir ; les photos sont déjà triées, reste plus qu’à en retoucher quelques unes et à publier.

Et comme toujours, un peu de shell :D

for img in $(find 2* -name "*.*"); do
    echo '![photo](/static/images/nepal2017/'${img}')' >> pics.md
done

22 Avril 2017 - Vallée de Langtang

map-trek

Démarrage à 1600 mètres environ !

photo photo photo photo photo photo photo photo

Sur la gauche de la photo, sur ce flan de montagne effondré, des nids d’abeilles. Elles font un miel qui est récolté.

photo

Premier « singe blanc », qui n’a pas voulu montrer sa tête. Impossible de l’avoir en plus gros, le zoom était déjà à son maximum.

photo photo photo

Arrivé au gîte (guest house) ce pti bonhomme parlait mieux anglais que quand j’avais son âge, et débordait d’énergie ! Il a voulu tester l’appareil photo, je l’ai laissé faire, bon, le résultat était pas probant :D

photo photo

Des rhododendrons !

photo

Soir nuageux.

photo

Matin dégagé.

Article suivant

by Florent Peterschmitt at January 13, 2019 03:15 PM

January 10, 2019

Romain Dessort

Joindre les 2 bouts de la Laurentienne

La région des Laurentides est très riche en pistes de ski nordique datant du siècle dernier, des sentiers historiques tracés en partie par des norvégiens, dont le fameux Jackrabbit, à l'époque où le ski de fond était populaire au Québec. Malheureusement aujourd'hui, beaucoup sont abandonnées, trop méconnues et réduites en morceaux à cause des développements immobilier et des propriétés privées. Découvrir et cartographier ces pistes historiques est devenu ma grande passion, et je skie plus dans le but de répertorier des nouvelles pistes que juste pour le plaisir de skier.

Si il y a un blog de ski nordique que je ne me lasse pas de lire, c'est celui de Barclay. J'ai découvert plusieurs pistes des Laurentides comme ça et c'est toujours intéressant d'avoir régulièrement les conditions des sentiers.

De mon côté jusqu'à présent, je me « contentais » de cartographier les sentiers de ski que je suivais dans OpenStreetMap. La région des Laurentides dans OSM a d'ailleurs explosé depuis quelques années, quelques autres personnes en rajoute aussi, et c'est maintenant clairement la carte la plus complète possible en terme de sentiers de ski (de fond ou de rando nordique). Mais en parler aussi ailleurs est toujours une bonne idée, donc je vais essayer de parler un peu plus de mes sorties ici. En tout cas au moins mes découvertes de pistes moins connues, car je n'aurai pas la motivation d'écrire à chaque fois, et il n'y a peu d'intérêt pour les pistes plus connues ou bien cartographiées.

Voila pour l'introduction, pour revenir à la Laurentienne, il faut retourner à la fin de saison de l'année dernière, j'avais repéré sur la carte du réseau nordique de Morin-Heights une piste qui s'en allait plein ouest, la Laurentienne (#24) puis qui s'arrêtait sans raison au milieu de nulle part. Ça a évidemment titillé ma curiosité et j'étais parti avec pour objectif de voir où elle allait. J'avais réussi à la suivre sur un bon bout au delà de la partie cartographiée, mais j'avais perdu sa trace ensuite dans une érablière et rendu sur le lac Ida. J'avais rebroussé chemin.

Samedi dernier donc, je l'ai prise en partant de Saint-Adolph, en stationnant au lac Louise, là où elle est censée arriver : la carte du réseau de Saint-Adolph montre aussi un début de piste qui a bien l'air de partir dans la bonne direction pour la connecter, mais ils ne l'ont pas cartographiée jusqu'au bout non plus.

À ma grande surprise, la piste était plutôt bien visible sur le terrain, et il y avait même quelques traces de skieurs et de raquetteurs qui m'avaient précédé. Au moins jusqu'à la route de la Montée des Quatres Lacs. Après c'était plus délicat à suivre mais la piste se devinait bien. J'ai pu retrouver l'endroit où je m'étais arrêté l'année dernière, près du lac Ida. La Laurentienne est maintenant complète dans OpenStreetMap !

Par contre des mystères persistent : sur certains sections (un peu avant le lac Ida), j'ai vu des très vieilles balises métalliques indiquant LSD, une autre piste porte déjà ce nom dans le secteur, c'est bizarre. Et après avoir passé la route de la Montée des Quatres Lacs, j'ai vu 2 panneaux dans le même style que ceux utilisés à Morin-Heights indiquant la Picher (#29) et la Dutch-Dash (#27). La Dutch-Dash est le début de la Laurentienne côté Morin-Heights. J'en déduis que ces 2 pistes restent confondues sur un bon bout. Mais après où va-t-elle ? Et la Picher, je n'en ai aucune idée de ce que c'est… Je ne la vois référencée sur aucune carte que j'ai.

Concernant l'allure de la Laurentienne, elle n'est en soit pas très difficile, il y a quelques up and down dans sa partie ouest mais la piste est suffisamment large pour pouvoir manœuvrer en descente. La partie reste essentiellement sur du plat, en passant à travers une belle forêt de cèdres à l'ouest du lac Ida, et longe des zones ruisseau et passe sur des zones marécageuses et des lacs dans sa partie est vers Morin-Heights.

J'étais parti avec l'idée que j'allais devoir débroussailler pas mal la piste, mais elle était assez propre. Il reste quand même quelques coups de scie à donner par-ci par-là si quelqu'un y retourne.

J'en ai aussi profité pour aller explorer la Castor (#6) qui connecte la Laurentienne à la Renard et traverse le lac Saint-Denis. Je me suis arrêté une fois rendu au lac car je voyais bien où est-ce que la Renard commence. C'est intéressant à avoir en tête car ça permet de faire une belle boucle entre Morin-Heights et Saint-Adolph.

January 10, 2019 07:13 PM

January 05, 2019

Romain Dessort

Patinoires de MontrĂŠal et donnĂŠes ouvertes

Dernièrement je me suis amusé à exploiter les données ouvertes que la ville de Montréal maintient sur les conditions de glace des patinoires publiques.

L'état de chaque patinoire est indiqué sur le site de la ville. Un fichier XML mis à jour quasi-quotidiennement durant la saison peut être trouvé ici, les données sont sous licence Creative Common BY 4.0.

L'idée était de pouvoir vérifier les conditions plus simplement que d'ouvrir mon navigateur, en une seule commande. Voici donc patinoires-mtl. Les cas d'usage sont mis en exemple dans le README : lister les patinoires ouvertes, afficher les conditions d'une ou plusieurs patinoires et, bonus, une sous-commande watch qui retourne les conditions seulement si elles ont changées, ce qui me permet d'envoyer ça à mail ou sendxmppet à mettre encron` pour être alerté dès qu'il y a un changement.

Lister les patinoires ouvertes :

$ ./patinoire.py list --open
Skating rink                                          Open
----------------------------------------------------  ----
Patinoire Bleu Blanc Bouge du parc Hayward (PSE)      1
Patinoire Bleu-Blanc-Bouge, Parc Confédération (PSE)  1
Patinoire réfrigérée, Lac aux Castors (PP)            1

Lister les patinoires du Plateau-Mont-Royal :

$ ./patinoire.py list --borough pmr
Skating rink                                           Open
-----------------------------------------------------  ----
Grande patinoire avec bandes, La Fontaine (PSE)        0
Patinoire avec bandes, Baldwin (PSE)                   0
Patinoire avec bandes, Jeanne-Mance (PSE)              0
Patinoire avec bandes, Sir-Wilfrid-Laurier no 1 (PSE)  0
Patinoire avec bandes, Sir-Wilfrid-Laurier no 2 (PSE)  0
Patinoire avec bandes, Sir-Wilfrid-Laurier no 3 (PSE)  0
Patinoire De Gaspé/Bernard (PSE)                       0
Patinoire de patin libre, Baldwin (PPL)                0
Patinoire de patin libre, Jeanne-Mance (PPL)           0
Patinoire de patin libre, Sir-Wilfrid-Laurier (PPL)    0
Patinoire décorative, De Lorimier (PP)                 0
Patinoire décorative, La Fontaine (PP)                 0

Afficher les conditions d'une patinoire en particulier :

$ ./patinoire.py conditions "lac aux castors"
                                            Open  Cleared  Sprayed  Resurfaced  Condition   
------------------------------------------  ----  -------  -------  ----------  ----------  
Patinoire réfrigérée, Lac aux Castors (PP)  1     1        1        1           Excellente 

Et une fonction pratique, pour être notifié d'un changement de condition sur une patinoire :

$ ./patinoire.py watch "lac aux castors"
                                            Open  Cleared  Sprayed  Resurfaced  Condition
------------------------------------------  ----  -------  -------  ----------  ----------
Patinoire réfrigérée, Lac aux Castors (PP)  1     1        1        1           Excellente
Updated on 2018-12-08 09:05:28
$ ./patinoire.py watch "lac aux castors"
$
[a few days later]
$ ./patinoire.py watch "lac aux castors"
                                            Open  Cleared  Sprayed  Resurfaced  Condition
------------------------------------------  ----  -------  -------  ----------  ----------
Patinoire réfrigérée, Lac aux Castors (PP)  1     1        1        1           Bonne
Updated on 2018-12-10 09:03:56

Comme j'adore les cartes, j'ai aussi fait patinoires-mtl-leaflet, qui permet de geolocaliser toutes les patinoires de Montréal sur un fond de carte OpenStreetMap et d'afficher leur état. Bon pour le coup, c'est de nouveau dans le navigateur forcement... La couleur des patinoires est en fonction de leur état. Un aperçu ici : https://univers-libre.net/patinoires-mtl/.

Pour la géolocalisation des patinoires, la ville de Montréal ne fournit aucune données. OpenStreetMap ne contient que très peu de patinoires non plus, et ça allait être compliqué de faire correspondre les noms des patinoires du XML de la ville avec ceux dans OpenStreetMap de toute façon. C'est alors que je suis tombé sur une application Android qui fait à peu près la même chose et qui a un fichier geojson avec la géolocalisation de toutes les patinoires. Rien n'est indiqué concernant la licence ou la provenance de ces données par contre.

L'outil en ligne de commande est en anglais et la carte est en français, ça ne fait aucune cohérence et comme ça tout le monde/personne (n')est content.

Je ne pense pas y faire d'autres modifications à part rajouter probablement la même chose pour les conditions des pistes de ski de fond.

January 05, 2019 02:29 AM

January 04, 2019

Luc Didry

Créer une boutique en ligne avec Word­press

Dans mon précé­dent article, j’évoquais le fait que j’ai ouvert ma boutique en ligne. Je vais expliquer un peu comment j’ai fait pour la mettre en place.

Le logi­ciel de base : Word­press

Pourquoi Word­press et pas Pres­ta­shop ou Magento qui sont des outils dédiés à la vente en ligne ? Plusieurs raisons à cela :

  • je connais déjà bien Word­press, je n’avais pas spécia­le­ment envie de m’enquiqui­ner à instal­ler d’autres trucs ;
  • Word­press, bien que ce soit au départ un moteur de blog, est un CMS que l’on peut tordre dans pas mal de sens. Je me suis dit que j’avais envie de voir si c’était simple d’en faire un site de vente en ligne ;
  • j’ai quand même essayé d’uti­li­ser Pres­ta­shop… sans réus­sir à passer la dernière étape d’ins­tal­la­tion : la mise en place de la base de données tombait en timeout à chaque fois (j’ai pas un énorme serveur et il est déjà bien plein de trucs).

Bref : on commence par instal­ler un Word­press. Je ne vais pas détailler, c’est un sujet fort bien traité sur les Inter­netz.

Les exten­sions

Pour trans­for­mer Word­press en boutique en ligne, il faut instal­ler quelques modules. Mais avant ça, voici ceux que j’ai installé pour la sécu­rité, les perfor­man­ces… :

  • Antis­pam Bee, pour l’an­tis­pam ;
  • iThemes Secu­rity, pour la sécu­rité ;
  • Smush, pour réduire la taille des images ;
  • WP Super Cache, pour les perfor­mances.

Ensuite, des exten­sions pour amélio­rer la boutique, mais pas obli­ga­toires :

  • Contact Form 7, pour le formu­laire de contact ;
  • Google XML Site­maps, pour amélio­rer le réfé­ren­ce­ment ;
  • Masto­don Auto­post, pour pouet­ter auto­ma­tique­ment quand on ajoute un article à la boutique.

Voici les exten­sions pour la boutique elle-même :

  • WooCom­merce, l’ex­ten­sion prin­ci­pale, propo­sée par la boîte qui déve­loppe Word­press, donc ça va, j’ai assez confiance sur la compa­ti­bi­lité ;
  • WooCom­merce Blocks, pour faire fonc­tion­ner WooCom­merce avec le nouvel éditeur de Word­press ;
  • WooS­wipe, pour avoir une gale­rie d’image sur les pages des produits (quand on clique sur l’image pour zoomer, pis pour voir les autres images, vous voyez le genre) ;
  • WooCom­merce Weight Based Ship­ping, pour gérer les frais d’en­voi selon le poids de la commande ;
  • Payment Gate­way Based Fees and Discounts for WooCom­merce, pour pouvoir ajou­ter des frais selon la méthode de paie­ment choi­sie.

Le thème

Il va de soit qu’un thème pour un blog a peu de chance d’al­ler pour faire une boutique. J’ai choisi le thème Shop Isle, que je trouve simple et bien foutu. J’en ai néan­moins fait un thème enfant pour déga­ger ces cochon­ne­ries de google fonts.

Les frais d’en­voi

L’ex­ten­sion « WooCom­merce Weight Based Ship­ping » me permet de défi­nir des frais d’en­voi selon le poids de la commande ainsi que sa desti­na­tion (vous imagi­nez bien qu’en­voyer deux cartes postales en France ne coûte pas le même prix qu’en envoyer 15 dans un autre pays).

Cette exten­sion est parfaite, à l’ex­cep­tion de l’in­ter­face pour choi­sir les pays de desti­na­tion quand on crée les règles : ça va bien quand on ajoute une ou deux desti­na­tions, mais pas quand on doit en sélec­tion­ner 12 (france métro­po­li­taine + DOM/TOM) ou plus (les pays de l’UE).

Person­nel­le­ment, j’ai choisi de faire des frais d’en­voi à prix plus ou moins coûtant : le tarif des timbres, arrondi un peu au-dessus pour faire le prix de l’en­ve­loppe. Je ne dis pas que je ne fais pas un peu de béné­fice dessus, mais ça se compte à coup de centimes.

Le paie­ment

WooCom­merce embarque déjà plusieurs moyens de paie­ments, je dois dire que c’est très bien foutu !

J’ai activé le vire­ment SEPA, parce que ça ne coûte rien, ni à l’en­voyeur, ni au desti­na­taire. Par contre, comme ça prend du temps (le vire­ment prend géné­ra­le­ment 24h pour appa­raître sur les comptes, sans comp­ter que certaines banques mettent plusieurs jours pour accep­ter un nouvel IBAN et permettre des vire­ments vers celui-ci), j’ai ajouté la possi­bi­lité d’uti­li­ser Paypal : ça propose d’uti­li­ser son compte Paypal (et c’est assez répandu pour que ça soit pratique pour un paquet de gens) pour payer ou la carte bancaire. Par contre, ça m’a fait deux blagou­nettes :

  • pour effec­ti­ve­ment pouvoir récu­pé­rer les paie­ments, j’ai du passer mon compte en compte busi­ness. Rien de bien méchant, mais la récu­pé­ra­tion des sous ne fonc­tion­nait pas et rien n’in­diquait sur Paypal que c’était ce qu’il fallait faire (il y avait juste un message d’er­reur complè­te­ment inutile). Une fois mon compte passé en busi­ness, plus de souci ;
  • mais du coup, des frais s’ap­pliquent (pas pour le client, qui paie bien ce que ma boutique lui indiquait, mais pour moi : je ne récu­père pas autant d’argent que ce qu’a payé le client). Comme je ne souhaite pas en être de ma poche, j’ai installé « Payment Gate­way Based Fees and Discounts for WooCom­merce » qui me permet d’ajou­ter le pour­cen­tage (3,4%) et le forfait (0,25€) de la commis­sion lorsqu’on choi­sit Paypal pour payer.

Condi­tions géné­rales de vente

Pour les rédi­ger, j’ai d’abord cher­ché sur le web, et je suis tombé sur https://www.donnees­per­son­nelles.fr/cgv, qui propose carré­ment une exten­sion Word­press. Après l’avoir instal­lée, je me suis contenté de repom­per les CGV qu’elle propo­sait : ça m’a permis, tout d’abord, de les lire, et ensuite de corri­ger des typos.

Après quoi, j’ai désins­tallé l’ex­ten­sion 😁

Conclu­sion

Je ne pense pas qu’il soit néces­saire pour moi de trop détailler le réglage des diffé­rentes exten­sions : les infos se trouvent assez faci­le­ment sur le web, et même si les réglages sont touf­fus, c’est assez intui­tif. La plus grosse partie du temps fut perdue dans la recherche des exten­sions et du thème kivon­bien.

Après quelques tâton­ne­ments, la mise en place de ma boutique fut assez simple. J’es­père que cet article servira à d’autres pour leur éviter de cher­cher autant que moi.

Crédits : Photo par Mike Petrucci sur Unsplash

Parta­ger

by Luc at January 04, 2019 05:09 PM

Merci à mes tipeurs 🙂

Le 14 juillet 2016, j’ai lancé mes pages Tipeee et Libe­ra­pay.

La récom­pense de base est l’ap­pa­ri­tion sur une page mensuelle de remer­cie­ments… voici celle de décembre.

Merci à :

Voici mon bilan de décembre :

EDIT : j’ai oublié de dire que j’ai bossé sur la réécri­ture de WemaWema en VueJS

Et sinon, grande annonce ! J’ai ouvert ma boutique en ligne !

Bon, pour l’ins­tant, il n’y a qu’un article, une carte postale d’une illus­tra­tion de Linux.pictures. J’avais fait un sondage sur Masto­don il y a quelques mois pour savoir quelle illus­tra­tion impri­mer, et voilà, j’ai enfin pris la peine (et les sous) de faire tirer les cartes postales. Les béné­fices, s’il y en a (vu que je vends à prix coûtant, à quelques arron­dis prêts, y en aura peut-être pas avant long­temps), iront à Linux.pictures (ou un projet libre si je n’ar­rive pas à les contac­ter).

Je compte rajou­ter prochai­ne­ment un recueil de mes mercre­dis fictions relié à la japo­naise par mes soins (vous pouvez voir ce que ça donne sur ce pouet).

Vous pouvez toujours me soute­nir via Duni­ter !

Je remets ici mon expli­ca­tion.

Disclai­mer : je vais peut-être dire des sottises parce que j’ai pas tout compris mais je m’en fous, j’aime bien l’idée

Duni­ter est un projet de crypto-monnaie mais contrai­re­ment au bitcoin où c’est la course à la puis­sance de calcul pour géné­rer la monnaie et deve­nir riche, le but est de créer une monnaie libre (le Ğ1, prononcé comme june en anglais), basée sur une toile de confiance (il faut rece­voir 5 certi­fi­ca­tions pour être membre) et où chacun des membres reçoit tous les jours un divi­dende univer­sel. Je dois dire que je suis assez curieux de ce qu’on peut faire avec un système ressem­blant forte­ment au salaire à vie 😉

C’est pourquoi j’ai créé mon compte et ait fait ce qu’il fallait pour rece­voir mes certi­fi­ca­tions (rencon­trer des gens, tous­sa…). Ma clé publique est :

 2t6NP6Fvvuok2iRWA188C6pGokWAB5Kpf1S1iGtkN9tg

Et comme un projet n’est utile que si on s’en sert, je vous propose de me soute­nir en Ğ1 tout en béné­fi­ciant des mêmes récom­penses que celles présentes sur ma page Tipeee (j’ai arbi­trai­re­ment choisi une parité euro/Ğ1 car je n’ai pas vrai­ment trouvé de page expliquant comment évaluer le cours du Ğ1 en euros, et au final, est-ce vrai­ment néces­saire ? À nous de choi­sir quelle valeur a cette nouvelle monnaie 🙂. Et puis ça me simpli­fie la vie).

Voici donc les diffé­rentes contre­par­ties et leur prix (chaque contre­par­tie comprend celles de tarif infé­rieur) :

  • 1 Ğ1 : vous appa­raî­trez sur la page mensuelle des remer­cie­ments
  • 2 Ğ1 : vous rece­vrez une photo dédi­ca­cée de mon chat
  • 3 Ğ1 : vous rece­vrez 3 stickers repre­nant les logos de Lstu, Lutim et Lufi. De quoi déco­rer son ordi et se la péter en société 😁
  • 5 Ğ1 : un commit vous sera dédié chaque mois (si je déve­loppe suffi­sam­ment pour le nombre de personnes à ce niveau)
  • 15 Ğ1 : vous pouvez me deman­der de bosser en prio­rité sur un bug ou une demande de fonc­tion­na­lité d’un de mes logi­ciels (dans la mesure du faisable, hein).
  • 100 Ğ1 : quand j’au­rais un nouveau projet, vous pour­rez en choi­sir le nom et le logo

Pour en savoir plus sur Duni­ter et Ğ1, je vous propose d’al­ler voir cet article de cgeek : https://blog.cgeek.fr/de-linte­ret-dune-monnaie-libre.html ainsi que la théo­rie rela­tive de la monnaie pour les enfants.

Crédits : Photo par Simon Migaj sur Unsplash

Parta­ger

by Luc at January 04, 2019 04:13 PM

December 24, 2018

Luc Didry

Double merci à mes tipeurs 🙂

Le 14 juillet 2016, j’ai lancé mes pages Tipeee et Libe­ra­pay.

La récom­pense de base est l’ap­pa­ri­tion sur une page mensuelle de remer­cie­ments… voici celle d’oc­to­bre… et de novembre. Du retard, toujours du retard et encore plus de retard !

Merci à :

Voici mon bilan d’oc­tobre :

Et mon bilan de novembre :

Vous pouvez toujours me soute­nir via Duni­ter !

Je remets ici mon expli­ca­tion.

Disclai­mer : je vais peut-être dire des sottises parce que j’ai pas tout compris mais je m’en fous, j’aime bien l’idée

Duni­ter est un projet de crypto-monnaie mais contrai­re­ment au bitcoin où c’est la course à la puis­sance de calcul pour géné­rer la monnaie et deve­nir riche, le but est de créer une monnaie libre (le Ğ1, prononcé comme june en anglais), basée sur une toile de confiance (il faut rece­voir 5 certi­fi­ca­tions pour être membre) et où chacun des membres reçoit tous les jours un divi­dende univer­sel. Je dois dire que je suis assez curieux de ce qu’on peut faire avec un système ressem­blant forte­ment au salaire à vie 🙂

C’est pourquoi j’ai créé mon compte et ait fait ce qu’il fallait pour rece­voir mes certi­fi­ca­tions (rencon­trer des gens, tous­sa…). Ma clé publique est :

 2t6NP6Fvvuok2iRWA188C6pGokWAB5Kpf1S1iGtkN9tg

Et comme un projet n’est utile que si on s’en sert, je vous propose de me soute­nir en Ğ1 tout en béné­fi­ciant des mêmes récom­penses que celles présentes sur ma page Tipeee (j’ai arbi­trai­re­ment choisi une parité euro/Ğ1 car je n’ai pas vrai­ment trouvé de page expliquant comment évaluer le cours du Ğ1 en euros, et au final, est-ce vrai­ment néces­saire ? À nous de choi­sir quelle valeur a cette nouvelle monnaie 🙂. Et puis ça me simpli­fie la vie).

Voici donc les diffé­rentes contre­par­ties et leur prix (chaque contre­par­tie comprend celles de tarif infé­rieur) :

  • 1 Ğ1 : vous appa­raî­trez sur la page mensuelle des remer­cie­ments
  • 2 Ğ1 : vous rece­vrez une photo dédi­ca­cée de mon chat
  • 3 Ğ1 : vous rece­vrez 3 stickers repre­nant les logos de Lstu, Lutim et Lufi. De quoi déco­rer son ordi et se la péter en société 😁
  • 5 Ğ1 : un commit vous sera dédié chaque mois (si je déve­loppe suffi­sam­ment pour le nombre de personnes à ce niveau)
  • 15 Ğ1 : vous pouvez me deman­der de bosser en prio­rité sur un bug ou une demande de fonc­tion­na­lité d’un de mes logi­ciels (dans la mesure du faisable, hein).
  • 100 Ğ1 : quand j’au­rais un nouveau projet, vous pour­rez en choi­sir le nom et le logo

Pour en savoir plus sur Duni­ter et Ğ1, je vous propose d’al­ler voir cet article de cgeek : https://blog.cgeek.fr/de-linte­ret-dune-monnaie-libre.html ainsi que la théo­rie rela­tive de la monnaie pour les enfants.

Crédits : Photo par Connor Wells sur Unsplash

Parta­ger

by Luc at December 24, 2018 04:21 PM

December 22, 2018

Romain Dessort

Safe paste in terminal

In my daily life of sysadmin, I copy-paste many chunk of text (commands, IP addresses, URLs, file paths, whole scripts or file contents sometimes because it's easier to copy-paste them rather than to scp them). I usually copy them from other servers or from my company's internal documentation (trusted sources). However, I may sometimes follow howtos or read Stackoverflow answers and copy text and commands from those less trusted websites.

I want to talk about how easy one could trick you by replacing copied text in your clipboard or inserting terminal escape sequences so that your shell will run additional and potentially malicious commands. And how I protecting myself against these tricks.

Tricks I have identified

Here is a list of tricks an attacker can use to fool you and make you paste a malicious command in your terminal.

Replacing copied text with Javascript

Dylan Ayrey demonstrates that with a few lines of Javascript, a malicious website can alter your clipboard content. The code runs on key release event (keyboard shortcut or right click) and override clipboard content with the malicious code the attacker wants you to execute.

Obviously this attack doesn't work if you disable Javascript on unstrusted websites, which is by the way always a good idea.

Hiding malicious code with CSS

But even with Javascript disabled it's easy for an attacker to put hidden text in your clipboard. The idea is to put the malicious code in the middle of the displayed command in a <span /> element and apply different CSS properties on it, so that the malicious code isn't visible for the user. For instance make it float and move it away from the viewport, make it transparent, with no height or width…

Here is an interesting article about this attack. It also show how to hide the malicious code once pasted in the victim's terminal so that the victim can't even known he has been p0wned.

What about disabling CSS then?

Inserting non-printable characters

That's no enough since you could have non-printable characters (like backspaces) in raw text. Here is an example (the whole thread is an interesting reading by the way). Basically, the idea is to insert extra characters and a backspace character after each of them to make the code appear inoffensive.

How I protect myself

So what next? What solutions I use to protect myself against those attacks?

Pasting into a text editor: the bad idea

In most shell, I can easily start $EDITOR with ^X^E to edit the current command. But that's totally ineffective since the pasted text can contain ^ESC:!<malicious code> which will work in vim, or simply send the right shortcut to close the editor and get back to the shell.

Shell's bracketed paste mode

This is an elegant solution. Bash (actually readline) can configure the terminal to enclose the pasted text between paste sequences (hence the bracketed paste mode):

enable-bracketed-paste (Off) When set to On, readline will configure the terminal in a way that will enable it to insert each paste into the editing buffer as a single string of charac‐ ters, instead of treating each character as if it had been read from the key‐ board. This can prevent pasted characters from being interpreted as editing commands.

To enable it:

$ echo "set enable-bracketed-paste on" >>~/.inputrc

If you then try to paste multi-lines commands in your terminal, the commands won't be executed unless you press enter. You have a chance to review and edit them before their execution.

But it has two main drawbacks:

  • obviously enabling bracketed paste mode will work on your own computer only, unless you enable it on all your remote servers as well.
  • you may still be vulnerable to code injection if the pasted text contains the bracketed-paste-mode end sequence, whether your terminal automatically filter this sequence or not.

You can test this here.

confirm-paste plugin in URxvt

URxvt has a nice plugin called paste-confirm. It is shipped with URxvt and can be found in its plugin directory (/usr/lib/urxvt/perl/). Once loaded, the plugin will detect multi-lines pastes and will ask you whether to really paste it to the shell or not.

To enable it, simply add it to the plugin list in your ~/.Xdefaults:

URxvt.perl-ext-common: […],confirm-paste

Print your clipboard content before you paste

It's always a good idea to double-check your clipboard content before pasting it somewhere. I sometimes don't remember or I'm not sure about what was my previous selection, and I remember pasting whole file content into my terminal a couple of times…

I now use clipster for my cliboard manager with roficlip. Thus with a simple shortcut I can quickly double-check and select the previous entries in my clipboard.

December 22, 2018 06:13 PM

December 08, 2018

Florent Peterschmitt

Femto Feed

Tous les lecteurs de flux me saoulent :

  • FreshRSS est génial, mais je ne veux pas avoir à installer tout ce qu’il demande. Actuellement mon blog est rendu via des fichiers statiques, je souhaite qu’il en aille de même pour ça.
  • Les services commerciaux comme Feedly ne m’apportent pas grand chose, je n’ai pas besoin des fonctionnalités payantes, et je me suis désinscrit récemment car ils poussent leurs offres payantes comme quelqu’un qui veut vous forcer à avaler un rat mort.
  • Il existe certainement des offres libres et gratuites, mais j’ai une idée très précise de ce que je veux, c’est très simple, alors je l’ai codé.

Femto Feed - Fonctionnalités

  • Récupérer les flux depuis une liste maintenue dans un fichier
  • Générer un fichier HTML rudimentaire avec : date | source | titre et lien

Et… c’est tout. Je le lance via une tâche planifiée et le fichier généré est servi via un serveur web. Fin de l’histoire.

Le nouveau-né s’appelle donc Femto Feed et il est disponible sur gitlab.com et github.com.

En Rust

Avec l’édition 2018 quit vient tout juste de sortir, c’était une raison suffisante pour utiliser ce langage :-P Bon, en vrai, c’est surtout parce que j’aime ça :

  • Typage statique
  • Génériques, et surtout Option et Result
  • Pattern matching
  • Gestion d’erreur sans exceptions
  • Outillage à l’image de ce qui se fait dans le monte Python et/ou Go
  • Move semantic, borrowing… (std::move et unique_ptr en C++ si je ne me trompe pas)

Enjoy !

Toute contribution sera la bienvenue, à partir du moment où on reste dans l’échelle femtomètrique, voir la TODO dans le README du projet.

by Florent Peterschmitt at December 08, 2018 09:00 PM