mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-15 14:50:37 +01:00
Refactor docker copy and keep entrypoints
This commit is contained in:
parent
dabd4a055c
commit
d3d523b2b8
4 changed files with 230 additions and 110 deletions
|
@ -104,113 +104,175 @@ func findPattern(s, pattern string) int {
|
||||||
return bytes.Index([]byte(s), []byte(pattern))
|
return bytes.Index([]byte(s), []byte(pattern))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Volume string
|
func copyEntryPoints(sourceFile, destFile string) error {
|
||||||
type Port string
|
// Read source file
|
||||||
type Expose string
|
sourceData, err := os.ReadFile(sourceFile)
|
||||||
|
|
||||||
type HealthCheck struct {
|
|
||||||
Test []string `yaml:"test,omitempty"`
|
|
||||||
Interval string `yaml:"interval,omitempty"`
|
|
||||||
Timeout string `yaml:"timeout,omitempty"`
|
|
||||||
Retries int `yaml:"retries,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DependsOnCondition struct {
|
|
||||||
Condition string `yaml:"condition,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
Image string `yaml:"image,omitempty"`
|
|
||||||
ContainerName string `yaml:"container_name,omitempty"`
|
|
||||||
Environment map[string]string `yaml:"environment,omitempty"`
|
|
||||||
HealthCheck *HealthCheck `yaml:"healthcheck,omitempty"`
|
|
||||||
DependsOn map[string]DependsOnCondition `yaml:"depends_on,omitempty"`
|
|
||||||
Labels []string `yaml:"labels,omitempty"`
|
|
||||||
Volumes []Volume `yaml:"volumes,omitempty"`
|
|
||||||
Ports []Port `yaml:"ports,omitempty"`
|
|
||||||
Expose []Expose `yaml:"expose,omitempty"`
|
|
||||||
Restart string `yaml:"restart,omitempty"`
|
|
||||||
Command interface{} `yaml:"command,omitempty"`
|
|
||||||
NetworkMode string `yaml:"network_mode,omitempty"`
|
|
||||||
CapAdd []string `yaml:"cap_add,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Network struct {
|
|
||||||
Driver string `yaml:"driver,omitempty"`
|
|
||||||
Name string `yaml:"name,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerConfig struct {
|
|
||||||
Version string `yaml:"version,omitempty"`
|
|
||||||
Services map[string]Service `yaml:"services"`
|
|
||||||
Networks map[string]Network `yaml:"networks,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddCrowdSecService(configPath string) error {
|
|
||||||
// Read existing config
|
|
||||||
data, err := os.ReadFile(configPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error reading source file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse existing config
|
// Read destination file
|
||||||
var config DockerConfig
|
destData, err := os.ReadFile(destFile)
|
||||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create CrowdSec service
|
|
||||||
crowdsecService := Service{
|
|
||||||
Image: "crowdsecurity/crowdsec:latest",
|
|
||||||
ContainerName: "crowdsec",
|
|
||||||
Environment: map[string]string{
|
|
||||||
"GID": "1000",
|
|
||||||
"COLLECTIONS": "crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules",
|
|
||||||
"ENROLL_INSTANCE_NAME": "pangolin-crowdsec",
|
|
||||||
"PARSERS": "crowdsecurity/whitelists",
|
|
||||||
"ACQUIRE_FILES": "/var/log/traefik/*.log",
|
|
||||||
"ENROLL_TAGS": "docker",
|
|
||||||
},
|
|
||||||
HealthCheck: &HealthCheck{
|
|
||||||
Test: []string{"CMD", "cscli", "capi", "status"},
|
|
||||||
},
|
|
||||||
DependsOn: map[string]DependsOnCondition{
|
|
||||||
"gerbil": {},
|
|
||||||
},
|
|
||||||
Labels: []string{"traefik.enable=false"},
|
|
||||||
Volumes: []Volume{
|
|
||||||
"./config/crowdsec:/etc/crowdsec",
|
|
||||||
"./config/crowdsec/db:/var/lib/crowdsec/data",
|
|
||||||
"./config/crowdsec_logs/auth.log:/var/log/auth.log:ro",
|
|
||||||
"./config/crowdsec_logs/syslog:/var/log/syslog:ro",
|
|
||||||
"./config/crowdsec_logs:/var/log",
|
|
||||||
"./config/traefik/logs:/var/log/traefik",
|
|
||||||
},
|
|
||||||
Ports: []Port{
|
|
||||||
"9090:9090",
|
|
||||||
"6060:6060",
|
|
||||||
},
|
|
||||||
Expose: []Expose{
|
|
||||||
"9090",
|
|
||||||
"6060",
|
|
||||||
"7422",
|
|
||||||
},
|
|
||||||
Restart: "unless-stopped",
|
|
||||||
Command: "-t",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add CrowdSec service to config
|
|
||||||
if config.Services == nil {
|
|
||||||
config.Services = make(map[string]Service)
|
|
||||||
}
|
|
||||||
config.Services["crowdsec"] = crowdsecService
|
|
||||||
|
|
||||||
// Marshal config with better formatting
|
|
||||||
yamlData, err := yaml.Marshal(&config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error reading destination file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write config back to file
|
// Parse source YAML
|
||||||
return os.WriteFile(configPath, yamlData, 0644)
|
var sourceYAML map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(sourceData, &sourceYAML); err != nil {
|
||||||
|
return fmt.Errorf("error parsing source YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination YAML
|
||||||
|
var destYAML map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(destData, &destYAML); err != nil {
|
||||||
|
return fmt.Errorf("error parsing destination YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get entryPoints section from source
|
||||||
|
entryPoints, ok := sourceYAML["entryPoints"]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("entryPoints section not found in source file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update entryPoints in destination
|
||||||
|
destYAML["entryPoints"] = entryPoints
|
||||||
|
|
||||||
|
// Marshal updated destination YAML
|
||||||
|
updatedData, err := yaml.Marshal(destYAML)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling updated YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write updated YAML back to destination file
|
||||||
|
if err := os.WriteFile(destFile, updatedData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("error writing to destination file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyWebsecureEntryPoint(sourceFile, destFile string) error {
|
||||||
|
// Read source file
|
||||||
|
sourceData, err := os.ReadFile(sourceFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading source file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read destination file
|
||||||
|
destData, err := os.ReadFile(destFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading destination file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse source YAML
|
||||||
|
var sourceYAML map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(sourceData, &sourceYAML); err != nil {
|
||||||
|
return fmt.Errorf("error parsing source YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination YAML
|
||||||
|
var destYAML map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(destData, &destYAML); err != nil {
|
||||||
|
return fmt.Errorf("error parsing destination YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get entryPoints section from source
|
||||||
|
entryPoints, ok := sourceYAML["entryPoints"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("entryPoints section not found in source file or has invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get websecure configuration
|
||||||
|
websecure, ok := entryPoints["websecure"]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("websecure entrypoint not found in source file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create entryPoints section in destination
|
||||||
|
destEntryPoints, ok := destYAML["entryPoints"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
// If entryPoints section doesn't exist, create it
|
||||||
|
destEntryPoints = make(map[string]interface{})
|
||||||
|
destYAML["entryPoints"] = destEntryPoints
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update websecure in destination
|
||||||
|
destEntryPoints["websecure"] = websecure
|
||||||
|
|
||||||
|
// Marshal updated destination YAML
|
||||||
|
updatedData, err := yaml.Marshal(destYAML)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling updated YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write updated YAML back to destination file
|
||||||
|
if err := os.WriteFile(destFile, updatedData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("error writing to destination file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDockerService(sourceFile, destFile, serviceName string) error {
|
||||||
|
// Read source file
|
||||||
|
sourceData, err := os.ReadFile(sourceFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading source file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read destination file
|
||||||
|
destData, err := os.ReadFile(destFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading destination file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse source Docker Compose YAML
|
||||||
|
var sourceCompose map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(sourceData, &sourceCompose); err != nil {
|
||||||
|
return fmt.Errorf("error parsing source Docker Compose file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination Docker Compose YAML
|
||||||
|
var destCompose map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(destData, &destCompose); err != nil {
|
||||||
|
return fmt.Errorf("error parsing destination Docker Compose file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get services section from source
|
||||||
|
sourceServices, ok := sourceCompose["services"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("services section not found in source file or has invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the specific service configuration
|
||||||
|
serviceConfig, ok := sourceServices[serviceName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("service '%s' not found in source file", serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create services section in destination
|
||||||
|
destServices, ok := destCompose["services"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
// If services section doesn't exist, create it
|
||||||
|
destServices = make(map[string]interface{})
|
||||||
|
destCompose["services"] = destServices
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update service in destination
|
||||||
|
destServices[serviceName] = serviceConfig
|
||||||
|
|
||||||
|
// Marshal updated destination YAML
|
||||||
|
// Use yaml.v3 encoder to preserve formatting and comments
|
||||||
|
updatedData, err := yaml.Marshal(destCompose)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling updated Docker Compose file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write updated YAML back to destination file
|
||||||
|
if err := os.WriteFile(destFile, updatedData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("error writing to destination file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
35
install/config/crowdsec/docker-compose.yml
Normal file
35
install/config/crowdsec/docker-compose.yml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
services:
|
||||||
|
crowdsec:
|
||||||
|
image: crowdsecurity/crowdsec:latest
|
||||||
|
container_name: crowdsec
|
||||||
|
environment:
|
||||||
|
GID: "1000"
|
||||||
|
COLLECTIONS: crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules
|
||||||
|
ENROLL_INSTANCE_NAME: "pangolin-crowdsec"
|
||||||
|
PARSERS: crowdsecurity/whitelists
|
||||||
|
ACQUIRE_FILES: "/var/log/traefik/*.log"
|
||||||
|
ENROLL_TAGS: docker
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "cscli", "capi", "status"]
|
||||||
|
depends_on:
|
||||||
|
- gerbil # Wait for gerbil to be healthy
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=false" # Disable traefik for crowdsec
|
||||||
|
volumes:
|
||||||
|
# crowdsec container data
|
||||||
|
- ./config/crowdsec:/etc/crowdsec # crowdsec config
|
||||||
|
- ./config/crowdsec/db:/var/lib/crowdsec/data # crowdsec db
|
||||||
|
# log bind mounts into crowdsec
|
||||||
|
- ./config/crowdsec_logs/auth.log:/var/log/auth.log:ro # auth.log
|
||||||
|
- ./config/crowdsec_logs/syslog:/var/log/syslog:ro # syslog
|
||||||
|
- ./config/crowdsec_logs:/var/log # crowdsec logs
|
||||||
|
- ./config/traefik/logs:/var/log/traefik # traefik logs
|
||||||
|
ports:
|
||||||
|
- 9090:9090 # port mapping for local firewall bouncers
|
||||||
|
- 6060:6060 # metrics endpoint for prometheus
|
||||||
|
expose:
|
||||||
|
- 9090 # http api for bouncers
|
||||||
|
- 6060 # metrics endpoint for prometheus
|
||||||
|
- 7422 # appsec waf endpoint
|
||||||
|
restart: unless-stopped
|
||||||
|
command: -t # Add test config flag to verify configuration
|
|
@ -80,7 +80,7 @@ entryPoints:
|
||||||
http:
|
http:
|
||||||
tls:
|
tls:
|
||||||
certResolver: "letsencrypt"
|
certResolver: "letsencrypt"
|
||||||
middlewares: # CHANGE MADE HERE (BOUNCER ENABLED) !!!
|
middlewares:
|
||||||
- crowdsec@file
|
- crowdsec@file
|
||||||
|
|
||||||
serversTransport:
|
serversTransport:
|
||||||
|
|
|
@ -15,17 +15,40 @@ func installCrowdsec(config Config) error {
|
||||||
return fmt.Errorf("backup failed: %v", err)
|
return fmt.Errorf("backup failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := AddCrowdSecService("docker-compose.yml"); err != nil {
|
|
||||||
return fmt.Errorf("crowdsec service addition failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := createConfigFiles(config); err != nil {
|
if err := createConfigFiles(config); err != nil {
|
||||||
fmt.Printf("Error creating config files: %v\n", err)
|
fmt.Printf("Error creating config files: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// moveFile("config/crowdsec/traefik_config.yml", "config/traefik/traefik_config.yml")
|
if err := copyDockerService("config/crowdsec/docker-compose.yml", "docker-compose.yml", "crowdsec"); err != nil {
|
||||||
moveFile("config/crowdsec/dynamic.yml", "config/traefik/dynamic.yml")
|
fmt.Printf("Error copying docker service: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyWebsecureEntryPoint("config/crowdsec/traefik_config.yml", "config/traefik/traefik_config.yml"); err != nil {
|
||||||
|
fmt.Printf("Error copying entry points: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyEntryPoints("config/traefik/traefik_config.yml", "config/crowdsec/traefik_config.yml"); err != nil {
|
||||||
|
fmt.Printf("Error copying entry points: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := moveFile("config/crowdsec/traefik_config.yml", "config/traefik/traefik_config.yml"); err != nil {
|
||||||
|
fmt.Printf("Error moving file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := moveFile("config/crowdsec/dynamic_config.yml", "config/traefik/dynamic_config.yml"); err != nil {
|
||||||
|
fmt.Printf("Error moving file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove("config/crowdsec/docker-compose.yml"); err != nil {
|
||||||
|
fmt.Printf("Error removing file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
if err := retrieveBouncerKey(config); err != nil {
|
if err := retrieveBouncerKey(config); err != nil {
|
||||||
return fmt.Errorf("bouncer key retrieval failed: %v", err)
|
return fmt.Errorf("bouncer key retrieval failed: %v", err)
|
||||||
|
|
Loading…
Reference in a new issue