mirror of
https://github.com/acedanger/shell.git
synced 2025-12-06 03:20:12 -08:00
feat: Implement comprehensive security enhancements in restoration scripts, including path validation, SQL injection prevention, and improved container management
This commit is contained in:
@@ -158,16 +158,44 @@ send_notification() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Cleanup function to ensure containers are restarted
|
||||
# Cleanup function to ensure containers are restarted and temp files cleaned
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
log_message "Running cleanup..."
|
||||
|
||||
# Clean up any temporary directories that might still exist
|
||||
find /tmp -maxdepth 1 -name "tmp.*" -user "$(whoami)" -mmin +60 2>/dev/null | while read -r tmpdir; do
|
||||
if [[ "$tmpdir" =~ ^/tmp/tmp\. ]]; then
|
||||
log_message "Cleaning up old temporary directory: $tmpdir"
|
||||
rm -rf "$tmpdir" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Restart containers if they were stopped
|
||||
if [ "$CONTAINERS_STOPPED" = true ]; then
|
||||
log_message "Restarting Immich containers..."
|
||||
docker start immich_postgres 2>/dev/null || true
|
||||
docker start immich_server 2>/dev/null || true
|
||||
|
||||
# Start PostgreSQL first
|
||||
if ! docker start immich_postgres 2>/dev/null; then
|
||||
log_message "Error: Failed to restart immich_postgres container"
|
||||
else
|
||||
# Wait for PostgreSQL to be ready before starting other services
|
||||
local max_wait=30
|
||||
local wait_count=0
|
||||
while [ $wait_count -lt $max_wait ]; do
|
||||
if docker exec immich_postgres pg_isready >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
wait_count=$((wait_count + 1))
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# Start Immich server
|
||||
if ! docker start immich_server 2>/dev/null; then
|
||||
log_message "Warning: Failed to restart immich_server container"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
|
||||
# Check if containers started successfully
|
||||
@@ -286,7 +314,25 @@ manage_containers() {
|
||||
fi
|
||||
|
||||
# Wait for containers to fully stop
|
||||
sleep 5
|
||||
log_message "Waiting for containers to stop completely..."
|
||||
local max_stop_attempts=10
|
||||
local stop_attempt=0
|
||||
|
||||
while [ $stop_attempt -lt $max_stop_attempts ]; do
|
||||
if ! docker ps -q --filter "name=immich_postgres" | grep -q . && \
|
||||
! docker ps -q --filter "name=immich_server" | grep -q .; then
|
||||
log_message "✓ All containers stopped successfully"
|
||||
break
|
||||
fi
|
||||
stop_attempt=$((stop_attempt + 1))
|
||||
log_message "Waiting for containers to stop... (attempt $stop_attempt/$max_stop_attempts)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $stop_attempt -eq $max_stop_attempts ]; then
|
||||
log_message "Warning: Containers may not have stopped completely"
|
||||
fi
|
||||
|
||||
CONTAINERS_STOPPED=true
|
||||
|
||||
elif [ "$action" = "start" ]; then
|
||||
@@ -338,6 +384,7 @@ restore_database() {
|
||||
|
||||
# Create temporary directory for decompression if needed
|
||||
local temp_dir=$(mktemp -d)
|
||||
chmod 700 "$temp_dir" # Secure permissions for temporary directory
|
||||
local sql_file="$DB_BACKUP"
|
||||
|
||||
# Decompress if it's a gzipped file
|
||||
@@ -360,28 +407,28 @@ restore_database() {
|
||||
log_message "Error: Failed to start immich_postgres container"
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
}
|
||||
} # Wait for PostgreSQL to be ready for database operations
|
||||
log_message "Waiting for PostgreSQL to be ready for database operations..."
|
||||
local max_attempts=30
|
||||
local attempt=0
|
||||
|
||||
# Wait for PostgreSQL to be ready
|
||||
log_message "Waiting for PostgreSQL to be ready..."
|
||||
local max_attempts=30
|
||||
local attempt=0
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
# Check both connectivity and ability to perform database operations
|
||||
if docker exec immich_postgres pg_isready -U "$DB_USERNAME" >/dev/null 2>&1 && \
|
||||
docker exec immich_postgres psql -U "$DB_USERNAME" -c "SELECT 1;" >/dev/null 2>&1; then
|
||||
log_message "✓ PostgreSQL is ready for operations (attempt $((attempt + 1)))"
|
||||
break
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
log_message "Waiting for PostgreSQL... (attempt $attempt/$max_attempts)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if docker exec immich_postgres pg_isready -U "$DB_USERNAME" >/dev/null 2>&1; then
|
||||
log_message "✓ PostgreSQL is ready (attempt $((attempt + 1)))"
|
||||
break
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
log_message "Error: PostgreSQL did not become ready within timeout (${max_attempts} attempts)"
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
log_message "Waiting for PostgreSQL... (attempt $attempt/$max_attempts)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
log_message "Error: PostgreSQL did not become ready within timeout (${max_attempts} attempts)"
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Drop existing database and recreate (if it exists)
|
||||
log_message "Preparing database for restoration..."
|
||||
@@ -408,12 +455,16 @@ restore_database() {
|
||||
log_message "✅ Database restoration completed successfully"
|
||||
else
|
||||
log_message "Error: Database restoration failed"
|
||||
docker stop immich_postgres 2>/dev/null || true
|
||||
rm -rf "$temp_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stop postgres container
|
||||
docker stop immich_postgres
|
||||
# Stop postgres container after successful restoration
|
||||
log_message "Stopping PostgreSQL container after restoration..."
|
||||
if ! docker stop immich_postgres; then
|
||||
log_message "Warning: Failed to stop PostgreSQL container"
|
||||
fi
|
||||
|
||||
# Cleanup temporary files
|
||||
rm -rf "$temp_dir"
|
||||
@@ -461,12 +512,38 @@ restore_uploads() {
|
||||
|
||||
# Create a secure temporary extraction directory
|
||||
local temp_extract_dir=$(mktemp -d)
|
||||
chmod 700 "$temp_extract_dir" # Secure permissions
|
||||
|
||||
# Extract with safety options to prevent path traversal
|
||||
if tar --no-absolute-names --strip-components=0 -xzf "$UPLOADS_BACKUP" -C "$temp_extract_dir"; then
|
||||
# Move extracted content to final location
|
||||
# Move extracted content to final location with comprehensive path validation
|
||||
if [ -d "$UPLOAD_LOCATION" ]; then
|
||||
log_message "Backup existing upload directory..."
|
||||
|
||||
# Comprehensive path validation to prevent traversal attacks
|
||||
local resolved_path=$(realpath "$UPLOAD_LOCATION" 2>/dev/null || echo "$UPLOAD_LOCATION")
|
||||
|
||||
# Check for path traversal patterns
|
||||
if [[ "$UPLOAD_LOCATION" =~ \.\. ]] || [[ "$resolved_path" =~ \.\. ]]; then
|
||||
log_message "Error: Path traversal detected in upload location: $UPLOAD_LOCATION"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate path contains only safe characters
|
||||
if [[ ! "$UPLOAD_LOCATION" =~ ^[a-zA-Z0-9/_-]+$ ]]; then
|
||||
log_message "Error: Invalid characters in upload location path: $UPLOAD_LOCATION"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure path is absolute and within expected boundaries
|
||||
if [[ ! "$UPLOAD_LOCATION" =~ ^/ ]]; then
|
||||
log_message "Error: Upload location must be an absolute path: $UPLOAD_LOCATION"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$UPLOAD_LOCATION" "${UPLOAD_LOCATION}.backup.$(date +%s)" || {
|
||||
log_message "Error: Failed to backup existing upload directory"
|
||||
rm -rf "$temp_extract_dir"
|
||||
@@ -474,22 +551,26 @@ restore_uploads() {
|
||||
}
|
||||
fi
|
||||
|
||||
# Move extracted content to final location
|
||||
mv "$temp_extract_dir"/* "$UPLOAD_LOCATION" 2>/dev/null || {
|
||||
# Safely move extracted content to final location
|
||||
# Use find with -exec to avoid shell expansion issues
|
||||
local move_success=false
|
||||
if find "$temp_extract_dir" -mindepth 1 -maxdepth 1 -exec mv {} "$UPLOAD_LOCATION" \; 2>/dev/null; then
|
||||
move_success=true
|
||||
else
|
||||
# Handle case where extraction created nested directories
|
||||
local extracted_dir=$(find "$temp_extract_dir" -mindepth 1 -maxdepth 1 -type d | head -1)
|
||||
if [ -n "$extracted_dir" ]; then
|
||||
mv "$extracted_dir" "$UPLOAD_LOCATION" || {
|
||||
log_message "Error: Failed to move extracted files to upload location"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
log_message "Error: No content found in uploads backup"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
if mv "$extracted_dir" "$UPLOAD_LOCATION" 2>/dev/null; then
|
||||
move_success=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
if [ "$move_success" = false ]; then
|
||||
log_message "Error: Failed to move extracted files to upload location"
|
||||
rm -rf "$temp_extract_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$temp_extract_dir"
|
||||
log_message "✅ Uploads restoration completed successfully"
|
||||
@@ -503,8 +584,16 @@ restore_uploads() {
|
||||
log_message "Setting proper ownership and permissions..."
|
||||
|
||||
# Find the user that should own these files (check docker container user)
|
||||
local container_user=$(docker exec immich_server id -u 2>/dev/null || echo "999")
|
||||
local container_group=$(docker exec immich_server id -g 2>/dev/null || echo "999")
|
||||
local container_user="999" # Default fallback
|
||||
local container_group="999" # Default fallback
|
||||
|
||||
# Try to get actual container user if immich_server is available
|
||||
if docker ps -q --filter "name=immich_server" | grep -q .; then
|
||||
container_user=$(docker exec immich_server id -u 2>/dev/null || echo "999")
|
||||
container_group=$(docker exec immich_server id -g 2>/dev/null || echo "999")
|
||||
else
|
||||
log_message "Note: Using default user/group (999:999) as immich_server container is not running"
|
||||
fi
|
||||
|
||||
# Set ownership (use chown with numeric IDs to avoid user name conflicts)
|
||||
if command -v chown >/dev/null; then
|
||||
@@ -617,11 +706,11 @@ RESTORE_END_TIME=$(date +%s)
|
||||
TOTAL_RESTORE_TIME=$((RESTORE_END_TIME - RESTORE_START_TIME))
|
||||
RESTORE_TIME_FORMATTED=""
|
||||
|
||||
if [ $TOTAL_RESTORE_TIME -gt 3600 ]; then
|
||||
if [ "$TOTAL_RESTORE_TIME" -gt 3600 ]; then
|
||||
HOURS=$((TOTAL_RESTORE_TIME / 3600))
|
||||
MINUTES=$(((TOTAL_RESTORE_TIME % 3600) / 60))
|
||||
RESTORE_TIME_FORMATTED="${HOURS}h ${MINUTES}m"
|
||||
elif [ $TOTAL_RESTORE_TIME -gt 60 ]; then
|
||||
elif [ "$TOTAL_RESTORE_TIME" -gt 60 ]; then
|
||||
MINUTES=$((TOTAL_RESTORE_TIME / 60))
|
||||
SECONDS=$((TOTAL_RESTORE_TIME % 60))
|
||||
RESTORE_TIME_FORMATTED="${MINUTES}m ${SECONDS}s"
|
||||
|
||||
Reference in New Issue
Block a user