Securing Sensitive Environment Variables in Linux
Table of Contents
1. [Introduction](#introduction) 2. [Understanding Environment Variables](#understanding-environment-variables) 3. [Security Risks](#security-risks) 4. [Best Practices Overview](#best-practices-overview) 5. [Methods for Securing Environment Variables](#methods-for-securing-environment-variables) 6. [Implementation Examples](#implementation-examples) 7. [Advanced Security Techniques](#advanced-security-techniques) 8. [Monitoring and Auditing](#monitoring-and-auditing) 9. [Common Mistakes and Pitfalls](#common-mistakes-and-pitfalls) 10. [Conclusion](#conclusion)Introduction
Environment variables are a fundamental component of Linux systems, providing a way to pass configuration data to applications and processes. However, when these variables contain sensitive information such as passwords, API keys, database credentials, or encryption keys, they become potential security vulnerabilities if not properly managed.
This comprehensive guide explores various methods and best practices for securing sensitive environment variables in Linux environments, from basic file permissions to advanced encryption techniques and specialized tools.
Understanding Environment Variables
What are Environment Variables
Environment variables are dynamic named values that can affect the way running processes behave on a computer. They are part of the environment in which a process runs and can be accessed by applications to retrieve configuration information.
Types of Environment Variables
| Type | Scope | Persistence | Example | |------|-------|-------------|---------| | System-wide | All users | Permanent | PATH, HOME | | User-specific | Single user | Session/permanent | USER, SHELL | | Process-specific | Single process | Process lifetime | Custom app variables | | Session-specific | Current session | Session lifetime | Temporary variables |
Common Commands for Environment Variables
`bash
Display all environment variables
envDisplay specific variable
echo $VARIABLE_NAMESet temporary variable (current session only)
export TEMP_VAR="value"Display variable with default value if not set
echo ${VARIABLE_NAME:-default_value}Unset variable
unset VARIABLE_NAMEList all variables (including shell variables)
set`Where Environment Variables are Defined
| Location | Scope | Purpose | |----------|-------|---------| | /etc/environment | System-wide | Global variables for all users | | /etc/profile | System-wide | Login shell initialization | | /etc/bash.bashrc | System-wide | Non-login bash shells | | ~/.bashrc | User-specific | User's bash shell configuration | | ~/.profile | User-specific | User's login shell | | ~/.bash_profile | User-specific | User's bash login shell |
Security Risks
Common Security Vulnerabilities
#### Process Visibility Environment variables are visible to other processes running under the same user context through the /proc filesystem:
`bash
View environment variables of a process
cat /proc/PID/environView environment variables of all processes (if permitted)
ps auxeView environment variables in process tree
pstree -p -a`#### Log File Exposure Applications may inadvertently log environment variables, exposing sensitive data:
`bash
Check system logs for potential exposure
grep -r "PASSWORD\|API_KEY\|SECRET" /var/log/Check application logs
journalctl -u service_name | grep -i "env\|variable"`#### Shell History Exposure Commands containing sensitive data may be stored in shell history:
`bash
Check bash history for sensitive data
grep -i "export.password\|export.key\|export.*secret" ~/.bash_history`Risk Assessment Matrix
| Risk Factor | Low | Medium | High | Critical | |-------------|-----|--------|------|----------| | Data Sensitivity | Public info | Internal data | Personal data | Credentials/Keys | | Access Level | Restricted user | Standard user | Privileged user | Root access | | Network Exposure | Local only | Internal network | DMZ | Internet-facing | | Logging Level | Minimal | Standard | Verbose | Debug mode |
Best Practices Overview
Fundamental Security Principles
1. Principle of Least Privilege: Grant minimal necessary access 2. Defense in Depth: Multiple layers of security 3. Separation of Concerns: Isolate sensitive data 4. Regular Auditing: Monitor and review access 5. Encryption: Protect data at rest and in transit
Security Hierarchy
| Security Level | Method | Use Case | |----------------|--------|----------| | Basic | File permissions | Development environments | | Intermediate | Encrypted files | Small production systems | | Advanced | Key management systems | Enterprise environments | | Enterprise | Hardware security modules | High-security requirements |
Methods for Securing Environment Variables
Method 1: Restricted File Permissions
#### Basic File Permission Security
`bash
Create secure environment file
sudo touch /etc/secure_env sudo chmod 600 /etc/secure_env sudo chown root:root /etc/secure_envAdd sensitive variables
sudo tee /etc/secure_env << EOF DATABASE_PASSWORD=super_secret_password API_KEY=abcd1234567890 ENCRYPTION_KEY=very_long_encryption_key EOF`#### Advanced Permission Management
`bash
Create dedicated group for application
sudo groupadd secure_appCreate service user
sudo useradd -r -s /bin/false -g secure_app app_serviceSet restrictive permissions
sudo chmod 640 /etc/secure_env sudo chown root:secure_app /etc/secure_envLoad variables securely in script
if [ -r /etc/secure_env ]; then set -a source /etc/secure_env set +a fi`Method 2: Using systemd for Service Environment Variables
#### Systemd Environment Files
`bash
Create systemd environment directory
sudo mkdir -p /etc/systemd/system/myapp.service.dCreate environment file
sudo tee /etc/systemd/system/myapp.service.d/environment.conf << EOF [Service] EnvironmentFile=-/etc/myapp/environment EOF`#### Secure Service Configuration
`ini
/etc/systemd/system/myapp.service
[Unit] Description=My Secure Application After=network.target[Service] Type=simple User=myapp Group=myapp EnvironmentFile=-/etc/myapp/secure.env ExecStart=/usr/local/bin/myapp Restart=always RestartSec=10
Security settings
NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/myapp PrivateTmp=true[Install]
WantedBy=multi-user.target
`
Method 3: Encryption-Based Solutions
#### GPG Encryption for Environment Files
`bash
Generate GPG key for encryption
gpg --gen-keyCreate and encrypt environment file
cat > sensitive.env << EOF DATABASE_URL=postgresql://user:password@localhost/db API_SECRET=very_secret_api_key PRIVATE_KEY=-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- EOFEncrypt the file
gpg --cipher-algo AES256 --compress-algo 1 --s2k-mode 3 \ --s2k-digest-algo SHA512 --s2k-count 65536 --symmetric \ --output sensitive.env.gpg sensitive.envRemove plain text file
shred -vfz -n 3 sensitive.env`#### Decryption Script
`bash
#!/bin/bash
decrypt_and_load.sh
set -euo pipefail
ENV_FILE_ENCRYPTED="/etc/myapp/sensitive.env.gpg" TEMP_DIR=$(mktemp -d) ENV_FILE_TEMP="$TEMP_DIR/environment"
Cleanup function
cleanup() { if [ -f "$ENV_FILE_TEMP" ]; then shred -vfz -n 3 "$ENV_FILE_TEMP" fi rm -rf "$TEMP_DIR" }Set trap for cleanup
trap cleanup EXIT INT TERMDecrypt environment file
gpg --quiet --batch --yes --decrypt --output "$ENV_FILE_TEMP" "$ENV_FILE_ENCRYPTED"Source the decrypted file
set -a source "$ENV_FILE_TEMP" set +aExecute the application
exec "$@"`Method 4: Using External Key Management Systems
#### HashiCorp Vault Integration
`bash
Install Vault CLI
wget https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip unzip vault_1.15.0_linux_amd64.zip sudo mv vault /usr/local/bin/Configure Vault client
export VAULT_ADDR='https://vault.example.com:8200' export VAULT_TOKEN='your-vault-token'Store secrets in Vault
vault kv put secret/myapp \ database_password="super_secret" \ api_key="abcd1234567890"`#### Vault Integration Script
`bash
#!/bin/bash
vault_env_loader.sh
set -euo pipefail
VAULT_PATH="secret/myapp" VAULT_ADDR="${VAULT_ADDR:-https://vault.example.com:8200}"
Function to retrieve secret from Vault
get_vault_secret() { local path="$1" local field="$2" vault kv get -field="$field" "$path" 2>/dev/null || { echo "Error: Failed to retrieve $field from $path" >&2 exit 1 } }Load secrets from Vault
export DATABASE_PASSWORD=$(get_vault_secret "$VAULT_PATH" "database_password") export API_KEY=$(get_vault_secret "$VAULT_PATH" "api_key") export ENCRYPTION_KEY=$(get_vault_secret "$VAULT_PATH" "encryption_key")Execute application
exec "$@"`Method 5: Container-Based Security
#### Docker Secrets Management
`dockerfile
Dockerfile with security considerations
FROM alpine:3.18Create non-root user
RUN addgroup -g 1001 appgroup && \ adduser -D -s /bin/sh -u 1001 -G appgroup appuserCopy application
COPY --chown=appuser:appgroup app /usr/local/bin/appSwitch to non-root user
USER appuserUse ENTRYPOINT for better signal handling
ENTRYPOINT ["/usr/local/bin/app"]`#### Docker Compose with Secrets
`yaml
docker-compose.yml
version: '3.8'services: app: image: myapp:latest environment: - DATABASE_PASSWORD_FILE=/run/secrets/db_password - API_KEY_FILE=/run/secrets/api_key secrets: - db_password - api_key deploy: resources: limits: memory: 512M cpus: '0.5'
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
`
Implementation Examples
Example 1: Secure Database Connection Script
`bash
#!/bin/bash
secure_db_connect.sh
set -euo pipefail
Configuration
SECRETS_DIR="/etc/myapp/secrets" DB_HOST="${DB_HOST:-localhost}" DB_PORT="${DB_PORT:-5432}" DB_NAME="${DB_NAME:-myapp}"Function to read secret from file
read_secret() { local secret_file="$1" if [ ! -f "$secret_file" ]; then echo "Error: Secret file $secret_file not found" >&2 exit 1 fi # Verify file permissions local perms=$(stat -c "%a" "$secret_file") if [ "$perms" != "600" ] && [ "$perms" != "400" ]; then echo "Warning: Secret file $secret_file has insecure permissions: $perms" >&2 fi cat "$secret_file" }Load database credentials
DB_USER=$(read_secret "$SECRETS_DIR/db_user") DB_PASSWORD=$(read_secret "$SECRETS_DIR/db_password")Construct connection string
export DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"Clear sensitive variables from memory when possible
unset DB_USER DB_PASSWORDExecute application
exec "$@"`Example 2: Systemd Service with Encrypted Environment
`bash
#!/bin/bash
setup_secure_service.sh
set -euo pipefail
SERVICE_NAME="myapp" SERVICE_USER="myapp" SECRETS_DIR="/etc/${SERVICE_NAME}/secrets"
Create service user
if ! id "$SERVICE_USER" &>/dev/null; then sudo useradd -r -s /bin/false -d "/var/lib/$SERVICE_NAME" "$SERVICE_USER" fiCreate directories
sudo mkdir -p "$SECRETS_DIR" sudo mkdir -p "/var/lib/$SERVICE_NAME" sudo mkdir -p "/var/log/$SERVICE_NAME"Set permissions
sudo chown root:root "$SECRETS_DIR" sudo chmod 700 "$SECRETS_DIR" sudo chown "$SERVICE_USER:$SERVICE_USER" "/var/lib/$SERVICE_NAME" sudo chown "$SERVICE_USER:$SERVICE_USER" "/var/log/$SERVICE_NAME"Create encrypted environment file
sudo tee "$SECRETS_DIR/environment.enc" << 'EOF'This would be encrypted content
DATABASE_PASSWORD=encrypted_password_here API_KEY=encrypted_api_key_here EOFCreate decryption script
sudo tee "/usr/local/bin/${SERVICE_NAME}-decrypt" << 'EOF' #!/bin/bash set -euo pipefailSECRETS_DIR="/etc/myapp/secrets" TEMP_ENV=$(mktemp)
cleanup() { [ -f "$TEMP_ENV" ] && shred -vfz -n 3 "$TEMP_ENV" } trap cleanup EXIT
Decrypt environment file (implement your decryption method)
decrypt_file "$SECRETS_DIR/environment.enc" > "$TEMP_ENV"Load environment
set -a source "$TEMP_ENV" set +aExecute application
exec "$@" EOFsudo chmod 755 "/usr/local/bin/${SERVICE_NAME}-decrypt"
`
Example 3: Kubernetes Secret Management
`yaml
kubernetes-secret-example.yaml
apiVersion: v1 kind: Secret metadata: name: myapp-secrets namespace: production type: Opaque stringData: database-password: "super_secret_password" api-key: "abcd1234567890" --- apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: production spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: securityContext: runAsNonRoot: true runAsUser: 1001 fsGroup: 1001 containers: - name: myapp image: myapp:latest env: - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: myapp-secrets key: database-password - name: API_KEY valueFrom: secretKeyRef: name: myapp-secrets key: api-key securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL resources: limits: memory: "512Mi" cpu: "500m" requests: memory: "256Mi" cpu: "250m"`Advanced Security Techniques
Hardware Security Module (HSM) Integration
`bash
#!/bin/bash
hsm_integration.sh
set -euo pipefail
PKCS#11 HSM integration example
PKCS11_MODULE="/usr/lib/x86_64-linux-gnu/pkcs11/libsofthsm2.so" HSM_SLOT="0" HSM_PIN="1234"Initialize HSM token
softhsm2-util --init-token --slot "$HSM_SLOT" --label "MyAppToken" --pin "$HSM_PIN" --so-pin "5678"Function to encrypt data using HSM
hsm_encrypt() { local plaintext="$1" local key_label="$2" echo "$plaintext" | pkcs11-tool --module "$PKCS11_MODULE" \ --slot "$HSM_SLOT" --pin "$HSM_PIN" \ --encrypt --mechanism RSA-PKCS \ --input-format binary --output-format base64 }Function to decrypt data using HSM
hsm_decrypt() { local ciphertext="$1" local key_label="$2" echo "$ciphertext" | base64 -d | pkcs11-tool --module "$PKCS11_MODULE" \ --slot "$HSM_SLOT" --pin "$HSM_PIN" \ --decrypt --mechanism RSA-PKCS \ --input-format binary }`SELinux Integration
`bash
SELinux policy for secure environment variables
myapp.te
policy_module(myapp, 1.0.0)
require { type unconfined_t; type admin_home_t; class file { read write create }; }
Define custom type for application
type myapp_t; type myapp_exec_t; init_daemon_domain(myapp_t, myapp_exec_t)Define type for secret files
type myapp_secret_t; files_type(myapp_secret_t)Allow application to read secret files
allow myapp_t myapp_secret_t:file read_file_perms;Prevent other processes from accessing secrets
neverallow { domain -myapp_t } myapp_secret_t:file *;`AppArmor Profile
`bash
/etc/apparmor.d/usr.local.bin.myapp
#include/usr/local/bin/myapp {
#include `
Monitoring and Auditing
Audit Configuration
`bash
/etc/audit/rules.d/sensitive-env.rules
Monitor access to sensitive environment files
-w /etc/myapp/secrets/ -p ra -k sensitive_env_accessMonitor environment variable access through /proc
-a always,exit -F arch=b64 -S open,openat -F dir=/proc -F success=1 -k proc_env_accessMonitor systemd environment file access
-w /etc/systemd/system/ -p wa -k systemd_config_changeMonitor GPG operations
-w /usr/bin/gpg -p x -k gpg_execution -w /usr/bin/gpg2 -p x -k gpg_execution`Monitoring Script
`bash
#!/bin/bash
env_security_monitor.sh
set -euo pipefail
LOG_FILE="/var/log/env-security.log" ALERT_EMAIL="admin@example.com"
Function to log security events
log_event() { local level="$1" local message="$2" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" >> "$LOG_FILE" if [ "$level" = "CRITICAL" ]; then echo "$message" | mail -s "Security Alert: Environment Variable Access" "$ALERT_EMAIL" fi }Check for suspicious process environment access
check_proc_access() { local suspicious_processes=$(lsof +D /proc 2>/dev/null | grep environ | wc -l) if [ "$suspicious_processes" -gt 0 ]; then log_event "WARNING" "Detected $suspicious_processes processes accessing /proc/*/environ" fi }Check file permissions on secret files
check_secret_permissions() { local secrets_dir="/etc/myapp/secrets" if [ -d "$secrets_dir" ]; then find "$secrets_dir" -type f ! -perm 600 ! -perm 400 | while read -r file; do local perms=$(stat -c "%a" "$file") log_event "CRITICAL" "Insecure permissions $perms on secret file: $file" done fi }Check for environment variables in process lists
check_process_env_exposure() { if ps auxe | grep -q "PASSWORD\|SECRET\|KEY" 2>/dev/null; then log_event "CRITICAL" "Sensitive environment variables detected in process list" fi }Main monitoring loop
main() { while true; do check_proc_access check_secret_permissions check_process_env_exposure sleep 60 done }Run monitoring
main`Log Analysis Commands
`bash
Search audit logs for environment variable access
sudo ausearch -k sensitive_env_access -ts todayCheck for failed access attempts
sudo ausearch -m AVC -ts today | grep environMonitor real-time audit events
sudo ausearch -k sensitive_env_access -i --start nowGenerate audit report
sudo aureport -x --summary -iSearch system logs for environment variable exposure
sudo journalctl --since="1 hour ago" | grep -i "password\|secret\|key"`Common Mistakes and Pitfalls
Security Anti-patterns
| Anti-pattern | Risk Level | Better Alternative | |--------------|------------|-------------------| | Hardcoded secrets in scripts | Critical | External secret files | | World-readable secret files | Critical | Restrictive permissions (600) | | Secrets in version control | Critical | Git hooks and .gitignore | | Plain text environment files | High | Encrypted storage | | Overprivileged service accounts | High | Principle of least privilege | | No audit logging | Medium | Comprehensive monitoring |
Common Configuration Errors
`bash
WRONG: World-readable secret file
chmod 644 /etc/myapp/secrets.envCORRECT: Restrictive permissions
chmod 600 /etc/myapp/secrets.env chown myapp:myapp /etc/myapp/secrets.envWRONG: Exposing secrets in command line
myapp --password=secret123CORRECT: Using environment variables or files
export APP_PASSWORD_FILE=/etc/myapp/password myappWRONG: Logging environment variables
env | loggerCORRECT: Selective logging
echo "Application starting with user: $USER" | logger`Troubleshooting Common Issues
`bash
Debug permission issues
namei -l /etc/myapp/secrets/passwordCheck file ownership and permissions
ls -la /etc/myapp/secrets/Verify group membership
groups myappTest file accessibility
sudo -u myapp cat /etc/myapp/secrets/passwordCheck SELinux context
ls -Z /etc/myapp/secrets/Verify systemd service environment loading
systemctl show myapp.service | grep Environment`Conclusion
Securing sensitive environment variables in Linux requires a multi-layered approach combining proper file permissions, encryption, access controls, and monitoring. The choice of security method should align with your threat model, compliance requirements, and operational complexity.
Key takeaways for implementing secure environment variable management:
1. Never store secrets in plain text in easily accessible locations 2. Use restrictive file permissions (600 or 400) for secret files 3. Implement proper access controls using users, groups, and system policies 4. Encrypt sensitive data at rest using GPG, Vault, or similar tools 5. Monitor and audit access to sensitive environment data 6. Use specialized tools like HashiCorp Vault for enterprise environments 7. Follow the principle of least privilege for all service accounts 8. Regularly rotate sensitive credentials and keys 9. Implement proper cleanup of temporary files containing secrets 10. Test your security measures regularly through audits and penetration testing
By implementing these practices and continuously monitoring your environment, you can significantly reduce the risk of sensitive data exposure while maintaining operational efficiency. Remember that security is an ongoing process that requires regular review and updates as threats and technologies evolve.