Github Actions um Golden Images with HashiCorp Packer zu bauen
In früheren Beiträgen haben wir bereits verschiedene Möglichkeiten gezeigt, wie man mit HashiCorp Packer Golden Images erstellt. In diesem Beitrag zeigen wir,
Im letzten HashiCorp Vault Artikel habne wir bereits einen Weg gezeigt, wie man in seiner Infrastruktur One Time Passwörter von Vault nutzen kann. Dieses Mal verwenden wir nun signierte Public Keys, um auf die Infrastruktur zuzugreifen.
Diese Methode ist für automatisierte Anwendungen nützlicher, aber auch für Benutzer ist es angenehmer - zudem müssen von Administrationsseite keinerlei SSH Benutzer Keys auf der Infrastruktur gewartet werden.
Als Administratoren können wir nun mit dieser Methode die Server so konfigurieren, dass diese einzelne SSH Keys gegen eine Certificate Authority ( CA ) validieren. Wenn ein Benutzer sich mit einem SSH Key, der von dieser CA signiert wurde, verbindet, wird sein Login zugelassen. Zusätzlich ist es möglich diesen signierten Keys auch eine Gültigkeitsdauer ( TTL = time to live ) mitzugeben. So können Benutzer sich zum Beispiel nur für 30 Minuten mittels diesem signierten Key verbinden und müssen dannach wieder einen neuen anfordern.
Weiters können wir auch mehrere Rollen dafür anlegen, um diverse Subnetzbereiche abzudecken und so den Zugriff auf diverse Subnetze auch steuern.
Wir beginnen mit der Konfiguration von HashiCorp Vault mittels terraform, um hier das Authentifizierungs Backend anzulegen. Der erste Block definiert unsere Vault Installation und erstellt in dieser ein Backend mit dem Typ ssh. Dieses wird auch unter dem Pfad ssh eingebunden. Dieser Pfad kann leicht adaptiert werden, da er im Terraform Code weitervererbt wird, aber innerhalb des Ansible Codes muss er dann auch angepasst werden.
provider "vault" {
address = "https://vault.local:8200/"
}
resource "vault_mount" "ssh" {
type = "ssh"
path = "ssh_signed_keys"
default_lease_ttl_seconds = "14400" # 4h
max_lease_ttl_seconds = "604800" # 1 week
}
Nun konfigurieren wir die Rolle, welche es uns erlaubt die Keys zu signieren. Hier setzen wir den key_type
entsprechend auf ca
. Es ist notwendig, dass man es nicht erlaubt, dass hier Host Zertifikate von dieser Rolle erstellt werden. Wie das gemacht werden kann wird in einem nachfolgenden Artikel behandelt.
Innerhalb dieser Rolle definieren wir einen kurzen Gültiskeitsbereich dieser Keys, da wir erreichen wollen, dass Benutzer immer wieder neue Keys anfordern, um neue Verbindungen zu erstellen.
Dieser Code Block verfügt auch schon über eine Policy, die den Benutzern innerhalb von Vault zugeordnet werden muss, damit diese Keys erzeugen können.
resource "vault_ssh_secret_backend_role" "client_keys" {
name = "client_keys"
backend = vault_mount.ssh.path
key_type = "ca"
allow_host_certificates = false
allow_subdomains = false
allow_user_key_ids = false
allow_user_certificates = true
default_extensions = {
"permit-pty" = ""
}
allowed_extensions = "permit-pty,permit-port-forwarding"
default_user = "martin"
allowed_users = "martin,ubuntu"
max_ttl = "30m"
ttl = "10m"
cidr_list = "172.16.0.0/16"
}
resource "vault_policy" "user_signing" {
name = "user_signing"
policy = <<EOT
path "${vault_mount.ssh.path}/sign/${vault_ssh_secret_backend_role.client_keys.name}" {
capabilities = ["create", "read", "update"]
}
EOT
}
Anschließend können wir diesen Code mittels Terraform anwenden - es bedarf hier eines gültigen Tokens, der diese Änderungen in Vault einbringen kann.
1export VAULT_TOKEN="my-vault-token"
2terraform apply
Da wir diesen Provisionierung ja automatisiert durchführen wollen, sollte hier auch nicht unser persönliches Vault token verwendet werden. Aus diesem Grund fügen wir hier Vault noch eine Approle Authentifizierung hinzu um die erfolderlichen Operationen in Vault durchführen zu können.
resource "vault_auth_backend" "approle" {
type = "approle"
}
resource "vault_approle_auth_backend_role" "automated_access" {
backend = data.vault_auth_backend.approle.path
role_name = "automated_access"
token_policies = [ vault_policy.ssh_ca_read.name ]
}
resource "vault_approle_auth_backend_role_secret_id" "automated_access" {
backend = vault_auth_backend.approle.path
role_name = vault_approle_auth_backend_role.automated_access.role_name
}
resource "vault_policy" "ssh_ca_read" {
name = "ssh_ca_read"
policy = <<EOT
path "${vault_mount.ssh.path}/config/ca" {
capabilities = [ "read" ]
}
EOT
}
output "approle_id" {
value = vault_approle_auth_backend_role.automated_access.role_id
}
output "secret_id" {
value = vault_approle_auth_backend_role_secret_id.automated_access.secret_id
}
Und abermals wenden wir diese Änderung gegenüber Vault an, um diese Konfiguration anzuwenden.
1export VAULT_TOKEN="my-vault-token"
2terraform apply
Zu diesem Zeitpunkt müssen wir nun unser Vault Token nicht mehr verwenden, um unsere Infrastruktur zu provisionieren.
Leider gab es für diesen Zweck noch keine Ansible Rolle, so haben wir diese selbst geschrieben, um hier eine Provisionierung durchführen zu können. Mit dem folgenden ansible-galaxy
Kommando kann man die Rolle benutzen:
1ansible-galaxy install https://github.com/infralovers/ansible-vault-ssh-signed-keys
Die Rolle interagiert rein mit der HTTP API von HashiCorp Vault. Es bedarf also keiner weiteren Toolinstallation.
Um die Rolle nun auch in einem Playbook zu verwenden dient der folgende Code Block. Zustätzlich zu diesem müssen auch die Variablen vault_host_role_id
und vault_host_secret_id
zur Verfügung gestellt werden. Wir empfehlen hierfür Ansible Vault zu verwenden. Diese beiden Variablen wurden oben in der Approle Authentifizierung erzeugt. Aber es ist natürlich auch möglich diese Variablen über die Kommandzeile anzugeben.
Für die Validierung von Benutzer Keys wird die Option TrustedUserCAKeys
von SSH Dämon konfiguriert.
- hosts: all
become: yes
vars:
- vault_addr: http://vault.local:8200/
- user_ssh_path: "ssh_signed_keys"
roles:
- role: vault-ssh-signed-keys
Wenn wir nun Ansible Vault verwenden, schaut der Aufruf folgendermaßen aus:
1ansible-playbook vault-ssh.yml
Wenn wir sie per Kommandozeile angeben so ( wobei diese nicht der empfohlene Weg ist ):
1ansible-playbook vault-ssh.yml -e vault_host_role_id="<your-approle-id>" -e vault_host_secret_id="<your-secret-id>"
Nach diesem Schritt sind nun alle Hosts so konfiguriert, dass sich Benutzer mit einem von HashiCorp Vault signierten SSH Key authentifizieren können.
Nun wollen wir diese Konfiguration auch als Benutzer verwenden, um uns mit der Infrastruktur zu verbinden. Dies ist nun ein 2 stufiger Prozess:
Die Signierung des Keys kann durch die unten folgende Fuktionen gemacht werden. Sämtliche Parameter der Funktion können über Umgebungsvariablen gesteuert werden. Hierzu lohnt ein Blick auf "Verzeichnis basierte Profile", um diese Variablen auf Grund des aktuellen Zeichnisses zu verändern.
Zu diesem Punkt erwarten die Funktionen eine Umgebungsvariable VAULT_TOKEN
. Diese kann zum Beispiel über eine weitere Funktion erstellt werden.
vault_sign_key () {
VAULT_ADDR="${VAULT_ADDR:-http://vault.local:8200}"
VAULT_MOUNT=${VAULT_MOUNT:-signed_keys}
VAULT_ROLE=${VAULT_ROLE:-client_keys}
VAULT_PUBLIC_SSH_KEY=${VAULT_PUBLIC_SSH_KEY:-"$HOME/.ssh/id_rsa.pub"}
VAULT_SIGNED_KEY=${VAULT_SIGNED_KEY:-"$HOME/.ssh/vault_signed_key.pub"}
SSH_USER=${SSH_USER:-ubuntu}
if [[ ! -n "${VAULT_TOKEN}" ]]; then
echo "[ERR] No vault access token found at ${VAULT_TOKEN}"
return
fi
export TMP_DIR=$(mktemp -d)
cat > "$(echo ${TMP_DIR}/ssh-ca.json)" << EOF
{
"public_key": "$(cat ${VAULT_PUBLIC_SSH_KEY})",
"valid_principals": "${SSH_USER}"
}
EOF
if ! curl -s --fail -H "X-Vault-Token: ${VAULT_TOKEN}" -X POST -d @${TMP_DIR}/ssh-ca.json \
${VAULT_ADDR}/v1/${VAULT_MOUNT}/sign/${VAULT_ROLE} | jq -r .data.signed_key > "${VAULT_SIGNED_KEY}" ; then
echo "[ERR] Failed to sign public key."
fi
chmod 0600 $VAULT_SIGNED_KEY
rm -rf $TMP_DIR
}
Wir können nun einen gültig signierten SSH Key erzeugen, der nun innerhalb der Infrastruktur benutzt werden kann. So nutzen wir diese Funktion anschließend innerhalb einer weiteren, welche uns genau diese SSH Verbindung aufbaut.
vault_ssh () {
if [[ -z "${1}" ]]; then
echo "[INFO] Usage: vault_ssh user@host [-p 2222]"
return
fi
if [[ "${1}" =~ ^-+ ]]; then
echo "[ERR] Additional SSH flags must be passed after the hostname. e.g. 'vssh user@host -p 2222'"
return
elif [[ "${1}" =~ ^[a-zA-Z]+@[a-zA-Z]+ ]]; then
SSH_USER=$(echo $1 | cut -d'@' -f1)
SSH_HOST=$(echo $1 | cut -d'@' -f2)
else
SSH_USER=$(whoami)
SSH_HOST=${1}
fi
SSH_CONFIG_USER=$(ssh -G "$SSH_HOST" | awk '$1 == "user" p{ print $2 }')
if [ -n "$SSH_CONFIG_USER" ]; then
SSH_USER=$SSH_CONFIG_USER
fi
VAULT_PRIVATE_SSH_KEY=${VAULT_PRIVATE_SSH_KEY:$HOME/.ssh/id_ed25519_private}
VAULT_SIGNED_KEY=$(echo "$HOME/.ssh/vault_signed_key.pub")
# sign the public key
vault_sign_key
# shift arguments one to the left to remove target address
shift 1
# construct an SSH command with the credentials, and append any extra args
ssh -i ${VAULT_SIGNED_KEY} -i ${VAULT_PRIVATE_SSH_KEY} ${SSH_USER}@${SSH_HOST} $@
}
Mit diesen Funktionen können wir nun eine SSH Verbindung zu jenen Server erzeugen, wo vorhin die Option TrustedUserCAKeys
in ssh gesetzt wurde und welche auch im gültigen Subnetzbereich sind. Für Administratoren bedeutet dies auch, dass sie auf den jeweiligen Servern keine Anpassen machen müssen, diese Berechtigungen müssen rein per HashiCorp Vault getätigt werden
1$ vault_ssh my-server
2Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-1025-raspi aarch64)
3
4 * Documentation: https://help.ubuntu.com
5 * Management: https://landscape.canonical.com
6 * Support: https://ubuntu.com/advantage
7
8 System information as of Wed Mar 3 10:16:00 UTC 2021
9
10 System load: 0.08 Temperature: 42.3 C
11 Usage of /: 10.6% of 29.05GB Processes: 147
12 Memory usage: 45% Users logged in: 0
13 Swap usage: 0% IPv4 address for eth0: 1.2.3.4
14
15 * Introducing self-healing high availability clusters in MicroK8s.
16 Simple, hardened, Kubernetes for production, from RaspberryPi to DC.
17
18 https://microk8s.io/high-availability
19
200 updates can be installed immediately.
210 of these updates are security updates.
22
23Last login: Wed Mar 8 00:00:00 2021 from 127.0.0.1
24ubuntu at my-server in ~
Sie interessieren sich für unsere Trainings oder haben einfach eine Frage, die beantwortet werden muss? Sie können uns jederzeit kontaktieren! Wir werden unser Bestes tun, um alle Ihre Fragen zu beantworten.
Hier kontaktieren