DNSCrypt-Proxy est proxy DNS, qui prend en charge les communications cryptées des DNS, comme les protocoles DNSCrypt v2, DNS-over-HTTPS et Anonymized DNSCrypt. En plus, on peut alimenter DNSCrypt-Proxy afin de s’en servir comme d’un bloqueur de publicité.
Ainsi, vous évitez les requêtes vers votre FAI et vous êtes préservé des publicités.
Pour mes besoins, je vais utiliser les outils suivants :
Kvm est une offre de virtualisation intégrée de base dans le noyaux Linux (Kernel Virtual Machine) qui existe depuis plus de 10 ans. On le retrouve dans de nombreuses offres cloud et on l’ulitise sans le savoir.
Comme je l’ai présenté dans un article précédent sur l’Infra as code, je vais juste parlé ici d’un élément interessant dans son fonctionnement. Packer permets de créer des images cloud d’un OS qui sera déployé par d’autres outils. Certains OS comme Debian, Centos, … Proposent des outils de configuration (debootstrap,kickstart,…). D’autres comme Alpine, OpenBSD, ne proposent pas ce genre de fichier de configuration et nécessitent la saisie de réponses manuelles. Packer gère ça à la perfection !
{
"variables":
{
"cpu": "1",
"ram": "1024",
"name": "alpine",
"disk_size": "1000",
"iso_checksum": "fe694a34c0e2d30b9e5dea7e2c1a3892c1f14cb474b69cc5c557a52970071da5",
"iso_checksum_type": "sha256",
"iso_urls": "http://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-virt-3.12.0-x86_64.iso",
"version": "3.12.0",
"headless": "true",
"ssh_username": "root",
"ssh_password": "toor"
},
"builders": [
{
"name": "{{user `name`}}-{{user `version`}}",
"type": "qemu",
"format": "qcow2",
"accelerator": "kvm",
"qemu_binary": "/usr/bin/qemu-system-x86_64",
"net_device": "virtio-net",
"disk_interface": "virtio",
"disk_cache": "none",
"qemuargs": [[ "-m", "{{user `ram`}}M" ],[ "-smp", "{{user `cpu`}}" ]],
"ssh_wait_timeout": "30m",
"http_directory": ".",
"http_port_min": 10082,
"http_port_max": 10089,
"ssh_host_port_min": 2222,
"ssh_host_port_max": 2229,
"ssh_username": "{{user `ssh_username`}}",
"ssh_password": "{{user `ssh_password`}}",
"iso_urls": "{{user `iso_urls`}}",
"iso_checksum": "{{user `iso_checksum`}}",
"boot_wait": "15s",
"boot_command": [
"root<enter>",
"ifconfig eth0 up \u0026\u0026 udhcpc -i eth0<enter><wait5>",
"wget -qO answers http://{{.HTTPIP}}:{{.HTTPPort}}/answers<enter><wait>",
"setup-alpine -f answers<enter><wait5>",
"toor<enter>",
"toor<enter>",
"<wait10>",
"reboot<enter>"
],
"disk_size": "{{user `disk_size`}}",
"disk_discard": "unmap",
"disk_compression": true,
"headless": "{{user `headless`}}",
"shutdown_command": "poweroff",
"output_directory": "{{user `name`}}-{{user `version`}}"
}
],
"provisioners": [
{
"inline": [
"echo http://dl-cdn.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories",
"echo http://dl-cdn.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories",
"apk update",
"apk add dbus avahi",
"apk add cloud-init cloud-utils",
"apk add qemu-guest-agent python3",
"echo GA_PATH=\"/dev/vport1p1\" >> /etc/conf.d/qemu-guest-agent",
"rc-update add cloud-init default",
"rc-update add qemu-guest-agent default",
"/sbin/setup-cloud-init"
],
"type": "shell"
}
]
}
Dans la section boot_command j’indique à Alpine Linux qui va pouvoir télécharger son fichier de réponses (les commandes qui doivent normalement être saisie manuellement) sur un serveur http automatiquement géré par Packer.
KEYMAPOPTS="us us"
HOSTNAMEOPTS="-n alpine"
INTERFACESOPTS="auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
"
DNSOPTS=""
TIMEZONEOPTS="-z Europe/Paris"
PROXYOPTS="none"
APKREPOSOPTS="http://dl-cdn.alpinelinux.org/alpine/latest-stable/main/"
SSHDOPTS="-c openssh"
NTPOPTS="-c chrony"
DISKOPTS="-s 0 -m sys /dev/vda"
Une fois les différentes instructions exécutées, dans la section provisioners j’ajoute les repositories edge et installe un certain nombre de composants :
Puis l’image est générée (dans mon cas au format qcow2).
J’utilise Terraform version 0.13.4 et le provider libvirt 0.6.3 (je participe d’ailleurs au projet qui dans cette version intègre un de mes pull-requests par rapport à l’agent Qemu), pour installer ce Terraform Provider il faut télécharger le binaire et l’installer dans votre répertoire .terraform.d/plugins.
Quant à l’utilisation de Terraform regardez mon précédent tuto pour devenir un devops.
Voici le contenu du 1er fichier Terraform : main.tf
# provider
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.6.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
# load data
data "template_file" "user_data" {
template = file(var.user_data_source)
vars = {
hostname = "srv-${random_id.randomId.hex}"
}
}
data "template_file" "network_config" {
template = file(var.network_config_source)
}
# create pool
resource "libvirt_pool" "alpine" {
name = var.pool_name
type = "dir"
path = var.pool_dir
}
resource "libvirt_volume" "os_image" {
source = var.image_source
name = "img-${random_id.randomId.hex}.qcow2"
pool = libvirt_pool.alpine.name
}
# create image
resource "libvirt_volume" "image-qcow2"{
name = "disk-${random_id.randomId.hex}"
base_volume_id = libvirt_volume.os_image.id
pool = libvirt_pool.alpine.name
format = "qcow2"
size = 1 * 1024 * 1024 * 1024
}
# cloudinit
resource "libvirt_cloudinit_disk" "commoninit" {
name = "commoninit-${random_id.randomId.hex}.iso"
pool = libvirt_pool.alpine.name
user_data = data.template_file.user_data.rendered
network_config = data.template_file.network_config.rendered
}
resource "random_id" "randomId" {
keepers = {
store = libvirt_pool.alpine.id
}
byte_length = 4
}
# Define KVM domain to create
resource "libvirt_domain" "virt-domain" {
name = "srv-${random_id.randomId.hex}"
memory = var.domain_memory
vcpu = var.domain_cpu
cloudinit = libvirt_cloudinit_disk.commoninit.id
qemu_agent = true
network_interface {
bridge = "br0"
wait_for_lease = true
}
provisioner "local-exec" {
command = <<EOT
echo "[dnscrypt]\n${self.network_interface.0.addresses.0} ansible_user=devops" > ./hosts.ini
sleep 30
ansible-playbook site.yml -i hosts.ini
EOT
}
console {
type = "pty"
target_type = "serial"
target_port = "0"
}
disk {
volume_id = libvirt_volume.image-qcow2.id
}
graphics {
type = "spice"
listen_type = "address"
autoport = true
}
}
Voici le contenu du 2nd fichier Terraform : variables.tf
variable "pool_name"{
description = "name of to create pool"
type = string
default = "alpine-pool-1"
}
variable "pool_dir"{
description = "dir of the new pool"
type = string
default = "/tmp/terraform_libvirt_provider_images_1/"
}
variable "domain_name"{
description = "name of the image including the format"
type = string
default = "alpine-server-1"
}
variable "image_name"{
description = "name of the image including the format"
type = string
default = "alpine-3.12.0-amd64-libvirt.qcow2"
}
variable "common_name"{
description = "name of cloud init disk"
type = string
default = "alpine-s1"
}
variable "image_source"{
description = "path to image source"
type = string
default = "./alpine-3.12.0-amd64-libvirt.qcow2"
}
variable "user_data_source"{
description = "path to use data"
type = string
default = "./user_data.cfg"
}
variable "network_config_source"{
description = ""
default = "./network_config.cfg"
}
variable "domain_memory"{
description = "name of the volume"
type = string
default = 512
}
variable "domain_cpu"{
description = "name of the volume"
type = string
default = 1
}
variable "network_name"{
description = "name of virtual network"
type = string
default = "default"
}
Et le dernier fichier, celui pour afficher l’ip de la VM en fin de process :
output "ip" {
value = libvirt_domain.virt-domain.network_interface[0].addresses[0]
}
Pour finir mon Terraform a besoin de 2 autres fichiers pour le réseau et les informations de configuration cloud-init :
#cloud-config
preserve_hostname: False
manage_etc_hosts: True
hostname: ${hostname}
fqdn: "${hostname}.local"
users:
- name: devops
lock-passwd: false
ssh_pwauth: True
chpasswd: { expire: False }
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin
home: /home/devops
shell: /bin/ash
ssh_authorized_keys:
- ssh-rsa AAAAB3N....kshdiuoJ8
disable_root: False
# Growpart resizes partitions to fill the available disk space
growpart:
mode: auto
devices: ['/']
Pour installer les compléments systèmes et DNSCrypt-Proxy, j’ai créé un rôle Ansible simple, qui réalise les actions suivantes :
---
# tasks file for dnscrypt
- name: Update cache
shell:
cmd: apk update
- name: Upgrade Alpine
shell:
cmd: apk upgrade
- name: Install dnscrypt
package:
name: dnscrypt-proxy
state: latest
update_cache: yes
- name: Copy dnscrypt config file
copy:
src: files/dnscrypt-proxy.toml
dest: /etc/dnscrypt-proxy/
owner: root
group: root
mode: 0644
- name: Enable dnscrypt-proxy service on startup
service:
name: dnscrypt-proxy
enabled: yes
- name: Copy ad-block script
copy:
src: files/update-adblocker.sh
dest: /usr/bin/update-adblocker.sh
owner: root
group: root
mode: u+x,g+x,o+x
- name: Creates a cron file under /etc/cron.d
cron:
name: ad-block
special_time: daily
user: root
job: /usr/bin/update-adblocker.sh
- name: Run script to init adblock
command: /usr/bin/update-adblocker.sh
notify:
- Restart dnscrypt
Dans le fichier de configuration de DNSCrypt-proxy, j’ai volontairement ouvert l’accès à l’ensemble du réseaux, afin que toutes les machines de mon réseau local puissent s’y connecter.
Et voilà, en 3 min vous avez une VM opétationnelle ! L’ensemble des fichiers de configuraiton sont dans le dépot Github du projet.