Aller au contenu

Image Packer et Ansible

Construction de l'image Guacamole avec Packer et trois rôles Ansible : Tomcat, guacd et la webapp Guacamole.


Template Packer

Variables

Créez packer/variables.pkr.hcl :

variable "project_id" {
  type        = string
  description = "ID du projet GCP"
}

variable "zone" {
  type        = string
  default     = "europe-west1-b"
  description = "Zone GCP pour le build"
}

variable "universe_domain" {
  type        = string
  default     = "googleapis.com"
  description = "Universe domain GCP (souverain ou standard)"
}

variable "cloudsql_host" {
  type        = string
  description = "Adresse Private IP de l'instance Cloud SQL"
}

variable "guacamole_version" {
  type        = string
  default     = "1.5.5"
  description = "Version d'Apache Guacamole"
}

Source et build

Créez packer/guacamole.pkr.hcl :

packer {
  required_plugins {
    googlecompute = {
      version = ">= 1.1.0"
      source  = "github.com/hashicorp/googlecompute"
    }
    ansible = {
      version = ">= 1.1.0"
      source  = "github.com/hashicorp/ansible"
    }
  }
}

source "googlecompute" "guacamole" {
  project_id          = var.project_id
  zone                = var.zone
  source_image_family = "debian-12"
  source_image_project_id = "debian-cloud"
  machine_type        = "e2-standard-2"
  ssh_username        = "packer"
  image_name          = "guacamole-{{timestamp}}"
  image_family        = "guacamole"
  image_description   = "Apache Guacamole - Tomcat + guacd + extensions JDBC/SAML"
  disk_size           = 20
  universe_domain     = var.universe_domain
}

build {
  sources = ["source.googlecompute.guacamole"]

  provisioner "ansible" {
    playbook_file = "../ansible/playbook.yml"
    user          = "packer"
    extra_arguments = [
      "--extra-vars", "cloudsql_host=${var.cloudsql_host}",
      "--extra-vars", "guacamole_version=${var.guacamole_version}"
    ]
  }
}

Fichier de variables

Créez packer/guacamole.auto.pkrvars.hcl :

project_id   = "MON-PROJET-GCP"
zone         = "europe-west1-b"
cloudsql_host = "10.0.0.X"  # Adresse Private IP Cloud SQL

Adresse Cloud SQL

L'adresse cloudsql_host sera connue après le provisionnement Cloud SQL (chapitre suivant). Vous pourrez revenir mettre à jour cette valeur.

Playbook Ansible

Créez ansible/playbook.yml :

---
- name: Provisionner Apache Guacamole
  hosts: all
  become: true
  vars:
    guacamole_version: "1.5.5"
    cloudsql_host: "10.0.0.X"
  roles:
    - tomcat
    - guacd
    - guacamole-webapp

Rôle tomcat

Variables par défaut

Créez roles/tomcat/defaults/main.yml :

tomcat_version: "9"
tomcat_java_version: "17"
tomcat_http_port: 8080

Tâches principales

Créez roles/tomcat/tasks/main.yml :

---
- name: Installer OpenJDK
  ansible.builtin.apt:
    name: "openjdk-{{ tomcat_java_version }}-jdk-headless"
    state: present
    update_cache: true

- name: Installer Tomcat
  ansible.builtin.apt:
    name: "tomcat{{ tomcat_version }}"
    state: present

- name: Supprimer les webapps par defaut
  ansible.builtin.file:
    path: "/var/lib/tomcat{{ tomcat_version }}/webapps/{{ item }}"
    state: absent
  loop:
    - ROOT
    - docs
    - examples
    - host-manager
    - manager

- name: Activer Tomcat au demarrage
  ansible.builtin.systemd:
    name: "tomcat{{ tomcat_version }}"
    enabled: true
    state: started

- name: Validation du role
  ansible.builtin.include_tasks: validate.yml
  tags: [validate]

Validation

Créez roles/tomcat/tasks/validate.yml :

---
- name: "Assert : Java installe"
  ansible.builtin.command: java -version
  changed_when: false

- name: "Assert : Tomcat actif"
  ansible.builtin.systemd:
    name: "tomcat{{ tomcat_version }}"
  register: tomcat_status
  failed_when: tomcat_status.status.ActiveState != "active"

- name: "Assert : Tomcat ecoute sur le port configure"
  ansible.builtin.wait_for:
    port: "{{ tomcat_http_port }}"
    timeout: 10

Handlers

Créez roles/tomcat/handlers/main.yml :

---
- name: Restart Tomcat
  ansible.builtin.systemd:
    name: "tomcat{{ tomcat_version }}"
    state: restarted

Rôle guacd

Variables par défaut

Créez roles/guacd/defaults/main.yml :

guacd_bind_host: "127.0.0.1"
guacd_bind_port: 4822

Tâches principales

Créez roles/guacd/tasks/main.yml :

---
- name: Installer les dependances de build
  ansible.builtin.apt:
    name:
      - build-essential
      - libcairo2-dev
      - libjpeg62-turbo-dev
      - libpng-dev
      - libtool-bin
      - uuid-dev
      # RDP
      - libfreerdp-dev
      - freerdp2-dev
      # SSH
      - libssh2-1-dev
      - libpango1.0-dev
      # WebSocket
      - libwebsockets-dev
      - libwebp-dev
      # Aucune dependance VNC (libvncserver-dev NON installe)
    state: present
    update_cache: true

- name: Telecharger guacamole-server
  ansible.builtin.get_url:
    url: "https://apache.org/dyn/closer.lua/guacamole/{{ guacamole_version }}/source/guacamole-server-{{ guacamole_version }}.tar.gz?action=download"
    dest: "/tmp/guacamole-server-{{ guacamole_version }}.tar.gz"
    mode: '0644'

- name: Extraire guacamole-server
  ansible.builtin.unarchive:
    src: "/tmp/guacamole-server-{{ guacamole_version }}.tar.gz"
    dest: /tmp
    remote_src: true

- name: Compiler et installer guacd
  ansible.builtin.shell: |
    cd /tmp/guacamole-server-{{ guacamole_version }}
    ./configure --with-init-dir=/etc/init.d \
      --disable-guacenc \
      --without-vnc
    make -j$(nproc)
    make install
    ldconfig
  args:
    creates: /usr/local/sbin/guacd

- name: Creer le service systemd guacd
  ansible.builtin.copy:
    dest: /etc/systemd/system/guacd.service
    content: |
      [Unit]
      Description=Guacamole proxy daemon
      After=network.target

      [Service]
      ExecStart=/usr/local/sbin/guacd -f -b {{ guacd_bind_host }} -l {{ guacd_bind_port }}
      Restart=on-failure
      User=daemon
      Group=daemon

      [Install]
      WantedBy=multi-user.target
    mode: '0644'
  notify: Restart guacd

- name: Activer guacd au demarrage
  ansible.builtin.systemd:
    name: guacd
    enabled: true
    state: started
    daemon_reload: true

- name: Validation du role
  ansible.builtin.include_tasks: validate.yml
  tags: [validate]

Validation

Créez roles/guacd/tasks/validate.yml :

---
- name: "Assert : guacd actif"
  ansible.builtin.systemd:
    name: guacd
  register: guacd_status
  failed_when: guacd_status.status.ActiveState != "active"

- name: "Assert : guacd ecoute sur le port configure"
  ansible.builtin.wait_for:
    host: "{{ guacd_bind_host }}"
    port: "{{ guacd_bind_port }}"
    timeout: 10

- name: "Assert : VNC non compile"
  ansible.builtin.shell: "! ldd /usr/local/sbin/guacd | grep -q vnc"
  changed_when: false

Handlers

Créez roles/guacd/handlers/main.yml :

---
- name: Restart guacd
  ansible.builtin.systemd:
    name: guacd
    state: restarted

Rôle guacamole-webapp

Variables par défaut

Créez roles/guacamole-webapp/defaults/main.yml :

guacamole_home: "/etc/guacamole"
guacamole_db_name: "guacamole"
guacamole_db_user: "guacamole"
guacamole_db_password: "changeme"
postgresql_driver_version: "42.7.4"

Tâches principales

Créez roles/guacamole-webapp/tasks/main.yml :

---
- name: Creer le repertoire GUACAMOLE_HOME
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    owner: "tomcat"
    group: "tomcat"
    mode: '0755'
  loop:
    - "{{ guacamole_home }}"
    - "{{ guacamole_home }}/extensions"
    - "{{ guacamole_home }}/lib"

- name: Telecharger le WAR Guacamole
  ansible.builtin.get_url:
    url: "https://apache.org/dyn/closer.lua/guacamole/{{ guacamole_version }}/binary/guacamole-{{ guacamole_version }}.war?action=download"
    dest: "/var/lib/tomcat{{ tomcat_version }}/webapps/guacamole.war"
    mode: '0644'
  notify: Restart Tomcat

- name: Telecharger l'extension JDBC PostgreSQL
  ansible.builtin.get_url:
    url: "https://apache.org/dyn/closer.lua/guacamole/{{ guacamole_version }}/binary/guacamole-auth-jdbc-{{ guacamole_version }}.tar.gz?action=download"
    dest: "/tmp/guacamole-auth-jdbc-{{ guacamole_version }}.tar.gz"
    mode: '0644'

- name: Extraire l'extension JDBC
  ansible.builtin.unarchive:
    src: "/tmp/guacamole-auth-jdbc-{{ guacamole_version }}.tar.gz"
    dest: /tmp
    remote_src: true

- name: Installer l'extension JDBC PostgreSQL
  ansible.builtin.copy:
    src: "/tmp/guacamole-auth-jdbc-{{ guacamole_version }}/postgresql/guacamole-auth-jdbc-postgresql-{{ guacamole_version }}.jar"
    dest: "{{ guacamole_home }}/extensions/"
    remote_src: true
    mode: '0644'

- name: Telecharger le driver JDBC PostgreSQL
  ansible.builtin.get_url:
    url: "https://jdbc.postgresql.org/download/postgresql-{{ postgresql_driver_version }}.jar"
    dest: "{{ guacamole_home }}/lib/"
    mode: '0644'

- name: Telecharger l'extension SAML
  ansible.builtin.get_url:
    url: "https://apache.org/dyn/closer.lua/guacamole/{{ guacamole_version }}/binary/guacamole-auth-sso-{{ guacamole_version }}.tar.gz?action=download"
    dest: "/tmp/guacamole-auth-sso-{{ guacamole_version }}.tar.gz"
    mode: '0644'

- name: Extraire l'extension SSO
  ansible.builtin.unarchive:
    src: "/tmp/guacamole-auth-sso-{{ guacamole_version }}.tar.gz"
    dest: /tmp
    remote_src: true

- name: Installer l'extension SAML
  ansible.builtin.copy:
    src: "/tmp/guacamole-auth-sso-{{ guacamole_version }}/saml/guacamole-auth-sso-saml-{{ guacamole_version }}.jar"
    dest: "{{ guacamole_home }}/extensions/"
    remote_src: true
    mode: '0644'

- name: Deployer guacamole.properties
  ansible.builtin.template:
    src: guacamole.properties.j2
    dest: "{{ guacamole_home }}/guacamole.properties"
    owner: tomcat
    group: tomcat
    mode: '0640'
  notify: Restart Tomcat

- name: Configurer GUACAMOLE_HOME pour Tomcat
  ansible.builtin.lineinfile:
    path: "/etc/default/tomcat{{ tomcat_version }}"
    line: "GUACAMOLE_HOME={{ guacamole_home }}"
  notify: Restart Tomcat

- name: Validation du role
  ansible.builtin.include_tasks: validate.yml
  tags: [validate]

Template guacamole.properties

Créez roles/guacamole-webapp/templates/guacamole.properties.j2 :

# Guacd
guacd-hostname: {{ guacd_bind_host | default('127.0.0.1') }}
guacd-port: {{ guacd_bind_port | default('4822') }}

# PostgreSQL (Cloud SQL)
postgresql-hostname: {{ cloudsql_host }}
postgresql-port: 5432
postgresql-database: {{ guacamole_db_name }}
postgresql-username: {{ guacamole_db_user }}
postgresql-password: {{ guacamole_db_password }}
postgresql-auto-create-accounts: true

# SAML (configure au chapitre 05)
# saml-idp-metadata-url:
# saml-idp-url:
# saml-entity-id:
# saml-callback-url:

Validation

Créez roles/guacamole-webapp/tasks/validate.yml :

---
- name: "Assert : WAR deploye"
  ansible.builtin.stat:
    path: "/var/lib/tomcat{{ tomcat_version }}/webapps/guacamole.war"
  register: war_file
  failed_when: not war_file.stat.exists

- name: "Assert : Extension JDBC presente"
  ansible.builtin.find:
    paths: "{{ guacamole_home }}/extensions"
    patterns: "guacamole-auth-jdbc-postgresql-*.jar"
  register: jdbc_ext
  failed_when: jdbc_ext.matched == 0

- name: "Assert : Extension SAML presente"
  ansible.builtin.find:
    paths: "{{ guacamole_home }}/extensions"
    patterns: "guacamole-auth-sso-saml-*.jar"
  register: saml_ext
  failed_when: saml_ext.matched == 0

- name: "Assert : Driver PostgreSQL present"
  ansible.builtin.find:
    paths: "{{ guacamole_home }}/lib"
    patterns: "postgresql-*.jar"
  register: pg_driver
  failed_when: pg_driver.matched == 0

- name: "Assert : guacamole.properties existe"
  ansible.builtin.stat:
    path: "{{ guacamole_home }}/guacamole.properties"
  register: props_file
  failed_when: not props_file.stat.exists

Ordre des rôles

Le rôle guacamole-webapp dépend des variables de tomcat et guacd. Ansible les exécuté dans l'ordre du playbook, donc tomcatguacdguacamole-webapp.