mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-15 14:50:37 +01:00
Reorg; create crowdsec folder properly now
This commit is contained in:
parent
81c4199e87
commit
60449afca5
15 changed files with 349 additions and 285 deletions
216
install/config.go
Normal file
216
install/config.go
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraefikConfig represents the structure of the main Traefik configuration
|
||||||
|
type TraefikConfig struct {
|
||||||
|
Experimental struct {
|
||||||
|
Plugins struct {
|
||||||
|
Badger struct {
|
||||||
|
Version string `yaml:"version"`
|
||||||
|
} `yaml:"badger"`
|
||||||
|
} `yaml:"plugins"`
|
||||||
|
} `yaml:"experimental"`
|
||||||
|
CertificatesResolvers struct {
|
||||||
|
LetsEncrypt struct {
|
||||||
|
Acme struct {
|
||||||
|
Email string `yaml:"email"`
|
||||||
|
} `yaml:"acme"`
|
||||||
|
} `yaml:"letsencrypt"`
|
||||||
|
} `yaml:"certificatesResolvers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicConfig represents the structure of the dynamic configuration
|
||||||
|
type DynamicConfig struct {
|
||||||
|
HTTP struct {
|
||||||
|
Routers map[string]struct {
|
||||||
|
Rule string `yaml:"rule"`
|
||||||
|
} `yaml:"routers"`
|
||||||
|
} `yaml:"http"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigValues holds the extracted configuration values
|
||||||
|
type ConfigValues struct {
|
||||||
|
DashboardDomain string
|
||||||
|
LetsEncryptEmail string
|
||||||
|
BadgerVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadTraefikConfig reads and extracts values from Traefik configuration files
|
||||||
|
func ReadTraefikConfig(mainConfigPath, dynamicConfigPath string) (*ConfigValues, error) {
|
||||||
|
// Read main config file
|
||||||
|
mainConfigData, err := os.ReadFile(mainConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading main config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainConfig TraefikConfig
|
||||||
|
if err := yaml.Unmarshal(mainConfigData, &mainConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing main config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read dynamic config file
|
||||||
|
dynamicConfigData, err := os.ReadFile(dynamicConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading dynamic config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dynamicConfig DynamicConfig
|
||||||
|
if err := yaml.Unmarshal(dynamicConfigData, &dynamicConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing dynamic config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract values
|
||||||
|
values := &ConfigValues{
|
||||||
|
BadgerVersion: mainConfig.Experimental.Plugins.Badger.Version,
|
||||||
|
LetsEncryptEmail: mainConfig.CertificatesResolvers.LetsEncrypt.Acme.Email,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract DashboardDomain from router rules
|
||||||
|
// Look for it in the main router rules
|
||||||
|
for _, router := range dynamicConfig.HTTP.Routers {
|
||||||
|
if router.Rule != "" {
|
||||||
|
// Extract domain from Host(`mydomain.com`)
|
||||||
|
if domain := extractDomainFromRule(router.Rule); domain != "" {
|
||||||
|
values.DashboardDomain = domain
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractDomainFromRule extracts the domain from a router rule
|
||||||
|
func extractDomainFromRule(rule string) string {
|
||||||
|
// Look for the Host(`mydomain.com`) pattern
|
||||||
|
if start := findPattern(rule, "Host(`"); start != -1 {
|
||||||
|
end := findPattern(rule[start:], "`)")
|
||||||
|
if end != -1 {
|
||||||
|
return rule[start+6 : start+end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// findPattern finds the start of a pattern in a string
|
||||||
|
func findPattern(s, pattern string) int {
|
||||||
|
return bytes.Index([]byte(s), []byte(pattern))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Volume string
|
||||||
|
type Port string
|
||||||
|
type Expose string
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse existing config
|
||||||
|
var config DockerConfig
|
||||||
|
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": "${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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write config back to file
|
||||||
|
return os.WriteFile(configPath, yamlData, 0644)
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ http:
|
||||||
routers:
|
routers:
|
||||||
# HTTP to HTTPS redirect router
|
# HTTP to HTTPS redirect router
|
||||||
main-app-router-redirect:
|
main-app-router-redirect:
|
||||||
rule: "Host(`{{.DomainName}}`)" # Dynamic Domain Name
|
rule: "Host(`{{.DashboardDomain}}`)" # Dynamic Domain Name
|
||||||
service: next-service
|
service: next-service
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- web
|
||||||
|
@ -65,7 +65,7 @@ http:
|
||||||
|
|
||||||
# Next.js router (handles everything except API and WebSocket paths)
|
# Next.js router (handles everything except API and WebSocket paths)
|
||||||
next-router:
|
next-router:
|
||||||
rule: "Host(`{{.DomainName}}`) && !PathPrefix(`/api/v1`)" # Dynamic Domain Name
|
rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)" # Dynamic Domain Name
|
||||||
service: next-service
|
service: next-service
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
|
@ -76,7 +76,7 @@ http:
|
||||||
|
|
||||||
# API router (handles /api/v1 paths)
|
# API router (handles /api/v1 paths)
|
||||||
api-router:
|
api-router:
|
||||||
rule: "Host(`{{.DomainName}}`) && PathPrefix(`/api/v1`)" # Dynamic Domain Name
|
rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)" # Dynamic Domain Name
|
||||||
service: api-service
|
service: api-service
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
|
@ -87,7 +87,7 @@ http:
|
||||||
|
|
||||||
# WebSocket router
|
# WebSocket router
|
||||||
ws-router:
|
ws-router:
|
||||||
rule: "Host(`{{.DomainName}}`)" # Dynamic Domain Name
|
rule: "Host(`{{.DashboardDomain}}`)" # Dynamic Domain Name
|
||||||
service: api-service
|
service: api-service
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
|
@ -10,7 +10,6 @@ services:
|
||||||
interval: "3s"
|
interval: "3s"
|
||||||
timeout: "3s"
|
timeout: "3s"
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
{{if .InstallGerbil}}
|
{{if .InstallGerbil}}
|
||||||
gerbil:
|
gerbil:
|
||||||
image: fosrl/gerbil:{{.GerbilVersion}}
|
image: fosrl/gerbil:{{.GerbilVersion}}
|
||||||
|
@ -34,15 +33,13 @@ services:
|
||||||
- 443:443 # Port for traefik because of the network_mode
|
- 443:443 # Port for traefik because of the network_mode
|
||||||
- 80:80 # Port for traefik because of the network_mode
|
- 80:80 # Port for traefik because of the network_mode
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v3.3.3
|
image: traefik:v3.3.3
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
{{if .InstallGerbil}}
|
{{if .InstallGerbil}}
|
||||||
network_mode: service:gerbil # Ports appear on the gerbil service
|
network_mode: service:gerbil # Ports appear on the gerbil service
|
||||||
{{end}}
|
{{end}}{{if not .InstallGerbil}}
|
||||||
{{if not .InstallGerbil}}
|
|
||||||
ports:
|
ports:
|
||||||
- 443:443
|
- 443:443
|
||||||
- 80:80
|
- 80:80
|
|
@ -2,45 +2,26 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed crowdsec/*
|
func installCrowdsec(config Config) error {
|
||||||
var configCrowdsecFiles embed.FS
|
|
||||||
|
|
||||||
// DockerContainer represents a Docker container
|
|
||||||
type DockerContainer struct {
|
|
||||||
NetworkSettings struct {
|
|
||||||
Networks map[string]struct {
|
|
||||||
IPAddress string `json:"IPAddress"`
|
|
||||||
} `json:"Networks"`
|
|
||||||
} `json:"NetworkSettings"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func installCrowdsec() error {
|
|
||||||
// Create configuration
|
|
||||||
config := &Config{}
|
|
||||||
|
|
||||||
// Run installation steps
|
// Run installation steps
|
||||||
if err := backupConfig(); err != nil {
|
if err := backupConfig(); err != nil {
|
||||||
return fmt.Errorf("backup failed: %v", err)
|
return fmt.Errorf("backup failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := modifyDockerCompose(); err != nil {
|
if err := AddCrowdSecService("docker-compose.yml"); err != nil {
|
||||||
return fmt.Errorf("docker-compose modification failed: %v", err)
|
return fmt.Errorf("crowdsec service addition failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createCrowdsecFiles(*config); err != nil {
|
if err := createConfigFiles(config); err != nil {
|
||||||
return fmt.Errorf("config file creation failed: %v", err)
|
fmt.Printf("Error creating config files: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
moveFile("config/crowdsec/traefik_config.yaml", "config/traefik/traefik_config.yaml")
|
moveFile("config/crowdsec/traefik_config.yaml", "config/traefik/traefik_config.yaml")
|
||||||
|
@ -50,14 +31,6 @@ func installCrowdsec() error {
|
||||||
return fmt.Errorf("bouncer key retrieval failed: %v", err)
|
return fmt.Errorf("bouncer key retrieval failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deployStack(); err != nil {
|
|
||||||
return fmt.Errorf("deployment failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := verifyDeployment(); err != nil {
|
|
||||||
return fmt.Errorf("verification failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,33 +53,7 @@ func backupConfig() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyDockerCompose() error {
|
func retrieveBouncerKey(config Config) error {
|
||||||
// Read existing docker-compose.yml
|
|
||||||
content, err := os.ReadFile("docker-compose.yml")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read docker-compose.yml: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify required services exist
|
|
||||||
requiredServices := []string{"services:", "pangolin:", "gerbil:", "traefik:"}
|
|
||||||
for _, service := range requiredServices {
|
|
||||||
if !bytes.Contains(content, []byte(service)) {
|
|
||||||
return fmt.Errorf("required service %s not found in docker-compose.yml", service)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add crowdsec service
|
|
||||||
modified := addCrowdsecService(string(content))
|
|
||||||
|
|
||||||
// Write modified content
|
|
||||||
if err := os.WriteFile("docker-compose.yml", []byte(modified), 0644); err != nil {
|
|
||||||
return fmt.Errorf("failed to write modified docker-compose.yml: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func retrieveBouncerKey(config *Config) error {
|
|
||||||
// Start crowdsec container
|
// Start crowdsec container
|
||||||
cmd := exec.Command("docker", "compose", "up", "-d", "crowdsec")
|
cmd := exec.Command("docker", "compose", "up", "-d", "crowdsec")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
@ -114,8 +61,24 @@ func retrieveBouncerKey(config *Config) error {
|
||||||
}
|
}
|
||||||
defer exec.Command("docker", "compose", "down").Run()
|
defer exec.Command("docker", "compose", "down").Run()
|
||||||
|
|
||||||
// Wait for container to start
|
// verify that the container is running if not keep waiting for 10 more seconds then return an error
|
||||||
|
count := 0
|
||||||
|
for {
|
||||||
|
cmd := exec.Command("docker", "inspect", "-f", "{{.State.Running}}", "crowdsec")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to inspect crowdsec container: %v", err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(output)) == "true" {
|
||||||
|
break
|
||||||
|
}
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
|
count++
|
||||||
|
|
||||||
|
if count > 4 {
|
||||||
|
return fmt.Errorf("crowdsec container is not running")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get bouncer key
|
// Get bouncer key
|
||||||
output, err := exec.Command("docker", "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer").Output()
|
output, err := exec.Command("docker", "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer").Output()
|
||||||
|
@ -135,181 +98,13 @@ func retrieveBouncerKey(config *Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deployStack() error {
|
func checkIsCrowdsecInstalledInCompose() bool {
|
||||||
cmd := exec.Command("docker", "compose", "up", "-d")
|
// Read docker-compose.yml
|
||||||
if err := cmd.Run(); err != nil {
|
content, err := os.ReadFile("docker-compose.yml")
|
||||||
return fmt.Errorf("failed to deploy stack: %v", err)
|
if err != nil {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Stack deployed. Waiting 2 minutes for services to initialize...")
|
// Check for crowdsec service
|
||||||
time.Sleep(2 * time.Minute)
|
return bytes.Contains(content, []byte("crowdsec:"))
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyDeployment() error {
|
|
||||||
resp, err := exec.Command("curl", "-s", "http://localhost:6060/metrics").Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get metrics: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Contains(resp, []byte("appsec")) {
|
|
||||||
return fmt.Errorf("appsec metrics not found in response")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
|
||||||
source, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer source.Close()
|
|
||||||
|
|
||||||
destination, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer destination.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(destination, source)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func moveFile(src, dst string) error {
|
|
||||||
if err := copyFile(src, dst); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Remove(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addCrowdsecService(content string) string {
|
|
||||||
// Implementation of adding crowdsec service to docker-compose.yml
|
|
||||||
// This would involve string manipulation or template rendering
|
|
||||||
// The actual implementation would depend on how you want to structure the docker-compose modifications
|
|
||||||
return content + `
|
|
||||||
crowdsec:
|
|
||||||
image: crowdsecurity/crowdsec:latest
|
|
||||||
container_name: crowdsec
|
|
||||||
environment:
|
|
||||||
GID: "${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
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=false"
|
|
||||||
volumes:
|
|
||||||
- ./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:
|
|
||||||
- 9090:9090
|
|
||||||
- 6060:6060
|
|
||||||
expose:
|
|
||||||
- 9090
|
|
||||||
- 6060
|
|
||||||
- 7422
|
|
||||||
restart: unless-stopped
|
|
||||||
command: -t`
|
|
||||||
}
|
|
||||||
|
|
||||||
func createCrowdsecFiles(config Config) error {
|
|
||||||
// Walk through all embedded files
|
|
||||||
err := fs.WalkDir(configCrowdsecFiles, "crowdsec", func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the root fs directory itself
|
|
||||||
if path == "fs" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the relative path by removing the "fs/" prefix
|
|
||||||
relPath := strings.TrimPrefix(path, "fs/")
|
|
||||||
|
|
||||||
// skip .DS_Store
|
|
||||||
if strings.Contains(relPath, ".DS_Store") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the full output path under "config/"
|
|
||||||
outPath := filepath.Join("config", relPath)
|
|
||||||
|
|
||||||
if d.IsDir() {
|
|
||||||
// Create directory
|
|
||||||
if err := os.MkdirAll(outPath, 0755); err != nil {
|
|
||||||
return fmt.Errorf("failed to create directory %s: %v", outPath, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the template file
|
|
||||||
content, err := configFiles.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse template
|
|
||||||
tmpl, err := template.New(d.Name()).Parse(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse template %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure parent directory exists
|
|
||||||
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
|
|
||||||
return fmt.Errorf("failed to create parent directory for %s: %v", outPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create output file
|
|
||||||
outFile, err := os.Create(outPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create %s: %v", outPath, err)
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
|
|
||||||
// Execute template
|
|
||||||
if err := tmpl.Execute(outFile, config); err != nil {
|
|
||||||
return fmt.Errorf("failed to execute template %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error walking config files: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the current directory
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get current directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourcePath := filepath.Join(dir, "config/docker-compose.yml")
|
|
||||||
destPath := filepath.Join(dir, "docker-compose.yml")
|
|
||||||
|
|
||||||
// Check if source file exists
|
|
||||||
if _, err := os.Stat(sourcePath); err != nil {
|
|
||||||
return fmt.Errorf("source docker-compose.yml not found: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to move the file
|
|
||||||
err = os.Rename(sourcePath, destPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to move docker-compose.yml from %s to %s: %v",
|
|
||||||
sourcePath, destPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,5 @@ go 1.23.0
|
||||||
require (
|
require (
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/term v0.28.0 // indirect
|
golang.org/x/term v0.28.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,3 +2,6 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
132
install/main.go
132
install/main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -24,7 +25,7 @@ func loadVersions(config *Config) {
|
||||||
config.BadgerVersion = "replaceme"
|
config.BadgerVersion = "replaceme"
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed fs/*
|
//go:embed config/*
|
||||||
var configFiles embed.FS
|
var configFiles embed.FS
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -46,6 +47,7 @@ type Config struct {
|
||||||
EmailNoReply string
|
EmailNoReply string
|
||||||
InstallGerbil bool
|
InstallGerbil bool
|
||||||
TraefikBouncerKey string
|
TraefikBouncerKey string
|
||||||
|
DoCrowdsecInstall bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -57,9 +59,12 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
config.DoCrowdsecInstall = false
|
||||||
|
|
||||||
// check if there is already a config file
|
// check if there is already a config file
|
||||||
if _, err := os.Stat("config/config.yml"); err != nil {
|
if _, err := os.Stat("config/config.yml"); err != nil {
|
||||||
config := collectUserInput(reader)
|
config = collectUserInput(reader)
|
||||||
|
|
||||||
loadVersions(&config)
|
loadVersions(&config)
|
||||||
|
|
||||||
|
@ -68,20 +73,55 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveFile("config/docker-compose.yml", "docker-compose.yml")
|
||||||
|
|
||||||
if !isDockerInstalled() && runtime.GOOS == "linux" {
|
if !isDockerInstalled() && runtime.GOOS == "linux" {
|
||||||
if readBool(reader, "Docker is not installed. Would you like to install it?", true) {
|
if readBool(reader, "Docker is not installed. Would you like to install it?", true) {
|
||||||
installDocker()
|
installDocker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fmt.Println("Config file already exists... skipping configuration")
|
fmt.Println("\n=== Starting installation ===")
|
||||||
}
|
|
||||||
|
|
||||||
if isDockerInstalled() {
|
if isDockerInstalled() {
|
||||||
if readBool(reader, "Would you like to install and start the containers?", true) {
|
if readBool(reader, "Would you like to install and start the containers?", true) {
|
||||||
pullAndStartContainers()
|
pullAndStartContainers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Looks like you already installed, so I am going to do the setup...")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checkIsCrowdsecInstalledInCompose() {
|
||||||
|
fmt.Println("\n=== Crowdsec Install ===")
|
||||||
|
// check if crowdsec is installed
|
||||||
|
if readBool(reader, "Would you like to install Crowdsec?", true) {
|
||||||
|
|
||||||
|
if config.DashboardDomain == "" {
|
||||||
|
traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml", "config/traefik/dynamic_config.yml")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading config: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.DashboardDomain = traefikConfig.DashboardDomain
|
||||||
|
config.LetsEncryptEmail = traefikConfig.LetsEncryptEmail
|
||||||
|
config.BadgerVersion = traefikConfig.BadgerVersion
|
||||||
|
|
||||||
|
// print the values and check if they are right
|
||||||
|
fmt.Println("Detected values:")
|
||||||
|
fmt.Printf("Dashboard Domain: %s\n", config.DashboardDomain)
|
||||||
|
fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail)
|
||||||
|
fmt.Printf("Badger Version: %s\n", config.BadgerVersion)
|
||||||
|
|
||||||
|
if !readBool(reader, "Are these values correct?", true) {
|
||||||
|
config = collectUserInput(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.DoCrowdsecInstall = true
|
||||||
|
installCrowdsec(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Installation complete!")
|
fmt.Println("Installation complete!")
|
||||||
}
|
}
|
||||||
|
@ -137,6 +177,11 @@ func readInt(reader *bufio.Reader, prompt string, defaultValue int) int {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDockerFilePresent() bool {
|
||||||
|
_, err := os.Stat("docker-compose.yml")
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
func collectUserInput(reader *bufio.Reader) Config {
|
func collectUserInput(reader *bufio.Reader) Config {
|
||||||
config := Config{}
|
config := Config{}
|
||||||
|
|
||||||
|
@ -262,31 +307,33 @@ func createConfigFiles(config Config) error {
|
||||||
os.MkdirAll("config/logs", 0755)
|
os.MkdirAll("config/logs", 0755)
|
||||||
|
|
||||||
// Walk through all embedded files
|
// Walk through all embedded files
|
||||||
err := fs.WalkDir(configFiles, "fs", func(path string, d fs.DirEntry, err error) error {
|
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the root fs directory itself
|
// Skip the root fs directory itself
|
||||||
if path == "fs" {
|
if path == "config" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the relative path by removing the "fs/" prefix
|
if !config.DoCrowdsecInstall && strings.Contains(path, "crowdsec") {
|
||||||
relPath := strings.TrimPrefix(path, "fs/")
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DoCrowdsecInstall && !strings.Contains(path, "crowdsec") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// skip .DS_Store
|
// skip .DS_Store
|
||||||
if strings.Contains(relPath, ".DS_Store") {
|
if strings.Contains(path, ".DS_Store") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the full output path under "config/"
|
|
||||||
outPath := filepath.Join("config", relPath)
|
|
||||||
|
|
||||||
if d.IsDir() {
|
if d.IsDir() {
|
||||||
// Create directory
|
// Create directory
|
||||||
if err := os.MkdirAll(outPath, 0755); err != nil {
|
if err := os.MkdirAll(path, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create directory %s: %v", outPath, err)
|
return fmt.Errorf("failed to create directory %s: %v", path, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -304,14 +351,14 @@ func createConfigFiles(config Config) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure parent directory exists
|
// Ensure parent directory exists
|
||||||
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create parent directory for %s: %v", outPath, err)
|
return fmt.Errorf("failed to create parent directory for %s: %v", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create output file
|
// Create output file
|
||||||
outFile, err := os.Create(outPath)
|
outFile, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create %s: %v", outPath, err)
|
return fmt.Errorf("failed to create %s: %v", path, err)
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer outFile.Close()
|
||||||
|
|
||||||
|
@ -327,30 +374,10 @@ func createConfigFiles(config Config) error {
|
||||||
return fmt.Errorf("error walking config files: %v", err)
|
return fmt.Errorf("error walking config files: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the current directory
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get current directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourcePath := filepath.Join(dir, "config/docker-compose.yml")
|
|
||||||
destPath := filepath.Join(dir, "docker-compose.yml")
|
|
||||||
|
|
||||||
// Check if source file exists
|
|
||||||
if _, err := os.Stat(sourcePath); err != nil {
|
|
||||||
return fmt.Errorf("source docker-compose.yml not found: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to move the file
|
|
||||||
err = os.Rename(sourcePath, destPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to move docker-compose.yml from %s to %s: %v",
|
|
||||||
sourcePath, destPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func installDocker() error {
|
func installDocker() error {
|
||||||
// Detect Linux distribution
|
// Detect Linux distribution
|
||||||
cmd := exec.Command("cat", "/etc/os-release")
|
cmd := exec.Command("cat", "/etc/os-release")
|
||||||
|
@ -491,3 +518,28 @@ func pullAndStartContainers() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
source, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
destination, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destination.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destination, source)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveFile(src, dst string) error {
|
||||||
|
if err := copyFile(src, dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Remove(src)
|
||||||
|
}
|
Loading…
Reference in a new issue