chore(infra): add ClamAV to docker-compose for v0.101

This commit is contained in:
senke 2026-02-18 12:03:14 +01:00
parent 68fececd8d
commit 1f72854192
6 changed files with 80 additions and 7 deletions

View file

@ -68,6 +68,24 @@ services:
cpus: '0.50'
memory: 256M
clamav:
image: clamav/clamav:latest
container_name: veza_clamav
restart: unless-stopped
networks:
- veza-network
healthcheck:
test: ["CMD", "clamdscan", "--ping", "1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 180s
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
# ============================================================================
# PAYMENT ROUTER (Hyperswitch)
# ============================================================================
@ -144,6 +162,9 @@ services:
- HYPERSWITCH_WEBHOOK_SECRET=${HYPERSWITCH_WEBHOOK_SECRET:-}
- HYPERSWITCH_ENABLED=${HYPERSWITCH_ENABLED:-false}
- CHECKOUT_SUCCESS_URL=${CHECKOUT_SUCCESS_URL:-https://veza.fr/purchases}
- ENABLE_CLAMAV=true
- CLAMAV_REQUIRED=true
- CLAMAV_ADDRESS=clamav:3310
depends_on:
postgres:
condition: service_healthy
@ -151,6 +172,8 @@ services:
condition: service_healthy
rabbitmq:
condition: service_healthy
clamav:
condition: service_started
networks:
- veza-network
healthcheck:

View file

@ -55,6 +55,27 @@ services:
reservations:
memory: 32M
# ClamAV - Virus scanning for uploads (v0.101)
clamav:
image: clamav/clamav:latest
container_name: veza_clamav
restart: unless-stopped
ports:
- "${PORT_CLAMAV:-13310}:3310"
networks:
- veza-net
healthcheck:
test: ["CMD", "clamdscan", "--ping", "1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 180s
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
# RabbitMQ - Message Broker
# Limit: 256MB RAM. Host 15672->AMQP(5672), 25672->Management(15672)
rabbitmq:
@ -153,6 +174,9 @@ services:
- COOKIE_PATH=/
- CORS_ALLOWED_ORIGINS=http://veza.fr:3000,http://veza.fr:5173
- RABBITMQ_URL=amqp://${RABBITMQ_DEFAULT_USER:-veza}:${RABBITMQ_DEFAULT_PASS:-devpassword}@rabbitmq:5672/
- ENABLE_CLAMAV=true
- CLAMAV_REQUIRED=false
- CLAMAV_ADDRESS=clamav:3310
ports:
- "${PORT_BACKEND:-18080}:8080"
depends_on:
@ -162,6 +186,8 @@ services:
condition: service_healthy
rabbitmq:
condition: service_healthy
clamav:
condition: service_started
networks:
- veza-net
healthcheck:

View file

@ -27,8 +27,8 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
# Runtime stage
FROM alpine:latest
# Install runtime dependencies
RUN apk --no-cache add ca-certificates tzdata wget
# Install runtime dependencies (clamav for virus scanning in v0.101)
RUN apk --no-cache add ca-certificates tzdata wget clamav
# Create non-root user for security
RUN addgroup -g 1001 -S app && \

View file

@ -32,10 +32,8 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
# Runtime stage - minimal alpine
FROM alpine:3.21
# Install only runtime dependencies
RUN apk --no-cache add ca-certificates tzdata && \
# Add wget for health checks
apk --no-cache add wget && \
# Install only runtime dependencies (clamav for virus scanning in v0.101)
RUN apk --no-cache add ca-certificates tzdata wget clamav && \
# Clean up apk cache
rm -rf /var/cache/apk/*

View file

@ -35,6 +35,10 @@ func (c *Config) initServices() error {
if p := getEnv("CLAMAV_CLAMD_PATH", ""); p != "" {
uploadConfig.ClamAVClamdPath = p
}
// Adresse ClamAV pour connexion TCP (ex: clamav:3310 en Docker)
if addr := getEnv("CLAMAV_ADDRESS", ""); addr != "" {
uploadConfig.ClamAVAddress = addr
}
var err error
c.UploadValidator, err = services.NewUploadValidator(uploadConfig, c.Logger)
if err != nil {

View file

@ -21,6 +21,7 @@ import (
type UploadValidator struct {
logger *zap.Logger
clamdPath string // Chemin vers clamdscan (ex: clamdscan, /usr/bin/clamdscan)
clamAVConfigPath string // Config pour connexion TCP distante (ex: clamav:3310)
quarantineDir string
clamAVRequiredButUnavailable bool // MOD-P1-001-REFINEMENT: Flag pour fail-secure localisé
clamAVRequired bool // MOD-P1-002: Si false, accepte uploads même si ClamAV down
@ -115,6 +116,21 @@ func NewUploadValidator(config *UploadConfig, logger *zap.Logger) (*UploadValida
}
fmt.Printf("🛡️ [UPLOAD VALIDATOR] ClamAV activé - Utilisation de %s (exec)\n", clamdPath)
// Config pour connexion TCP distante (ex: clamav:3310 en Docker)
clamAVConfigPath := ""
if config.ClamAVAddress != "" && strings.Contains(config.ClamAVAddress, ":") {
parts := strings.SplitN(config.ClamAVAddress, ":", 2)
host, port := parts[0], parts[1]
configContent := fmt.Sprintf("TCPSocket %s\nTCPAddr %s\n", port, host)
tmpFile, err := os.CreateTemp("", "veza-clamd-*.conf")
if err == nil {
_, _ = tmpFile.WriteString(configContent)
_ = tmpFile.Close()
clamAVConfigPath = tmpFile.Name()
fmt.Printf("🛡️ [UPLOAD VALIDATOR] Config TCP: %s -> %s\n", config.ClamAVAddress, clamAVConfigPath)
}
}
clamAVRequiredButUnavailable := false
// Test disponibilité: clamdscan --version
pingCtx, pingCancel := context.WithTimeout(context.Background(), 2*time.Second)
@ -141,6 +157,7 @@ func NewUploadValidator(config *UploadConfig, logger *zap.Logger) (*UploadValida
return &UploadValidator{
logger: logger,
clamdPath: clamdPath,
clamAVConfigPath: clamAVConfigPath,
quarantineDir: config.QuarantineDir,
clamAVRequiredButUnavailable: clamAVRequiredButUnavailable,
clamAVRequired: config.ClamAVRequired,
@ -495,7 +512,12 @@ func (uv *UploadValidator) scanWithClamAV(ctx context.Context, file io.Reader) (
return false, fmt.Errorf("failed to close temp file: %w", closeErr)
}
cmd := exec.CommandContext(scanCtx, uv.clamdPath, "--no-summary", tmpPath)
args := []string{"--no-summary"}
if uv.clamAVConfigPath != "" {
args = append(args, "-c", uv.clamAVConfigPath)
}
args = append(args, tmpPath)
cmd := exec.CommandContext(scanCtx, uv.clamdPath, args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
runErr := cmd.Run()