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