Skip to main content

Security Best Practices

This guide presents security best practices for PCH-SIG administration.

Authentication

Passwords

  • Minimum length: 8 characters
  • Complexity: Uppercase, lowercase, numbers, symbols
  • Renewal: Every 90 days
  • History: Do not reuse last 5 passwords

Account management

ActionRecommendation
CreationOne account per person
SharingNever share credentials
InactivityDisable after 90 days without login
DepartureDisable immediately

Multi-factor authentication

Although not currently implemented, consider:

  • TOTP application (Google Authenticator, Authy)
  • SMS (less secure)
  • Hardware security keys (YubiKey)

Authorizations

Principle of least privilege

  • Grant only necessary permissions
  • Start with restrictive access
  • Add permissions if justified

Separation of duties

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│ Creation │────▶│ Validation │────▶│ Approval │
│ (Operator) │ │ (Manager) │ │ (Admin) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
  • User who creates does not validate
  • User who validates does not approve
  • Critical actions require multiple people

Access review

  • Frequency: Quarterly
  • Checks:
    • Inactive accounts
    • Excessive permissions
    • Obsolete roles

Data protection

Classification

LevelDescriptionExamples
PublicAccessible to allDocumentation
InternalAuthenticated usersAggregated statistics
ConfidentialRestricted accessBeneficiary data
SecretHighly restrictedFinancial information

Encryption

  • In transit: HTTPS required
  • At rest: Disk encryption for sensitive data
  • Backups: Archive encryption

Anonymization

For exports and reports, anonymize sensitive data:

// Anonymization example
$beneficiaire = [
'id' => hash('sha256', $original['id']),
'region' => $original['region'],
'montant' => $original['montant'],
// NO name, phone, address
];

Infrastructure

Network

  • Firewall: Limit exposed ports
  • Segmentation: Separate environments
  • VPN: For administrative access

Ports to expose

PortServiceAccess
443HTTPS FrontendPublic
22SSHVPN only
5432PostgreSQLLocal only
6379RedisLocal only

Docker configuration

# Do not expose sensitive ports
services:
postgres:
# NO ports: exposed
networks:
- internal

backend:
networks:
- internal
- frontend

networks:
internal:
internal: true
frontend:

Audit and Logging

Events to log

CategoryEvents
AuthenticationSuccessful/failed logins
AuthorizationAccess denied
DataCreations, modifications, deletions
AdminConfiguration changes

Log retention

TypeDuration
Security1 year minimum
Application90 days
Debug7 days

Do not log

  • Passwords (even hashed)
  • Authentication tokens
  • Complete identity document numbers
  • Banking data

Backups

3-2-1 Strategy

  • 3 copies of data
  • 2 different media
  • 1 off-site copy

Backup security

  • Encrypt backups
  • Store keys separately
  • Test restores regularly

Secure script

#!/bin/bash
# backup-secure.sh

# Variables
BACKUP_DIR="/backups"
ENCRYPTION_KEY="/root/.backup-key"
DATE=$(date +%Y%m%d)

# Backup
pg_dump -U pch_admin pch_sig > /tmp/backup.sql

# Encryption
gpg --symmetric --cipher-algo AES256 \
--passphrase-file $ENCRYPTION_KEY \
--output $BACKUP_DIR/backup_$DATE.sql.gpg \
/tmp/backup.sql

# Cleanup
rm -f /tmp/backup.sql

Secure development

Input validation

// Always validate
$email = filter_var($input['email'], FILTER_VALIDATE_EMAIL);
$id = filter_var($input['id'], FILTER_VALIDATE_INT);

// Use Symfony constraints
use Symfony\Component\Validator\Constraints as Assert;

class User
{
#[Assert\Email]
#[Assert\NotBlank]
private string $email;

#[Assert\Length(min: 8)]
private string $password;
}

Protection against injections

// SQL - Use prepared statements
$query = $em->createQuery('SELECT m FROM Menage m WHERE m.region = :region');
$query->setParameter('region', $region);

// NEVER concatenation
// $query = "SELECT * FROM menages WHERE region = '$region'"; // DANGEROUS

XSS protection

// Escape outputs
{{ variable|e }} {# Twig escapes by default #}

// In React, use native mechanisms
<div>{variable}</div> // Escaped automatically

// NEVER
<div dangerouslySetInnerHTML={{ __html: variable }} /> // Avoid

Secrets management

Never commit

# .gitignore
.env
.env.local
config/jwt/private.pem
config/jwt/public.pem
*.key
*.pem

Environment variables

# Store secrets in environment, not in code
export DATABASE_URL="postgresql://user:password@host:5432/db"
export JWT_PASSPHRASE="secret_passphrase"

Secrets rotation

SecretFrequency
DB passwordsAnnual
JWT keysSemi-annual
External API keysPer policy

Incident response

Response plan

  1. Detection: Monitoring alerts
  2. Analysis: Assess impact
  3. Containment: Limit damage
  4. Eradication: Remove threat
  5. Recovery: Restore services
  6. Post-mortem: Analyze and improve

Emergency contacts

Maintain an up-to-date contact list:

  • System administrator
  • Security officer
  • Development team
  • Hosting support

Compromise procedure

# 1. Block access
docker stop pch_backend pch_frontend

# 2. Change passwords
docker exec pch_postgres psql -U pch_admin -c "
ALTER USER pch_admin WITH PASSWORD 'new_password';"

# 3. Regenerate tokens
docker exec pch_backend php bin/console lexik:jwt:generate-keypair --overwrite

# 4. Analyze logs
docker logs pch_backend --since "2024-01-15" > incident.log

Security checklist

Daily

  • Check monitoring alerts
  • Review failed login attempts

Weekly

  • Check for security updates
  • Analyze access logs

Monthly

  • Review user access
  • Test backups
  • Check SSL certificates

Quarterly

  • Permissions audit
  • Full restore test
  • Review security procedures

Annual

  • Rotate system passwords
  • Complete security audit
  • User training

Resources

References

Tools

  • Vulnerability analysis: Trivy, Snyk
  • Penetration testing: OWASP ZAP
  • Code audit: SonarQube

Next steps