Goal
Learn bash scripting fundamentals tailored for security workflows and master automating GPG encryption operations in scripts.
Prerequisites: Weeks 1-9 (encryption, GPG, system administration)
This is Part 1 of 2 - Covers scripting fundamentals and GPG automation.
1. Why Automate Security Workflows?
The Problem: Manual Tasks Get Skipped
Security fatigue is real:
- Manual backups → forgotten until disaster strikes
- Key rotation → procrastinated indefinitely
- Log reviews → “I’ll do it tomorrow” (never happens)
- Security updates → Delayed due to inconvenience
Result: Security degrades over time
The Solution: Automation + Encryption
Automated workflows ensure:
- Backups happen daily without thinking
- Logs get reviewed and archived
- Keys rotate on schedule
- Security tasks run consistently
Goal: “Set it and forget it” security that actually works
2. Bash Scripting Fundamentals Refresher
Basic Script Structure
#!/bin/bash
# Description: What this script does
# Author: Your name
# Date: 2025-10-14
# Variables
SOURCE_DIR="/home/user/documents"
BACKUP_DIR="/mnt/backup"
# Functions
function backup_files() {
echo "Starting backup..."
# Backup logic here
}
# Main execution
backup_files
echo "Backup complete!"
Essential Bash Concepts for Security Scripts
Variables and quoting:
FILE="sensitive document.txt" # Space in filename
cp "$FILE" backup/ # MUST quote variable
Error handling:
#!/bin/bash
set -e # Exit on any error
set -u # Exit on undefined variable
set -o pipefail # Exit if any command in pipe fails
# Now script will stop if anything goes wrong
Testing and conditionals:
if [ -f "/path/to/file" ]; then
echo "File exists"
elif [ -d "/path/to/dir" ]; then
echo "Directory exists"
else
echo "Neither exists"
fi
3. GPG in Scripts: Encrypted Workflows
Password-less GPG with gpg-agent
Problem: Scripts can’t enter passphrases interactively
Solution: Cache passphrase in gpg-agent
# Configure gpg-agent for longer cache
echo "default-cache-ttl 34560000" >> ~/.gnupg/gpg-agent.conf
echo "max-cache-ttl 34560000" >> ~/.gnupg/gpg-agent.conf
# Restart gpg-agent
gpgconf --kill gpg-agent
gpg-agent --daemon
Now scripts can use GPG for ~400 days without re-entering passphrase
Encrypting Files in Scripts
#!/bin/bash
# encrypt-file.sh - Encrypt a file with GPG
FILE="$1"
RECIPIENT="[email protected]"
if [ ! -f "$FILE" ]; then
echo "Error: File $FILE not found"
exit 1
fi
# Encrypt and sign
gpg --encrypt --sign --recipient "$RECIPIENT" --output "${FILE}.gpg" "$FILE"
# Securely delete original
shred -u "$FILE"
echo "Encrypted: ${FILE}.gpg"
Decrypting Files in Scripts
#!/bin/bash
# decrypt-file.sh - Decrypt GPG file
GPG_FILE="$1"
if [ ! -f "$GPG_FILE" ]; then
echo "Error: File $GPG_FILE not found"
exit 1
fi
# Decrypt (gpg-agent handles passphrase)
gpg --decrypt --output "${GPG_FILE%.gpg}" "$GPG_FILE"
echo "Decrypted: ${GPG_FILE%.gpg}"
4. Automated Encrypted Backups
Simple Encrypted Backup Script
#!/bin/bash
# encrypted-backup.sh - Daily encrypted backup
set -euo pipefail # Exit on errors
# Configuration
SOURCE="/home/$USER/documents"
BACKUP_BASE="/mnt/backup"
DATE=$(date +%Y-%m-%d)
BACKUP_DIR="${BACKUP_BASE}/${DATE}"
GPG_RECIPIENT="[email protected]"
LOG_FILE="/var/log/encrypted-backup.log"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Create backup directory
mkdir -p "$BACKUP_DIR"
log "Starting backup to $BACKUP_DIR"
# Tar + GPG encryption pipeline
tar -czf - "$SOURCE" | \
gpg --encrypt --recipient "$GPG_RECIPIENT" \
--output "${BACKUP_DIR}/backup-${DATE}.tar.gz.gpg"
# Verify backup was created
if [ -f "${BACKUP_DIR}/backup-${DATE}.tar.gz.gpg" ]; then
SIZE=$(du -h "${BACKUP_DIR}/backup-${DATE}.tar.gz.gpg" | cut -f1)
log "Backup successful: ${SIZE}"
else
log "ERROR: Backup failed!"
exit 1
fi
# Clean up old backups (keep last 7 days)
find "$BACKUP_BASE" -type d -mtime +7 -exec rm -rf {} \;
log "Old backups cleaned up"
Incremental Encrypted Backups with rsync
#!/bin/bash
# rsync-encrypted-backup.sh - Incremental backups
set -euo pipefail
SOURCE="/home/$USER/documents"
BACKUP_DIR="/mnt/backup/current"
SNAPSHOT_DIR="/mnt/backup/snapshots/$(date +%Y-%m-%d)"
# Rsync with hard links for space efficiency
rsync -av --delete \
--link-dest="$BACKUP_DIR" \
"$SOURCE/" "$SNAPSHOT_DIR/"
# Encrypt the snapshot
tar -czf - "$SNAPSHOT_DIR" | \
gpg --encrypt --recipient "[email protected]" \
--output "${SNAPSHOT_DIR}.tar.gz.gpg"
# Remove unencrypted snapshot
rm -rf "$SNAPSHOT_DIR"
echo "Incremental backup complete: ${SNAPSHOT_DIR}.tar.gz.gpg"
5. Encrypted Logging and Audit Trails
Creating Encrypted Log Files
#!/bin/bash
# log-encrypted.sh - Append to encrypted log
LOG_FILE="/var/log/sensitive.log.gpg"
TEMP_LOG="/tmp/log-temp-$$"
GPG_RECIPIENT="[email protected]"
# If encrypted log exists, decrypt it
if [ -f "$LOG_FILE" ]; then
gpg --decrypt "$LOG_FILE" > "$TEMP_LOG" 2>/dev/null
fi
# Append new log entry
echo "[$(date)] $1" >> "$TEMP_LOG"
# Re-encrypt
gpg --encrypt --recipient "$GPG_RECIPIENT" \
--output "$LOG_FILE" "$TEMP_LOG"
# Securely delete temp file
shred -u "$TEMP_LOG"
Usage:
./log-encrypted.sh "User logged in from 192.168.1.100"
./log-encrypted.sh "Backup completed successfully"
Rotating Encrypted Logs
#!/bin/bash
# rotate-logs.sh - Rotate and archive encrypted logs
LOG_FILE="/var/log/sensitive.log.gpg"
ARCHIVE_DIR="/var/log/archive"
DATE=$(date +%Y-%m)
# Create archive directory
mkdir -p "$ARCHIVE_DIR"
# Move current log to archive
if [ -f "$LOG_FILE" ]; then
mv "$LOG_FILE" "${ARCHIVE_DIR}/sensitive-${DATE}.log.gpg"
echo "Log rotated: ${ARCHIVE_DIR}/sensitive-${DATE}.log.gpg"
fi
# Clean up logs older than 90 days
find "$ARCHIVE_DIR" -name "*.log.gpg" -mtime +90 -delete
6. Automated Key Management
Key Expiration Reminder Script
#!/bin/bash
# check-key-expiration.sh - Warn about expiring GPG keys
DAYS_WARNING=30
EMAIL="[email protected]"
# Get key expiration dates
gpg --list-keys --with-colons "$EMAIL" | \
grep ^pub | \
while IFS=: read -r type trust length algo keyid date expires rest; do
if [ -n "$expires" ]; then
EXPIRE_DATE=$(date -d "@$expires" +%Y-%m-%d)
DAYS_UNTIL=$(( (expires - $(date +%s)) / 86400 ))
if [ $DAYS_UNTIL -lt $DAYS_WARNING ]; then
echo "WARNING: Key $keyid expires in $DAYS_UNTIL days ($EXPIRE_DATE)"
# Send email alert
echo "Your GPG key expires soon!" | mail -s "Key Expiration Warning" "$EMAIL"
fi
fi
done
Automated Subkey Rotation
#!/bin/bash
# rotate-subkey.sh - Generate new encryption subkey
set -euo pipefail
KEY_ID="[email protected]"
EXPIRE_DATE="+1y" # New key valid for 1 year
echo "Generating new encryption subkey..."
# Generate new subkey (non-interactive)
gpg --quick-add-key "$KEY_ID" rsa4096 encr "$EXPIRE_DATE"
# Export updated public key
gpg --armor --export "$KEY_ID" > pubkey-$(date +%Y-%m-%d).asc
# Backup secret keys (encrypted)
gpg --armor --export-secret-keys "$KEY_ID" | \
gpg --encrypt --recipient "$KEY_ID" \
--output "secret-backup-$(date +%Y-%m-%d).gpg"
echo "Subkey rotation complete"
echo "Upload new public key: pubkey-$(date +%Y-%m-%d).asc"
7. Error Handling Best Practices
#!/bin/bash
set -euo pipefail # Exit on error
# Trap errors and clean up
trap 'echo "Error on line $LINENO"; exit 1' ERR
# Check prerequisites
command -v gpg >/dev/null 2>&1 || { echo "GPG not installed"; exit 1; }
# Validate inputs
if [ -z "$SOURCE" ]; then
echo "Error: SOURCE variable not set"
exit 1
fi
# Your script logic here
Up Next
Week 10b covers cron scheduling, security maintenance automation, and building your complete security automation suite.
Key Takeaways
- Automation prevents security fatigue - Consistent execution beats manual effort
- gpg-agent enables scripted encryption - Cache passphrase for automation
- Always use
set -euo pipefail- Scripts should fail loudly on errors - Encrypt your logs too - Audit trails are sensitive data
- Automate key management - Expiration reminders and rotation
- Test scripts manually first - Debug before scheduling