Secrets sicher verwalten unter Linux – .env, Keyrings und Leaks vermeiden

API-Keys, Passwörter und Zertifikate unter Linux sicher verwalten: .env-Dateien richtig einsetzen, git-Leaks verhindern, Linux Keyring nutzen, Secrets in systemd-Services und Docker absichern.

9 min Lesezeit

Secrets sicher verwalten unter Linux – .env, Keyrings und Leaks vermeiden

API-Keys im Git-Repo, Passwörter in Config-Dateien, Datenbank-Credentials in Umgebungsvariablen ohne Schutz – diese Fehler passieren täglich und werden täglich ausgenutzt. Entwickler, die mit Linux arbeiten, haben gute Werkzeuge zur Verfügung. Man muss sie nur kennen.

Dieser Guide zeigt, wie du Secrets sicher verwaltest – von der Entwicklungsumgebung bis zum Produktionsserver.


Die häufigsten Fehler

# Klassisches Anti-Pattern: Secret direkt im Code
DATABASE_URL = "mysql://root:geheimespasswort@localhost/db"
API_KEY = "sk-1234abcd..."

# Genauso schlimm: In .env, aber .env ist in Git
git add .env   # NIEMALS!
git commit -m "Add env file"

# Häufig übersehen: Credentials in git log
git log --all -p | grep -i "password\|secret\|api_key"

# Credentials in Prozessliste (sichtbar für alle User!)
mysql -u root -pGeheimesPasswort    # SCHLECHT
mysql -u root -p                    # GUT (Passwort wird interaktiv abgefragt)

.env-Dateien richtig einsetzen

Die .env-Datei ist der Standard für lokale Entwicklung – aber sie muss korrekt eingesetzt werden.

Grundregeln

# .env IMMER in .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo "*.env" >> .gitignore

# .env.example STATTDESSEN in Git (ohne Werte!)
cat .env.example
# .env.example (committet in Git)
# Kopiere zu .env und fülle mit echten Werten

DATABASE_URL=
DATABASE_NAME=
REDIS_HOST=localhost
REDIS_PORT=6379
API_KEY=
MAIL_HOST=
MAIL_PASSWORD=
APP_SECRET=

.env-Datei sichern

# Korrekte Berechtigungen
chmod 600 .env     # nur Besitzer darf lesen/schreiben
chmod 640 .env     # Gruppe kann lesen (für Webserver-Gruppen)

# Besitzer prüfen
ls -la .env
# -rw------- 1 andre andre 456 Feb 18 .env

# Für Produktions-Webserver
sudo chown www-data:www-data /var/www/app/.env
sudo chmod 600 /var/www/app/.env

.env laden in verschiedenen Umgebungen

# Bash: .env manuell laden
export $(grep -v '^#' .env | xargs)

# Sicherer (mit Leerzeichen in Werten):
set -a
source .env
set +a

# In Python
from dotenv import load_dotenv   # pip install python-dotenv
load_dotenv()

# In Node.js
require('dotenv').config()       # npm install dotenv

# In PHP (Laravel macht das automatisch)

Git-Leaks verhindern: .gitignore und git-secrets

.gitignore für Secrets

# Globale .gitignore für alle Projekte
cat >> ~/.gitignore_global << 'EOF'
# Environment files
.env
.env.local
.env.*.local
*.env

# Keys und Zertifikate
*.pem
*.key
*.p12
*.pfx
id_rsa
id_ed25519

# Konfigurationen mit Credentials
config.local.php
database.yml.local
secrets.json
credentials.json
service-account.json
EOF

git config --global core.excludesfile ~/.gitignore_global

gitleaks: Secrets in Git-Repos aufspüren

# Installation
sudo apt install gitleaks   # Ubuntu 22.04+
# oder:
curl -L https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_amd64.tar.gz | \
    tar xz && sudo mv gitleaks /usr/local/bin/

# Aktuelles Repo auf Secrets scannen
gitleaks detect

# Vollständige Git-History scannen (!)
gitleaks detect --source . --log-opts="--all"

# Als Pre-Commit-Hook
gitleaks protect --staged   # nur staged files

pre-commit Hook gegen versehentliche Commits

# .git/hooks/pre-commit
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
# Verhindert Commits mit .env-Dateien

if git diff --cached --name-only | grep -qE "^\.env$|^\.env\..+$"; then
    echo "FEHLER: .env-Datei wird commitet! Entferne sie mit:"
    echo "  git reset HEAD .env"
    exit 1
fi

# Suche nach offensichtlichen Secrets in staged files
if git diff --cached | grep -qiE "(password|secret|api_key|private_key)\s*=\s*['\"][^'\"]{4,}"; then
    echo "WARNUNG: Mögliche Credentials gefunden. Überprüfe deinen Commit!"
    echo "Benutze 'git diff --cached' um zu prüfen."
    read -p "Trotzdem committen? [y/N] " ANTWORT
    [[ "$ANTWORT" =~ ^[yY]$ ]] || exit 1
fi
EOF

chmod +x .git/hooks/pre-commit

Secret aus Git-History entfernen

# WARNING: History-Rewrite – nur wenn sicher keine anderen haben den Commit!

# Mit git-filter-repo (empfohlen)
pip3 install git-filter-repo
git filter-repo --invert-paths --path .env

# Alternativ: mit BFG Repo Cleaner
java -jar bfg.jar --delete-files .env mein-repo.git

# Nach dem Bereinigen: Remote neu setzen
git push origin --force --all
git push origin --force --tags

# Kollegen: Repository neu klonen!
# Der Schlüssel muss trotzdem rotiert werden!

Wichtig: Wenn ein Secret in einem öffentlichen Repo war – auch nur kurz – muss es rotiert (erneuert) werden. GitHub scannt Commits automatisch und benachrichtigt Service-Provider.


Umgebungsvariablen in der Shell

# Variable für aktuelle Session setzen
export DATABASE_PASSWORD="geheimespasswort"

# Nur für einen Befehl
DATABASE_PASSWORD="geheimespasswort" myapp start

# Variablen anzeigen
env | grep DATABASE
printenv DATABASE_PASSWORD

# Sicher löschen (aus der Shell-Session)
unset DATABASE_PASSWORD

# Variable in ~/.bashrc (persistiert zwischen Sessions)
# NUR für nicht-sensible Konfigurationen!
echo 'export EDITOR=nano' >> ~/.bashrc

# NIEMALS Passwörter in ~/.bashrc!

Passwort-Prompt statt Hardcoding

# Passwort interaktiv abfragen und nicht in History speichern
read -s -p "Datenbankpasswort: " DB_PASS
export DB_PASS

# Verwendung
mysql -u root -p"$DB_PASS" << EOF
SHOW DATABASES;
EOF

# Variable wieder löschen
unset DB_PASS

# Shell-History für sensible Befehle deaktivieren
# Befehl mit Leerzeichen einleiten (bei HISTCONTROL=ignorespace):
 mysql -u root -pGeheimesPasswort  # nicht gespeichert!

# HISTCONTROL in ~/.bashrc:
export HISTCONTROL=ignorespace:ignoredups

Linux Keyring / Secret Service

Der Linux Keyring (via GNOME Keyring oder KWallet) speichert Secrets verschlüsselt und entsperrt sie automatisch beim Login.

secret-tool: Kommandozeilen-Zugriff

# Installieren
sudo apt install libsecret-tools   # Ubuntu/Debian

# Secret speichern
secret-tool store --label="Mein API Key" service "meine-api" username "mein-user"
# Passwort eingeben: [wird nicht angezeigt]

# Secret abrufen
secret-tool lookup service "meine-api" username "mein-user"

# In Skript verwenden (Passwort nicht in Skript!)
API_KEY=$(secret-tool lookup service "meine-api" username "mein-user")
curl -H "Authorization: Bearer $API_KEY" https://api.example.com

# Gespeicherte Secrets anzeigen
secret-tool search service "meine-api"

# Secret löschen
secret-tool clear service "meine-api" username "mein-user"

Pass: Das Unix-Passwort-Manager-Tool

# Installieren
sudo apt install pass

# Initialisieren (braucht GPG-Schlüssel)
gpg --gen-key   # falls noch kein GPG-Schlüssel vorhanden
pass init "Deine GPG-E-Mail@beispiel.de"

# Passwort speichern
pass insert entwicklung/database/prod
# Passwort eingeben:

# Passwort abrufen
pass entwicklung/database/prod
# Kopiert automatisch in Zwischenablage:
pass -c entwicklung/database/prod

# Alle gespeicherten Pfade anzeigen
pass

# Zufälliges Passwort generieren und speichern
pass generate entwicklung/api/github 32

# In Skript:
DB_PASS=$(pass entwicklung/database/prod)

Secrets in systemd-Services

Systemd bietet mehrere Wege, Secrets sicher an Services zu übergeben.

EnvironmentFile statt Environment

# /etc/systemd/system/meine-app.service

[Service]
# SCHLECHT: Secret direkt in Unit-Datei (sichtbar via systemctl cat!)
# Environment=DATABASE_PASSWORD=geheimespasswort

# GUT: Aus separater Datei laden
EnvironmentFile=/etc/meine-app/environment

# Datei-Berechtigungen setzen!
# chmod 600 /etc/meine-app/environment
# chown root:meine-app /etc/meine-app/environment
# /etc/meine-app/environment (chmod 600!)
DATABASE_URL=mysql://user:geheimespasswort@localhost/db
API_KEY=sk-1234abcd
REDIS_PASSWORD=redis-geheim
sudo systemctl daemon-reload
sudo systemctl restart meine-app

# Env-Variablen eines Services anzeigen (nur root):
sudo cat /proc/$(systemctl show meine-app --property=MainPID --value)/environ | tr '\0' '\n'

LoadCredential (systemd 247+, sicherer)

# Secret-Datei erstellen
sudo mkdir -p /etc/credentials/meine-app
echo "geheimespasswort" | sudo tee /etc/credentials/meine-app/db-password > /dev/null
sudo chmod 600 /etc/credentials/meine-app/db-password
[Service]
LoadCredential=db-password:/etc/credentials/meine-app/db-password

# Im Service verfügbar als:
# $CREDENTIALS_DIRECTORY/db-password

Secrets in Docker und Docker Compose

.env-Datei für Docker Compose

# .env (nie in Git!)
MARIADB_ROOT_PASSWORD=sicheres-db-passwort
APP_SECRET_KEY=32-zeichen-random-key
REDIS_PASSWORD=redis-geheim

# Generiere sichere Zufallspasswörter:
openssl rand -base64 32   # 32 Bytes = 44 Zeichen base64
python3 -c "import secrets; print(secrets.token_hex(32))"
# compose.yaml
services:
  db:
    image: mariadb:11.6
    environment:
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
    # BESSER: env_file statt environment für Secrets
    env_file:
      - .env

Docker Secrets (Swarm-Modus)

# Secret erstellen
echo "geheimespasswort" | docker secret create db_password -

# oder aus Datei
docker secret create ssl_cert ./cert.pem

# Im Service verwenden
docker service create \
  --secret db_password \
  --env DB_PASSWORD_FILE=/run/secrets/db_password \
  myapp

Secrets nie in Docker-Images

# SCHLECHT: Secret beim Build einbrennen
RUN curl -H "Authorization: Bearer $API_KEY" https://api.example.com

# GUT: Secrets zur Laufzeit injizieren
ENV API_KEY=""   # Platzhalter; echter Wert kommt per -e oder env_file

# BESSER für Build-Secrets: --secret (buildkit)
# docker build --secret id=api_key,env=API_KEY .
RUN --mount=type=secret,id=api_key \
    curl -H "Authorization: Bearer $(cat /run/secrets/api_key)" https://api.example.com

SSH-Schlüssel und GPG-Schlüssel verwalten

SSH: Mehrere Schlüssel sicher verwalten

# Separater Schlüssel pro Zweck
ssh-keygen -t ed25519 -C "github-privat" -f ~/.ssh/id_github
ssh-keygen -t ed25519 -C "arbeit-server" -f ~/.ssh/id_arbeit

# Berechtigungen (SSH verweigert bei falschen Rechten!)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/id_*.pub
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/authorized_keys

# ~/.ssh/config
Host github.com
    IdentityFile ~/.ssh/id_github
    AddKeysToAgent yes

Host arbeit-server
    HostName 10.0.0.5
    IdentityFile ~/.ssh/id_arbeit

GPG: Verschlüsselung und Signierung

# GPG-Schlüssel erstellen
gpg --full-generate-key
# RSA (4096) oder Ed25519 auswählen

# Öffentlichen Schlüssel exportieren
gpg --armor --export meine@email.de > mein-public-key.asc

# Datei verschlüsseln
gpg --recipient meine@email.de --encrypt geheimedatei.txt

# Datei entschlüsseln
gpg --decrypt geheimedatei.txt.gpg > geheimedatei.txt

# Git-Commits signieren
git config --global user.signingkey DEIN_KEY_ID
git config --global commit.gpgsign true

Checkliste: Was niemals in Git

NIEMALS in Git committen:
─────────────────────────
✗ .env Dateien
✗ API-Keys, API-Secrets
✗ Datenbankpasswörter
✗ SSH-Schlüssel (privat!)
✗ SSL/TLS-Zertifikate (.pem, .key, .pfx)
✗ OAuth-Tokens, Session-Keys
✗ AWS-Credentials (~/.aws/credentials)
✗ GCP-Service-Account-JSON
✗ Docker-Registry-Credentials
✗ VPN-Konfigurationen mit Schlüsseln
✗ .htpasswd-Dateien
✗ wp-config.php mit echten Werten

OK in Git (ohne echte Werte):
──────────────────────────────
✓ .env.example (Vorlage ohne Werte)
✓ .gitignore
✓ Öffentliche SSH-Schlüssel (.pub)
✓ Öffentliche GPG-Schlüssel
✓ SSL-Zertifikate (OHNE privaten Schlüssel)
✓ Konfigurationsvorlagen mit Platzhaltern

Secret-Audit im bestehenden Repo

# Mögliche Secrets in der History suchen
git log --all -p | grep -iE "(password|secret|api_key|token|private_key)\s*[=:]\s*['\"]?\S{4,}"

# Mit gitleaks (gründlicher)
gitleaks detect --source . --log-opts="--all" --report-format json | \
    python3 -c "import json,sys; [print(f'{r[\"File\"]}:{r[\"StartLine\"]} {r[\"Description\"]}') for r in json.load(sys.stdin)['Findings']]"

# Auf GitHub eingerichtete Regeln:
# GitHub scannt automatisch auf Secrets und benachrichtigt wenn ein Key leaked

Fazit

Sichere Secret-Verwaltung ist keine Raketenwissenschaft – aber sie erfordert Disziplin:

  1. .env immer in .gitignore – kein Ausnahme, keine Entschuldigung
  2. .env.example ohne Werte in Git – Vorlage für neue Entwickler
  3. gitleaks als pre-commit Hook oder in CI/CD
  4. secret-tool oder pass für lokale Dev-Credentials im Keyring
  5. EnvironmentFile in systemd statt Environment= für Secrets
  6. Docker-Compose .env für Container-Secrets – nie im Image
  7. Bei gelecktem Secret: sofort rotieren – History-Cleanup reicht nicht

War dieser Artikel hilfreich?