Koha Security Best Practices
Comprehensive security guide for hardening and securing your Koha AWS deployment.
Complete guide for securing your Koha deployment on AWS, covering access control, encryption, compliance, and security monitoring.
Security Overview
Security Layers
┌─────────────────────────────────────────┐
│ Application Layer (Koha) │ ← Koha security settings
├─────────────────────────────────────────┤
│ Web Server Layer (Apache) │ ← SSL/TLS, headers
├─────────────────────────────────────────┤
│ Database Layer (MySQL/Aurora) │ ← Encryption, access control
├─────────────────────────────────────────┤
│ Instance Layer (EC2) │ ← Patching, hardening
├─────────────────────────────────────────┤
│ Network Layer (VPC, SG) │ ← Firewalls, isolation
├─────────────────────────────────────────┤
│ Infrastructure Layer (AWS) │ ← IAM, encryption
└─────────────────────────────────────────┘
Security Responsibility Model
AWS Responsibilities:
- Physical data center security
- Network infrastructure security
- Hypervisor security
- Managed service security (RDS, S3)
Your Responsibilities:
- Operating system patching
- Application security (Koha)
- Network configuration (security groups)
- Access management (IAM, SSH keys)
- Data encryption
- Backup security
Network Security
Security Groups Configuration
Basic/Standard Tier
Recommended security group rules:
# SSH access (restrict to your IP)
Type: SSH (22)
Protocol: TCP
Port: 22
Source: YOUR_IP_ADDRESS/32
Description: Admin SSH access
# HTTP (redirect to HTTPS)
Type: HTTP (80)
Protocol: TCP
Port: 80
Source: 0.0.0.0/0
Description: Public HTTP (redirects to HTTPS)
# HTTPS
Type: HTTPS (443)
Protocol: TCP
Port: 443
Source: 0.0.0.0/0
Description: Public HTTPS access
# Outbound (all traffic)
Type: All traffic
Protocol: All
Port: All
Destination: 0.0.0.0/0
Description: Allow all outbound
Update security group via CLI:
# Get security group ID
SG_ID=$(aws ec2 describe-instances \
--instance-ids i-xxxxx \
--query 'Reservations[0].Instances[0].SecurityGroups[0].GroupId' \
--output text)
# Remove overly permissive SSH rule (if exists)
aws ec2 revoke-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
# Add restricted SSH rule
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 22 \
--cidr YOUR_IP/32 \
--group-name "Admin SSH"
Enterprise Tier
Additional considerations:
- ALB security group: Only 80/443 from 0.0.0.0/0
- Instance security group: Only 80/443 from ALB security group
- No direct SSH access: Use Session Manager or EC2 Instance Connect
- Aurora security group: Only 3306 from instance security group
Verify Enterprise security:
# List all security groups
aws ec2 describe-security-groups \
--filters "Name=tag:aws:cloudformation:stack-name,Values=your-stack-name" \
--query 'SecurityGroups[*].[GroupName,GroupId]' \
--output table
# Check ALB security group rules
aws ec2 describe-security-groups \
--group-ids sg-xxxxx \
--query 'SecurityGroups[0].IpPermissions'
VPC Configuration
Default VPC considerations:
- Public subnet: Instances get public IPs
- Internet Gateway: Allows outbound internet access
- Route table: Routes traffic to internet
Enhanced VPC security (Enterprise):
# Create private subnet for database
aws ec2 create-subnet \
--vpc-id vpc-xxxxx \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1a
# Move Aurora to private subnet
# (During initial deployment via CloudFormation)
Network Access Control Lists (NACLs)
For additional security layer:
# Create NACL
aws ec2 create-network-acl --vpc-id vpc-xxxxx
# Allow HTTPS inbound
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxxxx \
--ingress \
--rule-number 100 \
--protocol tcp \
--port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 \
--rule-action allow
# Allow SSH from specific IP
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxxxx \
--ingress \
--rule-number 110 \
--protocol tcp \
--port-range From=22,To=22 \
--cidr-block YOUR_IP/32 \
--rule-action allow
Access Control
SSH Key Management
Best practices:
- Use separate keys for each environment:
# Generate environment-specific keys ssh-keygen -t ed25519 -f ~/.ssh/koha-production -C "koha-prod" ssh-keygen -t ed25519 -f ~/.ssh/koha-staging -C "koha-stage" - Rotate keys regularly (every 90 days):
# Generate new key ssh-keygen -t ed25519 -f ~/.ssh/koha-production-new # Add new key to instance ssh -i ~/.ssh/koha-production ubuntu@instance \ "echo 'NEW_PUBLIC_KEY' >> ~/.ssh/authorized_keys" # Test new key ssh -i ~/.ssh/koha-production-new ubuntu@instance # Remove old key ssh -i ~/.ssh/koha-production-new ubuntu@instance \ "sed -i '/OLD_KEY_FINGERPRINT/d' ~/.ssh/authorized_keys" - Protect private keys:
chmod 600 ~/.ssh/koha-production chmod 644 ~/.ssh/koha-production.pub # Encrypt private key openssl enc -aes-256-cbc -in ~/.ssh/koha-production \ -out ~/.ssh/koha-production.enc - Use SSH config file:
cat >> ~/.ssh/config << EOF Host koha-prod HostName X.X.X.X User ubuntu IdentityFile ~/.ssh/koha-production IdentitiesOnly yes EOF chmod 600 ~/.ssh/config # Connect simply ssh koha-prod
AWS IAM Security
Create dedicated IAM user for Koha management:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2-instance-connect:SendSSHPublicKey",
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackResources",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
Enable MFA for IAM users:
# Generate QR code for MFA setup
aws iam create-virtual-mfa-device \
--virtual-mfa-device-name koha-admin \
--outfile /tmp/QRCode.png \
--bootstrap-method QRCodePNG
# Enable MFA
aws iam enable-mfa-device \
--user-name koha-admin \
--serial-number arn:aws:iam::ACCOUNT:mfa/koha-admin \
--authentication-code1 123456 \
--authentication-code2 789012
Koha User Access Control
Staff interface security:
- Strong password policy:
# In Koha: Administration → System preferences → Admin # Set: minPasswordLength: 12 RequireStrongPassword: Require EnableExpiredPasswordReset: Allow - Review user permissions regularly:
-- List superlibrarians sudo koha-mysql library << EOF SELECT borrowernumber, userid, firstname, surname, email FROM borrowers WHERE flags = 1; EOF -- List users with specific permissions sudo koha-mysql library << EOF SELECT b.borrowernumber, b.userid, b.firstname, b.surname, up.code FROM borrowers b JOIN user_permissions up ON b.borrowernumber = up.borrower_number WHERE up.code IN ('superlibrarian', 'parameters', 'manage_sysprefs'); EOF - Disable unused accounts:
# Find inactive accounts sudo koha-mysql library << EOF SELECT borrowernumber, userid, firstname, surname, DATE(lastseen) as last_seen FROM borrowers WHERE categorycode = 'STAFF' AND lastseen < DATE_SUB(NOW(), INTERVAL 90 DAY) ORDER BY lastseen; EOF # Disable account in staff interface: # Patrons → Search for user → Edit → Set expired - Enable session timeout:
# In Koha: Administration → System preferences → Admin # Set: timeout: 600 # 10 minutes (in seconds)
Data Encryption
Encryption at Rest
EC2 EBS Volumes
Check if volumes are encrypted:
aws ec2 describe-volumes \
--filters "Name=attachment.instance-id,Values=i-xxxxx" \
--query 'Volumes[*].[VolumeId,Encrypted,Size]' \
--output table
Encrypt existing unencrypted volume:
# 1. Create snapshot
aws ec2 create-snapshot \
--volume-id vol-xxxxx \
--description "Snapshot before encryption"
# 2. Copy snapshot with encryption
aws ec2 copy-snapshot \
--source-region us-east-1 \
--source-snapshot-id snap-xxxxx \
--destination-region us-east-1 \
--encrypted \
--kms-key-id alias/aws/ebs
# 3. Create volume from encrypted snapshot
aws ec2 create-volume \
--snapshot-id snap-yyyyy \
--availability-zone us-east-1a \
--encrypted
# 4. Stop instance, detach old volume, attach new encrypted volume
# (Requires downtime - plan accordingly)
Aurora Database (Enterprise)
Encryption is enabled by default in templates.
Verify encryption:
aws rds describe-db-clusters \
--db-cluster-identifier your-cluster \
--query 'DBClusters[0].[DBClusterIdentifier,StorageEncrypted,KmsKeyId]'
S3 Bucket Encryption (Standard/Enterprise)
Enable default encryption:
# Get bucket name from CloudFormation outputs
BUCKET_NAME="your-backup-bucket"
# Enable encryption
aws s3api put-bucket-encryption \
--bucket $BUCKET_NAME \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
},
"BucketKeyEnabled": true
}]
}'
# Verify
aws s3api get-bucket-encryption --bucket $BUCKET_NAME
Encryption in Transit
SSL/TLS Configuration
For Standard tier with custom domain:
# Verify SSL certificate
echo | openssl s_client -connect library.yourdomain.com:443 2>/dev/null | \
openssl x509 -noout -dates
# Check SSL configuration
sudo apache2ctl -M | grep ssl
sudo apache2ctl -t -D DUMP_VHOSTS | grep 443
Enforce HTTPS:
# Add redirect in Apache config
sudo tee -a /etc/apache2/sites-available/000-default.conf > /dev/null << EOF
<VirtualHost *:80>
ServerName library.yourdomain.com
Redirect permanent / https://library.yourdomain.com/
</VirtualHost>
EOF
sudo systemctl reload apache2
Strengthen SSL/TLS:
# Edit Koha SSL config
sudo nano /etc/apache2/sites-available/koha-library-ssl.conf
# Add/modify:
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
SSLHonorCipherOrder on
SSLCompression off
# Enable HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Test configuration
sudo apache2ctl configtest
# Apply
sudo systemctl reload apache2
Database Connection Encryption
For Enterprise Aurora:
# Aurora connections are encrypted by default
# Verify in RDS console or via CLI
aws rds describe-db-clusters \
--db-cluster-identifier your-cluster \
--query 'DBClusters[0].EnabledCloudwatchLogsExports'
For Basic/Standard local MySQL:
# Enable SSL for MySQL connections
sudo mysql << EOF
CREATE USER 'koha_library'@'localhost' REQUIRE SSL;
GRANT ALL ON koha_library.* TO 'koha_library'@'localhost';
FLUSH PRIVILEGES;
EOF
Application Security
Apache Hardening
Security headers:
# Enable headers module
sudo a2enmod headers
# Edit Apache config
sudo nano /etc/apache2/conf-available/security.conf
# Add security headers:
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Remove server signature
ServerTokens Prod
ServerSignature Off
# Enable configuration
sudo a2enconf security
sudo systemctl reload apache2
Verify headers:
curl -I https://library.yourdomain.com/ | grep -E "X-Frame|X-Content|X-XSS|Server:"
Koha Security Settings
System preferences to review:
Administration → System preferences → Security
- AutoLocation: Turn off (use manual IP mapping if needed)
- AllowMultipleIssuesOnABiblio: Consider disabling
- AllowNotForLoanOverride: Consider restricting
- AllowRenewalLimitOverride: Consider restricting
- IndependentBranches: Enable if using multiple branches
- IndependentBranchesPatronModifications: Enable
- SessionRestrictionByIP: Enable
- TrackLastPatronActivity: Enable (for security auditing)
Enable audit logging:
Administration → System preferences → Logs
- BorrowersLog: Track
- CataloguingLog: Track
- IssueLog: Track
- LetterLog: Track
- FinesLog: Track
- AuthoritiesLog: Track
- ReportsLog: Track
- AuthSuccessLog: Track
- AuthFailureLog: Track
Database Security
Basic/Standard tier:
# Secure MySQL installation
sudo mysql_secure_installation
# Remove test databases
sudo mysql << EOF
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
EOF
# Restrict remote access (should be localhost only)
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
# Ensure: bind-address = 127.0.0.1
# Review MySQL users
sudo mysql -e "SELECT User, Host FROM mysql.user;"
# Remove unused users
sudo mysql -e "DROP USER 'username'@'host';"
Aurora (Enterprise):
# Verify security group only allows access from app instances
aws ec2 describe-security-groups \
--group-ids sg-xxxxx \
--query 'SecurityGroups[0].IpPermissions'
# Enable audit logging
aws rds modify-db-cluster \
--db-cluster-identifier your-cluster \
--cloudwatch-logs-export-configuration '{"EnableLogTypes":["audit","error","general","slowquery"]}'
Security Monitoring
CloudTrail Logging
Enable CloudTrail for API auditing:
# Create S3 bucket for logs
aws s3 mb s3://koha-cloudtrail-logs-$RANDOM
# Create trail
aws cloudtrail create-trail \
--name koha-audit-trail \
--s3-bucket-name koha-cloudtrail-logs-xxxxx
# Start logging
aws cloudtrail start-logging --name koha-audit-trail
# Verify
aws cloudtrail get-trail-status --name koha-audit-trail
VPC Flow Logs
Monitor network traffic:
# Create CloudWatch log group
aws logs create-log-group --log-group-name /aws/vpc/flowlogs
# Enable VPC flow logs
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-xxxxx \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /aws/vpc/flowlogs \
--deliver-logs-permission-arn arn:aws:iam::ACCOUNT:role/flowlogsRole
Security Scanning
Install and configure fail2ban:
# Install
sudo apt-get install -y fail2ban
# Configure for Koha
sudo tee /etc/fail2ban/jail.local > /dev/null << EOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = 22
logpath = /var/log/auth.log
[apache-auth]
enabled = true
port = http,https
logpath = /var/log/apache2/*error.log
maxretry = 3
[koha-opac]
enabled = true
port = http,https
logpath = /var/log/koha/library/opac-error.log
maxretry = 10
findtime = 300
EOF
# Start service
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check status
sudo fail2ban-client status
Vulnerability scanning:
# Install lynis
sudo apt-get install -y lynis
# Run security audit
sudo lynis audit system
# Review results
sudo cat /var/log/lynis.log
Intrusion Detection
Install OSSEC (optional):
# Download and install
wget https://github.com/ossec/ossec-hids/archive/3.7.0.tar.gz
tar -xvzf 3.7.0.tar.gz
cd ossec-hids-3.7.0
sudo ./install.sh
# Configure
sudo nano /var/ossec/etc/ossec.conf
# Start
sudo /var/ossec/bin/ossec-control start
# Check alerts
sudo tail -f /var/ossec/logs/alerts/alerts.log
Compliance & Data Protection
GDPR Compliance
Koha GDPR features:
- Anonymize patron data:
Administration → System preferences → Privacy - AnonymousPatron: Set to anonymous patron account - Privacy: Allow patrons to choose - PatronPrivacySettings: Enable - Configure data retention:
# Cronjob to anonymize old issues sudo crontab -e -u root # Add: 0 2 * * * /usr/share/koha/bin/cronjobs/batch_anonymise.pl --days 90 - Export patron data (GDPR requests):
sudo koha-mysql library << EOF SELECT * FROM borrowers WHERE borrowernumber = PATRON_ID; SELECT * FROM issues WHERE borrowernumber = PATRON_ID; SELECT * FROM old_issues WHERE borrowernumber = PATRON_ID; EOF
PCI DSS (If Accepting Payments)
If using payment processing:
- Never store credit card details in Koha
- Use PCI-compliant payment gateways (PayPal, Stripe)
- Enable SSL/TLS for all payment pages
- Restrict access to payment configuration
- Log all transactions
Data Backup Security
Encrypt backups:
# For Basic/Standard tier
# Encrypt mysqldump backup
sudo mysqldump koha_library | \
openssl enc -aes-256-cbc -pbkdf2 -out backup.sql.enc
# Decrypt when needed
openssl enc -d -aes-256-cbc -pbkdf2 -in backup.sql.enc -out backup.sql
S3 bucket security (Standard/Enterprise):
# Enable versioning
aws s3api put-bucket-versioning \
--bucket your-backup-bucket \
--versioning-configuration Status=Enabled
# Enable MFA delete (highly recommended)
aws s3api put-bucket-versioning \
--bucket your-backup-bucket \
--versioning-configuration Status=Enabled,MFADelete=Enabled \
--mfa "arn:aws:iam::ACCOUNT:mfa/root-account-mfa-device 123456"
# Block public access
aws s3api put-public-access-block \
--bucket your-backup-bucket \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
Incident Response
Security Incident Checklist
If you suspect a security breach:
- Isolate affected systems:
# Remove from internet (if safe to do so) # Update security groups to restrict access aws ec2 modify-instance-attribute \ --instance-id i-xxxxx \ --groups sg-xxxxxxxx # Emergency restricted SG - Collect evidence:
# Capture logs before they rotate sudo tar -czf /tmp/incident-logs-$(date +%Y%m%d).tar.gz \ /var/log/koha/ \ /var/log/apache2/ \ /var/log/mysql/ \ /var/log/auth.log \ /var/log/syslog # Upload to secure location aws s3 cp /tmp/incident-logs-*.tar.gz s3://security-incidents/ - Create forensic snapshot:
aws ec2 create-snapshot \ --volume-id vol-xxxxx \ --description "Forensic snapshot - incident $(date +%Y%m%d)" - Review access logs:
# Check for unauthorized SSH logins sudo grep "Accepted publickey" /var/log/auth.log # Check for sudo usage sudo grep "sudo:" /var/log/auth.log # Check Apache access logs for suspicious activity sudo grep -E "POST|DELETE" /var/log/apache2/access.log | \ grep -vE "cgi-bin/koha" - Contact support:
- Email: [email protected]
- Subject: “SECURITY INCIDENT - [Your Library]”
- Include: Incident details, timeline, affected systems
Post-Incident Actions
- Rotate all credentials:
- SSH keys
- Database passwords
- Koha admin passwords
- AWS access keys
- Review and update security:
- Patch all systems
- Update security group rules
- Enable additional logging
- Implement monitoring alerts
- Document lessons learned:
- What happened
- How it was detected
- How it was resolved
- How to prevent future incidents
Security Checklist
Daily
- Review CloudWatch alarms
- Check fail2ban logs
- Monitor unusual activity
Weekly
- Review access logs
- Check for security updates
- Verify backups are encrypted
Monthly
- Review IAM permissions
- Audit user accounts
- Update security documentation
- Test backup restoration
Quarterly
- Rotate SSH keys
- Run vulnerability scan
- Review security group rules
- Security awareness training
- Disaster recovery drill
Annually
- Comprehensive security audit
- Penetration testing (if required)
- Update incident response plan
- Review compliance requirements
Security Resources
AWS Security Resources
Koha Security Resources
Getting Help
For security assistance:
- General: [email protected]
- Security incidents: [email protected]
- Subject: “Security - [Your Library]”
Related Documentation
Last Updated: December 2025
Next Steps
More in AWS & Deployment
Was this article helpful?