x

Menü

Hashicorp Vault Setup SSH Authentication mit Ansible und Terraform

veröffentlicht in: Devops Cloud Native Datum: 03.03.2021
Martin Buchleitner, Senior IT-Consultant

Über den Autor

Martin Buchleitner ist ein Senior IT-Berater für Infralovers und für Commandemy. Twitter github LinkedIn

Alle Artikel von diesem Autor sehen

Die allermeisten HashiCorp Vault Anleitungen - auch die von HashiCorp selbst - benutzen die Tools oder einfach nur curl um HashiCorp Vault zu konfigurieren. Aber eigentlich wollen wir ja beides nicht verwenden, wenn wir stattdessen lieber terraform Code benutzen können.

Terraform hat bereits einen Provider für HashiCorp Vault, der es uns ermöglicht diese Aufgaben durchzuführen, und so unsere Installation zu warten.

Wir werden hier auch Ansible verwenden, um die One Time Password ( OTP ) Authentifizierung auf der Infrastruktur auszurollen. Folgende Möglichkeiten ergeben sich durch die Verwendung von HashiCorp Vault

  • Zugriffskontrolle auf Infrastruktur mit Subnetz-Bereich als Faktor
  • Kontrolle über die Gültigkeitsdauer eines OTP

Es müssen hier weder SSH Schlüssel gewartet noch Passwörter über das Netzwerk verbreitet werden.

Konfiguration von HashiCorp Vault mit Terraform

Wir starten hier mit Terraform Code, um HashiCorp Vault zu konfigurieren so, dass man einen derartigen Endpunkt hat, der diese Passwörter ausgeben sowie validieren kann.

Der erste Block definiert den Zugriff auf unsere Vault Installation und erstellt zugleich ein Backend in Vault vom Typ SSH. Dieses wird auch unter dem Standardpfad ssh verbunden.

Das können wir jederzeit auch ändern, um es unseren Bedürfnissen anzupassen - jedoch muss diese Änderung auch im Ansible Code parallel dazu erfolgen. Innerhalb von Terraform wird diese Konfiguration einfach weitergegeben und bedarf keinerlei Änderungen.

provider "vault" {
  address = "http://vault.local:8200/"
}

resource "vault_mount" "ssh" {
  type = "ssh"
  path = "ssh"

  default_lease_ttl_seconds = "14400"  # 4h
  max_lease_ttl_seconds     = "604800" # 1w

}

Nun müssen wir noch eine Rolle vom Typ otp definieren. Diese Resource ermöglicht es uns gültige Benutzernamen zu definieren, sowie wie lange diese Passwörter gültig sind.

Außerdem definieren wir hier noch einen Netzwerkbereich für den diese Resource gültige Passwörter ausstellen kann. In unserem Beispiel sind die Benutzer centos und ubuntu möglich, sowie der Netzwerkbereich 192.168.0.0/24. Die Gültigkeitsdauer schränken wir auf 30 Minuten ein.

variable "ssh_cidr" {
    default = "192.168.0.0/24"
}

resource "vault_ssh_secret_backend_role" "otp" {
  name     = "otp"
  backend  = vault_mount.ssh.path
  key_type = "otp"

  allowed_extensions = "permit-pty,permit-port-forwarding"
  default_user       = "centos"
  allowed_users      = "centos,ubuntu"
  cidr_list          = var.ssh_cidr

  ttl = "30m"

}

Damit unsere Clients auch diese Resource nutzen können, bedarf es noch einer Regel. Diese kann dann den einzelnen Benutzern bzw Benutzergruppen innerhalb von Vault zugewiesen werden.

resource "vault_policy" "ssh_client_access" {
  name   = "ssh_clients"
  policy = <<EOT
path "${vault_mount.ssh.path}/creds/otp" {
    capabilities = ["create", "read", "update"]
}
EOT
}

Zum Abschluss können wir nun diese Änderungen anwenden:

export VAULT_TOKEN="my-vault-token"
terraform apply

Zu diesem Zeitpunkt ist HashiCorp Vault so konfiguriert, dass es uns One Time Passwörter zur SSH Authentifizierung ausstellen kann.

Ansible um SSH One Time Passwort Authentifizierung auszurollen

Ansible Rolle

Für Ansible gibt es bereits einige von der Community geschriebene Rollen für HashiCorp Vault. Allerdings ist keine davon aktuell zu dem Zeitpunkt des Artikels. So haben wir eine Variante geklont und mit ein paar Änderungen versehen, damit sie alle Funktionen unterstützt, die wir brauchen. Diese Rolle kann mit dem folgenden ansible-galaxy Befehl installiert werden:

ansible-galaxy install https://github.com/infralovers/ansible-vault-otp

Die Rolle verwendet den vault-ssh-helper für eine Konfiguration der Infrastruktur, um hier die Authentifizierung mittels Vault zu ermöglichen. Dieser SSH Helper validiert das passwort beim SSH Login gegen unsere Vault Konfiguration.

Ansible Rollout

Um die Rolle zu verwenden muss in einem Ansible Playbook folgendes verwenden werden:

- hosts: all
  become: yes
  vars:
  - vault_addr: http://vault.local:8200
  - ssh_mount_point: ssh
  roles:
  - role: infralovers.vault_otp

Wenn ein Vault mit gültigem HTTPS Zertifikat verwendet wird, muss man auch die Variable vault_ca_cert_file auf die jeweilige Zerftifikatsdatei setzen.

Benutzen der One Time Passwort Authentifizierung

Um diese Funktionalität auch benutzen zu können verwenden wir in diesem Beispiel nur curl, wofür wir uns aber eine Funktion schreiben und diese zum Beispiel in .bashrc hinzufügen.

Die folgende Funktion erstellt eine Anfrage an HashiCorp Vault für eine spezifische IP Adresse bzw Hostnamen. Die Funktion sucht auch in unserem Ansible Inventory nach dem Host um diesen eventuell aufzulösen. Wenn es hier nicht fündig wird, wird auch noch die SSH Konfiguration verwendet, um die IP Adresse zu ermitteln. Zu guter Letzt wird noch eine DNS Abfrage versucht. Optional kann auch der Benutzername übergeben werden, dieser wird aber auch versucht aus der SSH Konfiguration auszulesen - standard wird hier einfach ubuntu momentan verwendet.

function vault_otp() {

    VAULT_ADDR="http://vault.local:8200"
    SSH_MOUNT="ssh"
    SSH_ROLE="otp"
    ANSIBLE_INVENTORY=${ANSIBLE_INVENTORY:-/full/path/to/hosts.ini}
    REMOTE_USER="ubuntu"

    if [ -z "$VAULT_TOKEN" ]; then
        echo "missing vault token!"
        return
    fi
    HOSTNAME=$1
    if [ -n "$2" ]; then
        REMOTE_USER="$2"
    fi
    if [ -z "$VAULT_TOKEN" ]; then
        echo "missing vault token!"
        return
    fi

    IP=$(ansible-inventory -i "$ANSIBLE_INVENTORY" --host "$HOSTNAME" 2>/dev/null | jq -r '.ansible_host')

    if [ -z "$IP" ]; then
        IP=$(ssh -G "$HOSTNAME" | awk '$1 == "hostname" p{ print $2 }')
    fi
    CONFIGURED_USER=$(ssh -G "$HOSTNAME" | awk '$1 == "user" p{ print $2 }')
    if [ -n "$CONFIGURED_USER" ]; then
      REMOTE_USER="$CONFIGURED_USER"
    fi

    if [ -z "$IP" ]; then
        IP=$HOSTNAME
    fi

    VALIDATE=$(dig +short "$IP")
    if [ -n "$VALIDATE" ]; then
        IP=$VALIDATE
    fi
    OTP=$( curl \
            --header "X-Vault-Token: $VAULT_TOKEN" \
            --request POST \
            --data "{ \"username\": \"$REMOTE_USER\", \"ip\": \"${IP}\" }" \
            $VAULT_ADDR/v1/$SSH_MOUNT/creds/$SSH_ROLE | jq -r '.data.key')

    echo $OTP

}

Danach kann man sich relativ simpel ein derartiges Passwort anfordern und benutzen:

$ vault_otp my-server
d7a79b69-b3d6-ccce-9d2d-ce9a177b8e73
$ ssh ubuntu@my-server
Password: d7a79b69-b3d6-ccce-9d2d-ce9a177b8e73
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-1025-raspi aarch64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Mar  3 10:16:00 UTC 2021

  System load:  0.08               Temperature:           42.3 C
  Usage of /:   10.6% of 29.05GB   Processes:             147
  Memory usage: 45%                Users logged in:       0
  Swap usage:   0%                 IPv4 address for eth0: 1.2.3.4

 * Introducing self-healing high availability clusters in MicroK8s.
   Simple, hardened, Kubernetes for production, from RaspberryPi to DC.

     https://microk8s.io/high-availability

0 updates can be installed immediately.
0 of these updates are security updates.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Wed Mar  3 00:00:00 2021 from 127.0.0.1
ubuntu at my-server in ~