Compare commits
1 Commits
main
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e831176dec |
50
deploy/gitea-app-ini.patch
Normal file
50
deploy/gitea-app-ini.patch
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# ── Gitea app.ini Hardening Patch ────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Apply these changes to /etc/gitea/app.ini (or custom/conf/app.ini)
|
||||||
|
# AFTER running setup-gitea-tls.sh, or apply manually.
|
||||||
|
#
|
||||||
|
# The deploy script handles DOMAIN, ROOT_URL, HTTP_ADDR, and COOKIE_SECURE
|
||||||
|
# automatically. This file documents the FULL recommended hardening config
|
||||||
|
# from the security audit (#971).
|
||||||
|
#
|
||||||
|
# ── Instructions ────────────────────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# 1. Back up your current app.ini:
|
||||||
|
# cp /etc/gitea/app.ini /etc/gitea/app.ini.bak
|
||||||
|
#
|
||||||
|
# 2. Apply each section below by editing app.ini.
|
||||||
|
#
|
||||||
|
# 3. Restart Gitea:
|
||||||
|
# systemctl restart gitea
|
||||||
|
# # or: docker restart gitea
|
||||||
|
|
||||||
|
# ── [server] section ───────────────────────────────────────────────────────
|
||||||
|
# These are set automatically by setup-gitea-tls.sh:
|
||||||
|
#
|
||||||
|
# DOMAIN = git.alexanderwhitestone.com
|
||||||
|
# HTTP_ADDR = 127.0.0.1
|
||||||
|
# HTTP_PORT = 3000
|
||||||
|
# PROTOCOL = http
|
||||||
|
# ROOT_URL = https://git.alexanderwhitestone.com/
|
||||||
|
#
|
||||||
|
# Additionally recommended:
|
||||||
|
# ENABLE_PPROF = false
|
||||||
|
# OFFLINE_MODE = true
|
||||||
|
|
||||||
|
# ── [security] section ─────────────────────────────────────────────────────
|
||||||
|
# INSTALL_LOCK = true
|
||||||
|
# SECRET_KEY = <generate with: gitea generate secret SECRET_KEY>
|
||||||
|
# REVERSE_PROXY_TRUST_LOCAL = true
|
||||||
|
# COOKIE_SECURE = true (set by deploy script)
|
||||||
|
# SET_COOKIE_HTTP_ONLY = true
|
||||||
|
|
||||||
|
# ── [service] section ──────────────────────────────────────────────────────
|
||||||
|
# DISABLE_REGISTRATION = true
|
||||||
|
# ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
|
# SHOW_REGISTRATION_BUTTON = false
|
||||||
|
# ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||||
|
# REQUIRE_SIGNIN_VIEW = true
|
||||||
|
|
||||||
|
# ── [repository] section ───────────────────────────────────────────────────
|
||||||
|
# FORCE_PRIVATE = true
|
||||||
|
# DEFAULT_PRIVATE = private
|
||||||
75
deploy/nginx-gitea.conf
Normal file
75
deploy/nginx-gitea.conf
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# ── Gitea Reverse Proxy — TLS via Let's Encrypt ─────────────────────────────
|
||||||
|
#
|
||||||
|
# Install path: /etc/nginx/sites-available/gitea
|
||||||
|
# Symlink: ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - DNS A record: git.alexanderwhitestone.com -> 143.198.27.163
|
||||||
|
# - certbot + python3-certbot-nginx installed
|
||||||
|
# - Certificate obtained via: certbot --nginx -d git.alexanderwhitestone.com
|
||||||
|
#
|
||||||
|
# After certbot runs, it will auto-modify the ssl lines below.
|
||||||
|
# This config is the pre-certbot template that certbot enhances.
|
||||||
|
|
||||||
|
# ── HTTP → HTTPS redirect ───────────────────────────────────────────────────
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name git.alexanderwhitestone.com;
|
||||||
|
|
||||||
|
# Let's Encrypt ACME challenge
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── HTTPS — reverse proxy to Gitea ──────────────────────────────────────────
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name git.alexanderwhitestone.com;
|
||||||
|
|
||||||
|
# ── TLS (managed by certbot) ────────────────────────────────────────────
|
||||||
|
ssl_certificate /etc/letsencrypt/live/git.alexanderwhitestone.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/git.alexanderwhitestone.com/privkey.pem;
|
||||||
|
|
||||||
|
# ── TLS hardening ───────────────────────────────────────────────────────
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
|
||||||
|
# ── Security headers ────────────────────────────────────────────────────
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Content-Type-Options nosniff always;
|
||||||
|
add_header X-Frame-Options SAMEORIGIN always;
|
||||||
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
||||||
|
|
||||||
|
# ── Proxy to Gitea ──────────────────────────────────────────────────────
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# WebSocket support (for live updates)
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
# Large repo pushes
|
||||||
|
client_max_body_size 512m;
|
||||||
|
|
||||||
|
# Timeouts for large git operations
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
proxy_send_timeout 300;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
299
deploy/setup-gitea-tls.sh
Executable file
299
deploy/setup-gitea-tls.sh
Executable file
@@ -0,0 +1,299 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Gitea TLS Setup — Nginx + Let's Encrypt ─────────────────────────────────
|
||||||
|
#
|
||||||
|
# Sets up a reverse proxy with automatic TLS for the Gitea instance.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Ubuntu/Debian server with root access
|
||||||
|
# - DNS A record pointing to this server's IP
|
||||||
|
# - Gitea running on localhost:3000
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sudo bash deploy/setup-gitea-tls.sh git.alexanderwhitestone.com
|
||||||
|
# sudo bash deploy/setup-gitea-tls.sh git.alexanderwhitestone.com --email admin@alexanderwhitestone.com
|
||||||
|
#
|
||||||
|
# What it does:
|
||||||
|
# 1. Installs Nginx + Certbot
|
||||||
|
# 2. Deploys the Nginx reverse proxy config
|
||||||
|
# 3. Obtains a Let's Encrypt TLS certificate
|
||||||
|
# 4. Patches Gitea app.ini for HTTPS
|
||||||
|
# 5. Blocks direct access to port 3000
|
||||||
|
# 6. Restarts services
|
||||||
|
|
||||||
|
BOLD='\033[1m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${GREEN}[+]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[x]${NC} $1"; }
|
||||||
|
step() { echo -e "\n${BOLD}── $1 ──${NC}"; }
|
||||||
|
|
||||||
|
# ── Parse arguments ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
DOMAIN=""
|
||||||
|
EMAIL=""
|
||||||
|
GITEA_INI="/etc/gitea/app.ini"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--email) EMAIL="$2"; shift 2 ;;
|
||||||
|
--ini) GITEA_INI="$2"; shift 2 ;;
|
||||||
|
--dry-run) DRY_RUN=true; shift ;;
|
||||||
|
-*) error "Unknown option: $1"; exit 1 ;;
|
||||||
|
*) DOMAIN="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$DOMAIN" ]; then
|
||||||
|
error "Usage: $0 <domain> [--email you@example.com] [--ini /path/to/app.ini]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$EMAIL" ]; then
|
||||||
|
EMAIL="admin@${DOMAIN#*.}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${CYAN}${BOLD}"
|
||||||
|
echo " ╔══════════════════════════════════════════╗"
|
||||||
|
echo " ║ Gitea TLS Setup ║"
|
||||||
|
echo " ║ Nginx + Let's Encrypt ║"
|
||||||
|
echo " ╚══════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
echo " Domain: $DOMAIN"
|
||||||
|
echo " Email: $EMAIL"
|
||||||
|
echo " Gitea INI: $GITEA_INI"
|
||||||
|
echo " Dry run: $DRY_RUN"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Preflight checks ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
error "This script must be run as root (or with sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify DNS resolves to this server
|
||||||
|
step "Checking DNS"
|
||||||
|
RESOLVED_IP=$(dig +short "$DOMAIN" 2>/dev/null | head -1)
|
||||||
|
LOCAL_IP=$(curl -4sf https://ifconfig.me 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
|
||||||
|
if [ -z "$RESOLVED_IP" ]; then
|
||||||
|
error "DNS record for $DOMAIN not found."
|
||||||
|
error "Create an A record pointing $DOMAIN to $LOCAL_IP first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RESOLVED_IP" != "$LOCAL_IP" ]; then
|
||||||
|
warn "DNS for $DOMAIN resolves to $RESOLVED_IP but this server is $LOCAL_IP"
|
||||||
|
warn "Let's Encrypt will fail if DNS doesn't point here. Continue anyway? [y/N]"
|
||||||
|
read -r CONTINUE
|
||||||
|
if [ "$CONTINUE" != "y" ] && [ "$CONTINUE" != "Y" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
info "DNS OK: $DOMAIN -> $RESOLVED_IP"
|
||||||
|
|
||||||
|
# Verify Gitea is running
|
||||||
|
step "Checking Gitea"
|
||||||
|
if curl -sf http://127.0.0.1:3000/ > /dev/null 2>&1; then
|
||||||
|
info "Gitea is running on localhost:3000"
|
||||||
|
else
|
||||||
|
warn "Gitea not responding on localhost:3000 — continuing anyway"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $DRY_RUN; then
|
||||||
|
info "Dry run — would install nginx, certbot, configure TLS for $DOMAIN"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 1: Install Nginx + Certbot ────────────────────────────────────────
|
||||||
|
|
||||||
|
step "Installing Nginx + Certbot"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq nginx certbot python3-certbot-nginx
|
||||||
|
info "Nginx + Certbot installed"
|
||||||
|
|
||||||
|
# ── Step 2: Deploy Nginx config ────────────────────────────────────────────
|
||||||
|
|
||||||
|
step "Deploying Nginx Configuration"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
NGINX_CONF="$SCRIPT_DIR/nginx-gitea.conf"
|
||||||
|
|
||||||
|
if [ ! -f "$NGINX_CONF" ]; then
|
||||||
|
error "nginx-gitea.conf not found at $NGINX_CONF"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install config (replacing domain if different)
|
||||||
|
sed "s/git\.alexanderwhitestone\.com/$DOMAIN/g" "$NGINX_CONF" \
|
||||||
|
> /etc/nginx/sites-available/gitea
|
||||||
|
|
||||||
|
ln -sf /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/gitea
|
||||||
|
|
||||||
|
# Remove default site if it conflicts
|
||||||
|
if [ -L /etc/nginx/sites-enabled/default ]; then
|
||||||
|
rm /etc/nginx/sites-enabled/default
|
||||||
|
info "Removed default Nginx site"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test config (will fail on missing cert — that's expected pre-certbot)
|
||||||
|
# First deploy without SSL, get cert, then enable SSL
|
||||||
|
cat > /etc/nginx/sites-available/gitea <<PRESSL
|
||||||
|
# Temporary HTTP-only config for certbot initial setup
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name $DOMAIN;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PRESSL
|
||||||
|
|
||||||
|
nginx -t && systemctl reload nginx
|
||||||
|
info "Temporary HTTP proxy deployed"
|
||||||
|
|
||||||
|
# ── Step 3: Obtain TLS Certificate ─────────────────────────────────────────
|
||||||
|
|
||||||
|
step "Obtaining TLS Certificate"
|
||||||
|
certbot --nginx \
|
||||||
|
-d "$DOMAIN" \
|
||||||
|
--email "$EMAIL" \
|
||||||
|
--agree-tos \
|
||||||
|
--non-interactive \
|
||||||
|
--redirect
|
||||||
|
|
||||||
|
info "TLS certificate obtained and Nginx configured"
|
||||||
|
|
||||||
|
# Now deploy the full config (certbot may have already modified it, but
|
||||||
|
# let's ensure our hardened version is in place)
|
||||||
|
sed "s/git\.alexanderwhitestone\.com/$DOMAIN/g" "$NGINX_CONF" \
|
||||||
|
> /etc/nginx/sites-available/gitea
|
||||||
|
|
||||||
|
nginx -t && systemctl reload nginx
|
||||||
|
info "Full TLS proxy config deployed"
|
||||||
|
|
||||||
|
# ── Step 4: Patch Gitea app.ini ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
step "Patching Gitea Configuration"
|
||||||
|
if [ -f "$GITEA_INI" ]; then
|
||||||
|
# Backup first
|
||||||
|
cp "$GITEA_INI" "${GITEA_INI}.bak.$(date +%Y%m%d%H%M%S)"
|
||||||
|
info "Backed up app.ini"
|
||||||
|
|
||||||
|
# Patch server section
|
||||||
|
sed -i "s|^DOMAIN\s*=.*|DOMAIN = $DOMAIN|" "$GITEA_INI"
|
||||||
|
sed -i "s|^ROOT_URL\s*=.*|ROOT_URL = https://$DOMAIN/|" "$GITEA_INI"
|
||||||
|
sed -i "s|^HTTP_ADDR\s*=.*|HTTP_ADDR = 127.0.0.1|" "$GITEA_INI"
|
||||||
|
|
||||||
|
# Enable secure cookies
|
||||||
|
if grep -q "^COOKIE_SECURE" "$GITEA_INI"; then
|
||||||
|
sed -i "s|^COOKIE_SECURE\s*=.*|COOKIE_SECURE = true|" "$GITEA_INI"
|
||||||
|
else
|
||||||
|
sed -i "/^\[security\]/a COOKIE_SECURE = true" "$GITEA_INI"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Gitea config patched: DOMAIN=$DOMAIN, ROOT_URL=https://$DOMAIN/, HTTP_ADDR=127.0.0.1"
|
||||||
|
else
|
||||||
|
warn "Gitea config not found at $GITEA_INI"
|
||||||
|
warn "Update manually:"
|
||||||
|
warn " DOMAIN = $DOMAIN"
|
||||||
|
warn " ROOT_URL = https://$DOMAIN/"
|
||||||
|
warn " HTTP_ADDR = 127.0.0.1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 5: Block direct port 3000 access ───────────────────────────────────
|
||||||
|
|
||||||
|
step "Blocking Direct Port 3000 Access"
|
||||||
|
if command -v ufw &> /dev/null; then
|
||||||
|
ufw deny 3000/tcp 2>/dev/null || true
|
||||||
|
info "Port 3000 blocked via ufw"
|
||||||
|
else
|
||||||
|
# Use iptables as fallback
|
||||||
|
iptables -A INPUT -p tcp --dport 3000 -j DROP 2>/dev/null || true
|
||||||
|
info "Port 3000 blocked via iptables (not persistent — install ufw for persistence)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure HTTP/HTTPS are allowed
|
||||||
|
if command -v ufw &> /dev/null; then
|
||||||
|
ufw allow 80/tcp 2>/dev/null || true
|
||||||
|
ufw allow 443/tcp 2>/dev/null || true
|
||||||
|
ufw allow 22/tcp 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 6: Restart Gitea ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
step "Restarting Gitea"
|
||||||
|
if systemctl is-active --quiet gitea; then
|
||||||
|
systemctl restart gitea
|
||||||
|
info "Gitea restarted"
|
||||||
|
elif docker ps --format '{{.Names}}' | grep -q gitea; then
|
||||||
|
docker restart "$(docker ps --format '{{.Names}}' | grep gitea | head -1)"
|
||||||
|
info "Gitea container restarted"
|
||||||
|
else
|
||||||
|
warn "Could not auto-restart Gitea — restart it manually"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 7: Verify ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
step "Verifying Deployment"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Check HTTPS
|
||||||
|
if curl -sf "https://$DOMAIN" > /dev/null 2>&1; then
|
||||||
|
info "HTTPS is working: https://$DOMAIN"
|
||||||
|
else
|
||||||
|
warn "HTTPS check failed — may need a moment to propagate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check HSTS
|
||||||
|
HSTS=$(curl -sI "https://$DOMAIN" 2>/dev/null | grep -i "strict-transport-security" || true)
|
||||||
|
if [ -n "$HSTS" ]; then
|
||||||
|
info "HSTS header present: $HSTS"
|
||||||
|
else
|
||||||
|
warn "HSTS header not detected — check Nginx config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check HTTP redirect
|
||||||
|
HTTP_STATUS=$(curl -sI "http://$DOMAIN" 2>/dev/null | head -1 | awk '{print $2}')
|
||||||
|
if [ "$HTTP_STATUS" = "301" ]; then
|
||||||
|
info "HTTP->HTTPS redirect working (301)"
|
||||||
|
else
|
||||||
|
warn "HTTP redirect returned $HTTP_STATUS (expected 301)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Summary ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}${BOLD}"
|
||||||
|
echo " ╔══════════════════════════════════════════╗"
|
||||||
|
echo " ║ Gitea TLS Setup Complete! ║"
|
||||||
|
echo " ╚══════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " Gitea: https://$DOMAIN"
|
||||||
|
echo ""
|
||||||
|
echo " Certbot auto-renewal is enabled by default."
|
||||||
|
echo " Test it: certbot renew --dry-run"
|
||||||
|
echo ""
|
||||||
|
echo " To check status:"
|
||||||
|
echo " nginx -t # test config"
|
||||||
|
echo " systemctl status nginx # proxy status"
|
||||||
|
echo " certbot certificates # TLS cert info"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user