Wanneer een script een wachtwoord, API key, token of private secret nodig heeft, is de onveiligste plek om het op te slaan direct in de source code. Hardcoded credentials lekken gemakkelijk via Git commits, backups, screenshots, logs, shell history en gekopieerde bestanden.
Een secure setup op Linux volgt meestal één regel:
sla het secret buiten het script op en injecteer het pas tijdens runtime.
Dit artikel legt 10 van de veiligste veelgebruikte opties uit om credentials secure te gebruiken in Linux, wanneer je ze gebruikt en hoe je ze implementeert in Python.
Waarom hardcoding van secrets gevaarlijk is
Laten we kijken naar het gevaar van hardcoding van secrets aan de hand van een voorbeeld. Dit is wat je niet moet doen:
DB_PASSWORD = "SuperSecretPassword123"Zelfs in een private repo kan dat wachtwoord terechtkomen in:
- Git history
- pull requests
- code review tools
- editor autosaves
- backups
- chat screenshots
- logs als het per ongeluk wordt geprint
- AI-conversaties als je je codebase blootstelt aan een LLM
Zelfs als je het later verwijdert, kan Git de oude waarde nog steeds bevatten in eerdere commits.
Algemene security-principes voor het omgaan met credentials
Voordat we naar specifieke voorbeelden kijken, gelden deze principes overal:
-
Houd secrets uit de source code: Je script moet alleen verwijzen naar een variabelenaam zoals
DB_PASSWORD, niet naar het daadwerkelijke wachtwoord.
-
Beperk toegang: Alleen de gebruiker of service account die het secret nodig heeft, mag het kunnen lezen.
-
Geef secrets nooit mee op de command line: Vermijd commando's zoals:
python3 app.py --password mysecret. Command-line arguments kunnen zichtbaar zijn in process listings en logs.
-
Log secrets nooit: Print ze niet voor debugging en let erop dat je ze niet opneemt in exceptions.
-
Geef waar mogelijk de voorkeur aan tokens boven wachtwoorden: Als een service API tokens, short-lived credentials of IAM-based access ondersteunt, zijn die vaak beter dan statische wachtwoorden.
-
Roteer secrets: Zelfs goed beschermde secrets kunnen uiteindelijk uitlekken. Rotatie beperkt de schade.
-
Gebruik gitignore: Als je bijvoorbeeld een
.env-bestand gebruikt, voeg dit dan toe aangitignore
-
Laad wachtwoorden vanuit een environment variable of een beschermd bestand (minimaal)
-
Bestandsrechten: stel bestandsrechten in op
600
Welke optie moet je gebruiken?
In dit artikel behandelen we 10 opties voor het secure omgaan met gevoelige credentials in Linux. Bij het gebruiken van deze opties helpen de algemene richtlijnen je om de beste optie te kiezen:
Voor local development
- Python keyring
-
.env-bestand buiten Git gehouden - Environment variable die interactief is ingesteld
Voor een handmatig admin script
getpass- Python keyring
Voor een server waarop een Linux service draait
- Systemd credentials
- Systemd met een beschermd environment-bestand
Voor containers
- Docker/Kubernetes secrets
- Of de secret manager van je platform
Voor serieuze productie-omgevingen
- Een dedicated secret manager
- Of... op zijn minst
systemdcredentials / mounted secret files
Optie 1: Environment variables gebruiken
Environment variables zijn een van de meest voorkomende benaderingen op Linux en houden het secret buiten je code.
Voor-, nadelen & use cases van environment variables gebruiken
| Voordeel | Nadeel | Beste use case |
| Simpel | Environment variables kunnen zichtbaar zijn voor privileged users of via debugging tools | local development |
| breed ondersteund | Ze kunnen lekken naar logs, crash dumps of child processes als je er niet zorgvuldig mee omgaat | CI/CD |
| gemakkelijk voor automation | Ze zijn vaak prima, maar niet de sterkste optie voor high-security omgevingen | containers |
| houdt het secret buiten de source code |
tijdelijke scriptuitvoering
|
Python-voorbeeld
import os
def main():
db_user = os.getenv("DB_USER", "appuser")
db_host = os.getenv("DB_HOST", "localhost")
db_password = os.getenv("DB_PASSWORD")
if not db_password:
raise RuntimeError("DB_PASSWORD is not set")
print(f"Connecting to {db_host} as {db_user}")
# connect_to_db(user=db_user, password=db_password, host=db_host)
if __name__ == "__main__":
main()Het wachtwoord wordt niet opgeslagen in het script. Het wordt tijdens runtime verwacht vanuit de environment.
Veilige manieren om de variabele in te stellen
Goed: interactief prompten
read -s -p "DB_PASSWORD: " DB_PASSWORD; echo
export DB_PASSWORD
python3 app.py
unset DB_PASSWORDDit voorkomt dat het wachtwoord in de shell history terechtkomt.
Riskanter: inline assignment
DB_PASSWORD='mypassword' python3 app.pyDit is handig, maar niet ideaal op gedeelde systemen omdat het secret zichtbaar is in het shell-commando dat je hebt getypt en mogelijk wordt opgeslagen in de shell history.
Optie 2: Beschermd bestand met strikte permissies
Een andere goede Linux-native optie is om secrets op te slaan in een apart bestand dat alleen leesbaar is voor de juiste gebruiker. Dit is vaak beter dan secrets verspreiden over shell-profielen.
Voor-, nadelen & use cases van beschermde bestanden met strikte permissies
| Voordelen | Nadelen | Use cases |
| Zeer Linux-vriendelijk | Het secret staat nog steeds als plaintext opgeslagen | persoonlijke servers |
| eenvoudig te beheren op servers | Bestandsrechten moeten correct zijn | eenvoudige automation |
| werkt goed met backup- en permissiecontroles | Gemakkelijk om per ongeluk onveilig te kopiëren of te back-uppen | system accounts |
| gecontroleerde VM-omgevingen |
Voorbeeld van een secret-bestand
We kunnen bijvoorbeeld een secret-bestand hebben op deze locatie:
~/.config/myapp/secrets.envHet bestand kan de volgende inhoud hebben:
DB_USER=appuser
DB_HOST=localhost
DB_PASSWORD=replace_meOm het bestand te beschermen met strikte permissies, kun je bijvoorbeeld permissies zoals deze instellen:
mkdir -p ~/.config/myapp
chmod 700 ~/.config/myapp
chmod 600 ~/.config/myapp/secrets.envDit betekent:
- De directory is alleen toegankelijk voor jouw gebruiker
- Het bestand is alleen leesbaar/schrijfbaar voor jouw gebruiker
Python-voorbeeld dat het secret-bestand gebruikt
Je kunt het secret-bestand laden vanuit de shell voordat je Python start, of het direct uitlezen.
Methode A: source het in de shell
set -a
source ~/.config/myapp/secrets.env
set +a
python3 app.py
Methode B: lees het bestand in Python uit
from pathlib import Path
def load_secrets_file(path):
data = {}
for line in Path(path).read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
key, value = line.split("=", 1)
data[key.strip()] = value.strip()
return data
def main():
secrets = load_secrets_file(Path.home() / ".config/myapp/secrets.env")
db_password = secrets.get("DB_PASSWORD")
db_user = secrets.get("DB_USER", "appuser")
db_host = secrets.get("DB_HOST", "localhost")
if not db_password:
raise RuntimeError("DB_PASSWORD missing in secrets file")
print(f"Connecting to {db_host} as {db_user}")
# connect_to_db(user=db_user, password=db_password, host=db_host)
if __name__ == "__main__":
main()
Optie 3: .env-bestand alleen voor development
Een env-bestand is een veelgebruikt convenience-patroon voor developers. Het is prima voor development als je er zorgvuldig mee omgaat, maar het moet niet worden behandeld als een high-security production secret store.
Voor-, nadelen & use cases van .env alleen voor development
| Voordelen | Nadelen | Use cases |
| Zeer handig voor development | Gemakkelijk per ongeluk te committen | local development |
| gebruikelijk ecosystem pattern | Plaintext-bestand | demo's |
| Niet de beste keuze voor productie | non-production setups |
Voorbeeld van een .env-bestand
DB_USER=appuser
DB_HOST=localhost
DB_PASSWORD=replace_me
Python-voorbeeld met python-dotenv
Stap 1
Installeer python-dotenv:
pip install python-dotenv
Stap 2
Maak een script dat python-dotenv implementeert, bijvoorbeeld:
import os
from dotenv import load_dotenv
load_dotenv()
def main():
db_user = os.getenv("DB_USER", "appuser")
db_host = os.getenv("DB_HOST", "localhost")
db_password = os.getenv("DB_PASSWORD")
if not db_password:
raise RuntimeError("DB_PASSWORD is not set")
print(f"Connecting to {db_host} as {db_user}")
# connect_to_db(user=db_user, password=db_password, host=db_host)
if __name__ == "__main__":
main()
Aanbevolen patroon
Commit een template in plaats van een echt .env-bestand met secrets, bijvoorbeeld een bestand met de naam env.example met de volgende inhoud:
DB_USER=appuser
DB_HOST=localhost
DB_PASSWORD=Daarna maakt elke developer zijn of haar eigen lokale env-bestand op basis van het voorbeeldbestand.
Optie 4: Prompt tijdens runtime met getpass
Als een persoon het script handmatig uitvoert, is de veiligste eenvoudige optie vaak om het wachtwoord helemaal niet op te slaan.
Voor-, nadelen & use cases van prompten tijdens runtime met getpass
| Voordelen | Nadelen | Use cases |
| Zeer eenvoudig | Niet bruikbaar voor automation | one-off scripts |
| Niets opgeslagen at rest | Bestaat nog steeds in het geheugen terwijl het proces draait | admin tools |
| Goed voor incidentele admin scripts | lokale handmatige taken |
Python-voorbeeld
from getpass import getpass
def main():
db_user = "appuser"
db_host = "localhost"
db_password = getpass("Enter DB password: ")
print(f"Connecting to {db_host} as {db_user}")
# connect_to_db(user=db_user, password=db_password, host=db_host)
if __name__ == "__main__":
main()Het wachtwoord:
- wordt niet getoond tijdens het typen
- wordt niet opgeslagen in het script
- hoeft niet op schijf te worden opgeslagen
Optie 5: Linux desktop secret store via Python keyring
Voor developer-laptops en workstations is een betere oplossing om de credential store van het OS te gebruiken. Op Linux kan Python’s keyring-library gebruikmaken van:
- Secret Service API
- GNOME Keyring
- KWallet
- andere ondersteunde backends
Voor-, nadelen & use cases van een desktop secret store via Python keyring
| Voordelen | Nadelen | Use cases |
| Beter dan plaintext-bestanden | Kan lastiger zijn om op te zetten op headless servers | Local development op Linux desktop |
| Integreert met desktop security-mechanismen | Backend-ondersteuning verschilt per omgeving | Scripts op een persoonlijke workstation |
| Eenvoudig voor lokale user workflows | Developer tooling |
Een Python keyring gebruiken
Stap 1
Installeer Python keyring:
pip install keyring
Stap 2
Sla een wachtwoord eenmalig op:
import keyring
from getpass import getpass
service_name = "myapp-db"
username = "appuser"
password = getpass("Enter DB password to store: ")
keyring.set_password(service_name, username, password)
print("Password stored securely.")
Stap 3
Haal het wachtwoord op dat in de keyring is opgeslagen in je app
import keyring
def main():
service_name = "myapp-db"
username = "appuser"
db_password = keyring.get_password(service_name, username)
if not db_password:
raise RuntimeError("No password found in keyring")
print("Password loaded from keyring")
# connect_to_db(user=username, password=db_password, host="localhost")
if __name__ == "__main__":
main()
Optie 6: systemd service environment files
Voor production Linux services is systemd vaak de juiste plek om secrets te injecteren. In plaats van het wachtwoord in het script op te slaan, zet je het in een beschermd bestand en laat je systemd het aan het proces leveren.
Voor-, nadelen & use cases van een systemd service gebruiken
| Voordelen | Nadelen | Use cases |
| Past bij standaard deployment van Linux services | Secret bestaat nog steeds als plaintext op schijf | VM-based production apps |
| Houdt het secret buiten de code | Vereist zorgvuldige permissies en ops hygiene | background daemons |
| Goede toegangscontrole via filesystem permissions | interne services op Linux |
Voorbeeldapplicatie met systemd service environment files
Gebruik hetzelfde environment-based Python-script van eerder.
Stap 1
Maak een secret-bestand aan, bijvoorbeeld /etc/myapp/myapp.env, en geef het de volgende inhoud:
DB_USER=appuser
DB_HOST=localhost
DB_PASSWORD=replace_me
Stap 2
Pas de permissies van de directory en bestanden uit stap 1 aan, bijvoorbeeld:
sudo mkdir -p /etc/myapp
sudo chown root:root /etc/myapp
sudo chmod 755 /etc/myapp
sudo chown root:myapp /etc/myapp/myapp.env
sudo chmod 640 /etc/myapp/myapp.env Het bestand is eigendom van root, maar leesbaar voor de myapp-groep.
Stap 3
Maak vervolgens een service unit file aan, bijvoorbeeld etc/systemd/system/myapp.service
[Unit]
Description=My Python App
After=network.target
[Service]
User=myapp
Group=myapp
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
Stap 4
Start de service die je zojuist hebt aangemaakt:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
Optie 7: systemd credentials (veiliger dan gewone env-bestanden)
Als je iets sterkers wilt dan een eenvoudig environment-bestand onder systemd (optie 6), dan is het credential-mechanisme het overwegen waard. Het vermijdt enkele nadelen van standaard environment variables.
In plaats van secrets bloot te stellen als normale environment variables, wordt het secret tijdens runtime via credential files aan de service geleverd. Waarom is dit beter?
- Het voorkomt normale blootstelling via environment variables
- Schonere scheiding tussen app en secret source
- Geschikter voor serieuze service deployments
Het gebruik van systemd credentials is bedoeld voor meer security-bewuste deployments van Linux services en production services die door systemd worden beheerd. Afgezien daarvan gelden de meeste voor-, nadelen en use cases van het gebruik van een systemd service hier ook.
Voorbeeld van een service unit
[Unit]
Description=My Secure Python App
After=network.target
[Service]
User=myapp
Group=myapp
LoadCredential=db_password:/etc/secretstore/db_password
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
Python-voorbeeld
from pathlib import Path
import os
def get_systemd_credential(name: str) -> str:
creds_dir = os.environ.get("CREDENTIALS_DIRECTORY")
if not creds_dir:
raise RuntimeError("CREDENTIALS_DIRECTORY not set")
return (Path(creds_dir) / name).read_text().strip()
def main():
db_password = get_systemd_credential("db_password")
print("Loaded password via systemd credentials")
# connect_to_db(user="appuser", password=db_password, host="localhost")
if __name__ == "__main__":
main()
Optie 8: Docker secrets
Als je app in containers draait, zijn environment variables gebruikelijk, maar Docker secrets zijn veiliger voor gevoelige waarden.
Voor-, nadelen & use cases van Docker secrets gebruiken
| Voordelen | Nadelen | Use cases |
| Beter dan secrets in images bakken | Afhankelijk van de deployment setup | Docker Swarm |
| In veel gevallen beter dan gewone container env vars | Local development gebruikt mogelijk nog steeds env of env vars |
Gecontaineriseerde Linux services |
| Production container deployments |
Voorbeeld Python-code
from pathlib import Path
import os
def get_secret():
secret_file = os.getenv("DB_PASSWORD_FILE", "/run/secrets/db_password")
return Path(secret_file).read_text().strip()
def main():
db_password = get_secret()
print("Loaded password from Docker secret file")
# connect_to_db(user="appuser", password=db_password, host="db")
if __name__ == "__main__":
main() In plaats van DB_PASSWORD direct in te stellen, mount je een secret-bestand in de container.
Optie 9: Kubernetes secrets
Kubernetes Secrets zijn een standaardmanier om secrets aan workloads te leveren. Ze worden vaak als bestanden gemount of als env vars blootgesteld. Voor security hebben mounted files meestal de voorkeur boven environment variables.
Python-voorbeeld
from pathlib import Path
def main():
db_password = Path("/var/run/secrets/db_password").read_text().strip()
print("Loaded password from mounted Kubernetes secret")
# connect_to_db(user="appuser", password=db_password, host="db")
if __name__ == "__main__":
main() Belangrijke opmerking: Kubernetes Secrets zijn niet automatisch "high security" alleen omdat ze secrets heten. Hun bescherming hangt af van:
- etcd-encryptie
- RBAC
- pod security
- clusterconfiguratie
Optie 10: Cloud of externe secret managers
Voor grotere of production-grade systemen zijn dedicated secret managers vaak de beste optie, bijvoorbeeld Google Secret Manager of HashiCorp Vault. Deze kunnen het volgende bieden:
- Toegangscontrole
- Auditing
- Secret rotation
- Tijdelijke credentials
- Centraal beheer
Dedicated secret managers zijn het meest geschikt voor:
- Production infrastructure
- Meerdere services
- Gereguleerde omgevingen
- Teams die auditability en rotation nodig hebben
Generiek Python-patroon
def get_secret_from_manager():
# Placeholder for Vault / AWS / Azure / GCP SDK logic
return "retrieved_secret"
def main():
db_password = get_secret_from_manager()
print("Loaded password from external secret manager")
# connect_to_db(user="appuser", password=db_password, host="db")
if __name__ == "__main__":
main()
Een goed gecombineerd Python-voorbeeld
Deze versie ondersteunt meerdere secure bronnen in een logische volgorde:
- environment variable
- systemd credential
- lokaal secret-bestand
- interactieve prompt
import os
import sys
from pathlib import Path
from getpass import getpass
def get_password() -> str:
pw = os.getenv("DB_PASSWORD")
if pw:
return pw
creds_dir = os.getenv("CREDENTIALS_DIRECTORY")
if creds_dir:
cred_file = Path(creds_dir) / "db_password"
if cred_file.exists():
return cred_file.read_text().strip()
secret_file = Path.home() / ".config/myapp/db_password"
if secret_file.exists():
return secret_file.read_text().strip()
if sys.stdin.isatty():
return getpass("Enter DB password: ")
raise RuntimeError("No password source available")
def main():
db_user = os.getenv("DB_USER", "appuser")
db_host = os.getenv("DB_HOST", "localhost")
db_password = get_password()
print(f"Connecting to {db_host} as {db_user}")
# connect_to_db(user=db_user, password=db_password, host=db_host)
if __name__ == "__main__":
main() Dit houdt het wachtwoord buiten het script en maakt tegelijk secure setups mogelijk in verschillende omgevingen.
Veelgemaakte fouten bij het omgaan met secure credentials
-
Secrets in Git zetten en vertrouwen op
gitignore:gitignorehelpt alleen voordat een bestand wordt gecommit. Zodra het is gecommit, staat het secret in de history.
-
Secrets opslaan in shell startup files: Productiewachtwoorden in
bashrcofzshrczetten is vaak rommelig en te breed. Deze bestanden worden geladen voor veel shell-sessies en kunnen terechtkomen in backups of support bundles.
-
World-readable bestanden gebruiken: Een secret-bestand met
644-permissies is geen secret-bestand. Gebruik: chmod 600 secretfile
-
Configuratie-dictionaries printen: Dit kan per ongeluk waarden zoals wachtwoorden en API keys blootstellen.
- Overal hetzelfde wachtwoord hergebruiken: Als één systeem lekt, worden alle andere ook geraakt.