An Auditor’s Checklist for Securing Ruby Backends on Linode
SSH Hardening and Access Control
Securing SSH access is the first line of defense for any Linode instance hosting a Ruby backend. This involves disabling root login, enforcing key-based authentication, and potentially restricting access to specific IP addresses.
Begin by editing the SSH daemon configuration file, typically located at /etc/ssh/sshd_config. Ensure the following directives are set:
Disabling Root Login and Password Authentication
To prevent direct root logins and fallback to password authentication, modify or add these lines:
PermitRootLogin no PasswordAuthentication no ChallengeResponseAuthentication no
After making these changes, restart the SSH service to apply them. On most Debian/Ubuntu systems, this is:
sudo systemctl restart sshd
On CentOS/RHEL systems:
sudo systemctl restart sshd
Enforcing Key-Based Authentication
Ensure that public key authentication is enabled. This is usually the default, but it’s good practice to verify:
PubkeyAuthentication yes
Users should have their public SSH keys placed in ~/.ssh/authorized_keys within their respective home directories. The permissions for the .ssh directory and the authorized_keys file are critical:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys
Restricting SSH Access by IP Address (Optional but Recommended)
For enhanced security, you can restrict SSH access to specific IP addresses or ranges using iptables or ufw. If using ufw:
sudo ufw allow from YOUR_TRUSTED_IP to any port 22 proto tcp sudo ufw deny 22/tcp # Deny all other SSH access
Replace YOUR_TRUSTED_IP with the actual IP address or CIDR block you want to allow. Remember to enable ufw if it’s not already active:
sudo ufw enable
Web Server Configuration (Nginx Example)
Assuming Nginx is used as a reverse proxy for the Ruby application (e.g., Puma, Unicorn), several security configurations are essential. This includes SSL/TLS hardening, disabling unnecessary HTTP methods, and setting appropriate headers.
SSL/TLS Configuration
Use strong TLS protocols and cipher suites. A good starting point for your Nginx server block:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your_domain.com;
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s; # Google DNS, adjust as needed
resolver_timeout 5s;
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Other security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';"; # Example CSP, tailor to your app
location / {
proxy_pass http://unix:/path/to/your/app.sock; # Or http://localhost:PORT
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;
}
# ... other configurations
}
To test your SSL configuration, use tools like SSL Labs’ SSL Test (https://www.ssllabs.com/ssltest/).
Disabling Unnecessary HTTP Methods
Limit the HTTP methods allowed to prevent potential abuse. For most Ruby applications, only GET, POST, PUT, PATCH, and DELETE are typically required. You can enforce this at the Nginx level:
location / {
# ... proxy_pass directives ...
if ($request_method !~ ^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$) {
return 405;
}
}
This configuration will return a 405 Method Not Allowed error for any requests using disallowed HTTP verbs.
Ruby Application Security Best Practices
Beyond infrastructure, the Ruby application itself must be secured. This involves dependency management, input validation, and secure handling of sensitive data.
Dependency Management and Vulnerability Scanning
Regularly audit your application’s dependencies for known vulnerabilities. Tools like Bundler-Audit are invaluable.
# Ensure you have the latest vulnerability database bundle exec bundle-audit fetch # Scan your Gemfile.lock for known vulnerabilities bundle exec bundle-audit check
Integrate this into your CI/CD pipeline to catch vulnerabilities early. Consider using services like Snyk or Dependabot for automated dependency updates and vulnerability alerts.
Input Validation and Sanitization
Never trust user input. Implement robust validation and sanitization at the application level. For example, in a Rails application:
# Example using strong_parameters in Rails controllers
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
For non-Rails applications, ensure you are explicitly whitelisting or blacklisting input fields and sanitizing any data that will be used in database queries, file operations, or rendered in HTML.
Secure Handling of Secrets
Avoid hardcoding API keys, database credentials, or other secrets directly in your code or configuration files. Use environment variables or dedicated secret management solutions.
# Example using ENV variables in Ruby db_password = ENV['DATABASE_PASSWORD'] api_key = ENV['EXTERNAL_API_KEY'] # In a Rails app, consider using Rails credentials or a service like HashiCorp Vault # For environment variables, ensure they are set securely on the Linode instance # and not exposed in logs or version control. # Example: In a systemd service file for your app: # Environment="DATABASE_PASSWORD=your_secret_password"
For more complex deployments, integrate with tools like HashiCorp Vault, AWS Secrets Manager, or Linode’s own secret management capabilities if available.
Database Security (PostgreSQL Example)
The database is a critical component. Secure it by limiting network access, using strong authentication, and applying the principle of least privilege.
Network Access Control
Configure PostgreSQL’s pg_hba.conf to only allow connections from trusted sources. Typically, this means from your application server’s IP address or localhost.
# TYPE DATABASE USER ADDRESS METHOD # Allow connections from localhost for local development/administration local all all trust host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256 # Allow connections from your application server's IP host your_app_db your_app_user APP_SERVER_IP/32 scram-sha-256 # Deny all other connections host all all 0.0.0.0/0 reject host all all ::/0 reject
Replace your_app_db, your_app_user, and APP_SERVER_IP with your specific values. After modifying pg_hba.conf, reload the PostgreSQL configuration:
sudo systemctl reload postgresql
User Privileges
Ensure your application’s database user has only the necessary privileges. Avoid using the superuser account for application connections.
-- Connect as a PostgreSQL superuser -- Create a dedicated user for your application CREATE USER your_app_user WITH PASSWORD 'your_strong_password'; -- Create a dedicated database for your application CREATE DATABASE your_app_db OWNER your_app_user; -- Grant specific privileges to the application user on its database GRANT CONNECT ON DATABASE your_app_db TO your_app_user; -- Connect to your application database \c your_app_db -- Grant necessary privileges (e.g., SELECT, INSERT, UPDATE, DELETE on tables) GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO your_app_user; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO your_app_user; -- If using PostgreSQL 9.5+ and need to grant privileges on future tables/sequences ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO your_app_user; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO your_app_user; -- Revoke unnecessary privileges (e.g., superuser, administrative roles) -- REVOKE SUPERUSER FROM your_app_user; -- (if it was accidentally granted)
Regularly review these privileges, especially after major application updates or schema changes.
Logging and Monitoring
Comprehensive logging and proactive monitoring are crucial for detecting and responding to security incidents. Ensure logs are collected, stored securely, and analyzed.
Centralized Logging
Configure your application, web server, and system logs to be sent to a centralized logging system. Tools like rsyslog, Fluentd, or Logstash can be used. For Linode, consider their managed logging solutions or self-hosting ELK/Graylog.
# Example: Configuring rsyslog to forward logs to a remote server # On the client (your Linode instance) # Edit /etc/rsyslog.conf or create a new file in /etc/rsyslog.d/ # Add the following line to forward all messages to a remote syslog server: # *.* @remote_syslog_server_ip:514 # On the remote syslog server, ensure it's configured to receive logs. # For example, in /etc/rsyslog.conf on the server: # module(load="imudp") # input(type="imudp" port="514") # module(load="imtcp") # input(type="imtcp" port="514") # ... then define rules for where to store incoming logs ...
Security Auditing and Alerting
Implement security auditing tools and set up alerts for suspicious activities. This could include:
- Failed login attempts (SSH, application).
- Unusual traffic patterns or spikes.
- Errors indicating potential injection attempts (SQLi, XSS).
- Changes to critical system files.
- High resource utilization that could indicate a compromise.
Tools like fail2ban can automatically block IPs with repeated failed login attempts:
# Install fail2ban sudo apt update && sudo apt install fail2ban -y # Debian/Ubuntu sudo yum install epel-release && sudo yum install fail2ban -y # CentOS/RHEL # Configure jail.local for SSH sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Edit jail.local and ensure the [sshd] section is enabled and configured # Example: # [sshd] # enabled = true # port = ssh # filter = sshd # logpath = /var/log/auth.log # Or /var/log/secure on CentOS/RHEL # maxretry = 3 # bantime = 1h # findtime = 10m # Restart fail2ban to apply changes sudo systemctl restart fail2ban
For application-level security events, integrate alerts into your monitoring system (e.g., Prometheus with Alertmanager, Datadog, New Relic).