Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca4daa1539 |
136
git-harden.sh
136
git-harden.sh
@@ -10,7 +10,7 @@ IFS=$'\n\t'
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Constants
|
# Constants
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
readonly VERSION="0.3.0"
|
readonly VERSION="0.3.1"
|
||||||
readonly BACKUP_DIR="${HOME}/.config/git"
|
readonly BACKUP_DIR="${HOME}/.config/git"
|
||||||
readonly HOOKS_DIR="${HOME}/.config/git/hooks"
|
readonly HOOKS_DIR="${HOME}/.config/git/hooks"
|
||||||
readonly ALLOWED_SIGNERS_FILE="${HOME}/.config/git/allowed_signers"
|
readonly ALLOWED_SIGNERS_FILE="${HOME}/.config/git/allowed_signers"
|
||||||
@@ -1552,46 +1552,95 @@ setup_allowed_signers() {
|
|||||||
# SSH config hardening
|
# SSH config hardening
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
apply_ssh_directive() {
|
ssh_directive_needs_change() {
|
||||||
local directive="$1"
|
local directive="$1"
|
||||||
local value="$2"
|
local value="$2"
|
||||||
|
|
||||||
# Check if directive already exists with correct value (case-insensitive directive match)
|
|
||||||
local current
|
local current
|
||||||
current="$(grep -i "^[[:space:]]*${directive}[[:space:]=]" "$SSH_CONFIG" 2>/dev/null | head -1 | sed 's/^[[:space:]]*[^[:space:]=]*[[:space:]=]*//' || true)"
|
current="$(grep -i "^[[:space:]]*${directive}[[:space:]=]" "$SSH_CONFIG" 2>/dev/null | head -1 | sed 's/^[[:space:]]*[^[:space:]=]*[[:space:]=]*//' || true)"
|
||||||
current="$(strip_ssh_value "$current")"
|
current="$(strip_ssh_value "$current")"
|
||||||
|
|
||||||
if [ "$current" = "$value" ]; then
|
[ "$current" != "$value" ]
|
||||||
return
|
}
|
||||||
fi
|
|
||||||
|
apply_single_ssh_directive() {
|
||||||
|
local directive="$1"
|
||||||
|
local value="$2"
|
||||||
|
|
||||||
|
local current
|
||||||
|
current="$(grep -i "^[[:space:]]*${directive}[[:space:]=]" "$SSH_CONFIG" 2>/dev/null | head -1 | sed 's/^[[:space:]]*[^[:space:]=]*[[:space:]=]*//' || true)"
|
||||||
|
current="$(strip_ssh_value "$current")"
|
||||||
|
|
||||||
if [ -n "$current" ]; then
|
if [ -n "$current" ]; then
|
||||||
# Directive exists but with wrong value
|
# Replace existing directive
|
||||||
if prompt_yn "Update SSH directive: $directive $current -> $value?"; then
|
local tmpfile
|
||||||
# Use temp file to avoid sed -i portability issues
|
tmpfile="$(mktemp "${SSH_CONFIG}.XXXXXX")"
|
||||||
local tmpfile
|
local replaced=false
|
||||||
tmpfile="$(mktemp "${SSH_CONFIG}.XXXXXX")"
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
trap 'rm -f "$tmpfile"' EXIT
|
if [ "$replaced" = false ] && printf '%s' "$line" | grep -qi "^[[:space:]]*${directive}[[:space:]=]"; then
|
||||||
# Replace first occurrence of the directive (case-insensitive)
|
printf '%s %s\n' "$directive" "$value"
|
||||||
local replaced=false
|
replaced=true
|
||||||
while IFS= read -r line || [ -n "$line" ]; do
|
else
|
||||||
if [ "$replaced" = false ] && printf '%s' "$line" | grep -qi "^[[:space:]]*${directive}[[:space:]=]"; then
|
printf '%s\n' "$line"
|
||||||
printf '%s %s\n' "$directive" "$value"
|
fi
|
||||||
replaced=true
|
done < "$SSH_CONFIG" > "$tmpfile"
|
||||||
else
|
mv "$tmpfile" "$SSH_CONFIG"
|
||||||
printf '%s\n' "$line"
|
chmod 600 "$SSH_CONFIG"
|
||||||
fi
|
|
||||||
done < "$SSH_CONFIG" > "$tmpfile"
|
|
||||||
mv "$tmpfile" "$SSH_CONFIG"
|
|
||||||
chmod 600 "$SSH_CONFIG"
|
|
||||||
print_info "Updated $directive = $value in $SSH_CONFIG"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
# Directive missing entirely
|
printf '%s %s\n' "$directive" "$value" >> "$SSH_CONFIG"
|
||||||
if prompt_yn "Add SSH directive: $directive $value?"; then
|
fi
|
||||||
printf '%s %s\n' "$directive" "$value" >> "$SSH_CONFIG"
|
}
|
||||||
print_info "Added $directive $value to $SSH_CONFIG"
|
|
||||||
|
apply_ssh_directive_group() {
|
||||||
|
local group_name="$1"
|
||||||
|
local description="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
# Collect pending changes (directives that need updating)
|
||||||
|
local pending_keys=""
|
||||||
|
local pending_vals=""
|
||||||
|
local pending_explanations=""
|
||||||
|
local count=0
|
||||||
|
|
||||||
|
while [ $# -ge 3 ]; do
|
||||||
|
local key="$1" value="$2" explanation="$3"
|
||||||
|
shift 3
|
||||||
|
if ssh_directive_needs_change "$key" "$value"; then
|
||||||
|
pending_keys="${pending_keys}${key}"$'\n'
|
||||||
|
pending_vals="${pending_vals}${value}"$'\n'
|
||||||
|
pending_explanations="${pending_explanations}${explanation}"$'\n'
|
||||||
|
count=$((count + 1))
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$count" -eq 0 ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n %b%s%b\n' "$BOLD" "$group_name" "$RESET" >&2
|
||||||
|
printf ' %s\n\n' "$description" >&2
|
||||||
|
|
||||||
|
local i=0
|
||||||
|
while [ "$i" -lt "$count" ]; do
|
||||||
|
local key val expl
|
||||||
|
key="$(printf '%s' "$pending_keys" | sed -n "$((i + 1))p")"
|
||||||
|
val="$(printf '%s' "$pending_vals" | sed -n "$((i + 1))p")"
|
||||||
|
expl="$(printf '%s' "$pending_explanations" | sed -n "$((i + 1))p")"
|
||||||
|
printf ' %-45s %s\n' "${key} ${val}" "# ${expl}" >&2
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
printf '\n' >&2
|
||||||
|
|
||||||
|
if prompt_yn "Apply these ${count} directives?"; then
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt "$count" ]; do
|
||||||
|
local key val
|
||||||
|
key="$(printf '%s' "$pending_keys" | sed -n "$((i + 1))p")"
|
||||||
|
val="$(printf '%s' "$pending_vals" | sed -n "$((i + 1))p")"
|
||||||
|
apply_single_ssh_directive "$key" "$val"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
print_info "Applied ${count} SSH directives"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1612,11 +1661,28 @@ apply_ssh_config() {
|
|||||||
print_info "Created $SSH_CONFIG with mode 600"
|
print_info "Created $SSH_CONFIG with mode 600"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
apply_ssh_directive "StrictHostKeyChecking" "accept-new"
|
apply_ssh_directive_group "Host Verification" \
|
||||||
apply_ssh_directive "HashKnownHosts" "yes"
|
"Trust-on-first-use (TOFU): accept new host keys automatically, but reject
|
||||||
apply_ssh_directive "IdentitiesOnly" "yes"
|
changed keys (the actual MITM scenario). The default 'ask' just trains users
|
||||||
apply_ssh_directive "AddKeysToAgent" "yes"
|
to blindly type 'yes'. Hashing known_hosts prevents hostname enumeration if
|
||||||
apply_ssh_directive "PubkeyAcceptedAlgorithms" "ssh-ed25519,sk-ssh-ed25519@openssh.com,ecdsa-sha2-nistp256,sk-ecdsa-sha2-nistp256@openssh.com"
|
the file is exfiltrated." \
|
||||||
|
"StrictHostKeyChecking" "accept-new" "Auto-accept new hosts, reject changed keys" \
|
||||||
|
"HashKnownHosts" "yes" "Hash hostnames in known_hosts (privacy)"
|
||||||
|
|
||||||
|
apply_ssh_directive_group "Key & Agent Management" \
|
||||||
|
"Without IdentitiesOnly, ssh-agent offers ALL loaded keys to every server —
|
||||||
|
a malicious server can enumerate which services you have access to.
|
||||||
|
AddKeysToAgent reduces passphrase fatigue so developers actually use them." \
|
||||||
|
"IdentitiesOnly" "yes" "Only offer keys explicitly configured (prevents key leakage)" \
|
||||||
|
"AddKeysToAgent" "yes" "Auto-add keys to ssh-agent after first use"
|
||||||
|
|
||||||
|
apply_ssh_directive_group "Algorithm Restrictions" \
|
||||||
|
"Disables RSA and DSA negotiation entirely. This prevents downgrade attacks
|
||||||
|
to weaker algorithms. May break connections to legacy servers that only
|
||||||
|
support RSA — those servers should be upgraded (RSA-SHA1 deprecated since
|
||||||
|
OpenSSH 8.7)." \
|
||||||
|
"PubkeyAcceptedAlgorithms" "ssh-ed25519,sk-ssh-ed25519@openssh.com,ecdsa-sha2-nistp256,sk-ecdsa-sha2-nistp256@openssh.com" \
|
||||||
|
"Ed25519 + ECDSA (software and hardware-backed)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user