mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-12 16:12:44 +00:00
549 lines
13 KiB
Go
549 lines
13 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/go-errors/errors"
|
|
|
|
"github.com/phishingclub/phishingclub/data"
|
|
"github.com/phishingclub/phishingclub/errs"
|
|
"github.com/phishingclub/phishingclub/file"
|
|
)
|
|
|
|
var (
|
|
ErrMissingIP = errors.New("missing IP")
|
|
ErrMissingPort = errors.New("missing port")
|
|
ErrMissingDatabaseDSN = errors.New("missing database DSN")
|
|
ErrInvalidIP = errors.New("invalid IP")
|
|
ErrInvalidPort = errors.New("invalid port")
|
|
ErrInvalidDatabase = errors.New("invalid database")
|
|
ErrWriterIsNil = errors.New("writer is nil")
|
|
)
|
|
|
|
const (
|
|
DefaultACMEEmail = ""
|
|
DefaultDevACMEEmail = ""
|
|
|
|
DatabaseUsePostgres = "postgres"
|
|
DefaultAdministrationUseSqlite = "sqlite3"
|
|
DefaultDatabase = DefaultAdministrationUseSqlite
|
|
DefaultAdministrationDSN = "file:./db.sqlite3"
|
|
|
|
DefaultDevAdministrationPort = 0 // 0 uses ephemeral port, random available port
|
|
DefaultDevHTTPPhishingPort = 8080
|
|
DefaultDevHTTPSPhishingPort = 8443
|
|
|
|
DefaultProductionAdministrationPort = 0 // 0 uses ephemeral port, random available port
|
|
DefaultProductionHTTPPhishingPort = 80
|
|
DefaultProductionHTTPSPhishingPort = 443
|
|
|
|
// empty is none
|
|
DefaultLogFilePath = ""
|
|
DefaultErrLogFilePath = ""
|
|
|
|
DefaultTrustedIPHeader = ""
|
|
|
|
DefaultAdminHost = ""
|
|
DefaultAdminAutoTLS = true
|
|
DefaultAdminAutoTLSString = "true"
|
|
)
|
|
|
|
var (
|
|
defaultTrustedProxies = []string{}
|
|
defaultAdminAllowed = []string{}
|
|
)
|
|
|
|
type (
|
|
// Config config
|
|
Config struct {
|
|
acme ACME
|
|
|
|
tlsHost string
|
|
tlsAuto bool
|
|
|
|
tlsCertPath string
|
|
tlsKeyPath string
|
|
|
|
adminNetAddress net.TCPAddr
|
|
phishingHTTPNetAddress net.TCPAddr
|
|
phishingHTTPSNetAddress net.TCPAddr
|
|
database Database
|
|
fileWriter file.Writer
|
|
|
|
LogPath string
|
|
ErrLogPath string
|
|
|
|
IPSecurity IPSecurityConfig
|
|
}
|
|
|
|
// ConfigDTO config DTO
|
|
ConfigDTO struct {
|
|
ACME ACME `json:"acme"`
|
|
AdministrationServer AdministrationServer `json:"administration"`
|
|
PhishingServer PhishingServer `json:"phishing"`
|
|
Database Database `json:"database"`
|
|
Log Log `json:"log"`
|
|
IPSecurity IPSecurityConfig `json:"ip_security"`
|
|
}
|
|
|
|
Log struct {
|
|
Path string `json:"path"`
|
|
ErrorPath string `json:"errorPath"`
|
|
}
|
|
|
|
// AdministrationServer ConfigDTO administration
|
|
AdministrationServer struct {
|
|
TLSHost string `json:"tls_host"`
|
|
TLSAuto bool `json:"tls_auto"`
|
|
TLSCertPath string `json:"tls_cert_path"`
|
|
TLSKeyPath string `json:"tls_key_path"`
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
// PhishingServer ConfigDTO phishing
|
|
PhishingServer struct {
|
|
Http string `json:"http"`
|
|
Https string `json:"https"`
|
|
}
|
|
|
|
// Database ConfigDTO database
|
|
Database struct {
|
|
Engine string `json:"engine"`
|
|
DSN string `json:"dsn"`
|
|
}
|
|
|
|
// ACME ConfigDTO acme
|
|
ACME struct {
|
|
Email string `json:"email"`
|
|
}
|
|
)
|
|
|
|
type IPSecurityConfig struct {
|
|
// ip/cidr that are allowed to access the admin interface
|
|
AdminAllowed []string `json:"admin_allowed"`
|
|
|
|
// ip/cidr of legitimate reverse proxies (e.g., Nginx, HAProxy, Cloudflare edges)
|
|
TrustedProxies []string `json:"trusted_proxies"`
|
|
|
|
// headers to check for real client IP
|
|
// examples: CF-Connecting-IP, X-Real-IP, True-Client-IP, X-Forwarded-For
|
|
TrustedIPHeader string `json:"trusted_ip_header"`
|
|
}
|
|
|
|
// ValidateFileWriter validates the file writer
|
|
func ValidateFileWriter(fileWriter file.Writer) error {
|
|
if fileWriter == nil {
|
|
return ErrWriterIsNil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewConfig factory
|
|
func NewConfig(
|
|
acmeEmail string,
|
|
tlsHost string,
|
|
tlsAuto bool,
|
|
adminPublicCertPath string,
|
|
adminPrivateCertKey string,
|
|
adminAddress string,
|
|
phishingHTTPAddress string,
|
|
phishingHTTPSAddress string,
|
|
database Database,
|
|
fileWriter file.Writer,
|
|
logPath string,
|
|
errLogPath string,
|
|
ipSecurity IPSecurityConfig,
|
|
) (*Config, error) {
|
|
if err := ValidateFileWriter(fileWriter); err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
adminNetAddress, err := StringAddressToTCPAddr(adminAddress)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
phishingHTTPNetAddress, err := StringAddressToTCPAddr(phishingHTTPAddress)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
phishingHTTPSNetAddress, err := StringAddressToTCPAddr(phishingHTTPSAddress)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
switch database.Engine {
|
|
case DatabaseUsePostgres:
|
|
case DefaultAdministrationUseSqlite:
|
|
default:
|
|
return nil, ErrInvalidDatabase
|
|
}
|
|
|
|
return &Config{
|
|
acme: ACME{
|
|
Email: acmeEmail,
|
|
},
|
|
tlsHost: tlsHost,
|
|
tlsAuto: tlsAuto,
|
|
tlsCertPath: adminPublicCertPath,
|
|
tlsKeyPath: adminPrivateCertKey,
|
|
adminNetAddress: *adminNetAddress,
|
|
phishingHTTPNetAddress: *phishingHTTPNetAddress,
|
|
phishingHTTPSNetAddress: *phishingHTTPSNetAddress,
|
|
database: Database{
|
|
Engine: database.Engine,
|
|
DSN: database.DSN,
|
|
},
|
|
fileWriter: &file.FileWriter{},
|
|
LogPath: logPath,
|
|
ErrLogPath: errLogPath,
|
|
IPSecurity: ipSecurity,
|
|
}, nil
|
|
}
|
|
|
|
// NewDevDefaultConfig returns a default config
|
|
func NewDevDefaultConfig() *Config {
|
|
tlsHost := "phish.test"
|
|
tlsAuto := false
|
|
publicCertPath := fmt.Sprintf(
|
|
"%s/%s",
|
|
data.DefaultAdminCertDir,
|
|
data.DefaultAdminPublicCertFileName,
|
|
)
|
|
privateCertPath := fmt.Sprintf(
|
|
"%s/%s",
|
|
data.DefaultAdminCertDir,
|
|
data.DefaultAdminPrivateCertFileName,
|
|
)
|
|
return &Config{
|
|
acme: ACME{
|
|
Email: DefaultACMEEmail,
|
|
},
|
|
tlsHost: tlsHost,
|
|
tlsAuto: tlsAuto,
|
|
tlsCertPath: publicCertPath,
|
|
tlsKeyPath: privateCertPath,
|
|
adminNetAddress: net.TCPAddr{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Port: DefaultDevAdministrationPort,
|
|
},
|
|
phishingHTTPNetAddress: net.TCPAddr{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Port: DefaultDevHTTPPhishingPort,
|
|
},
|
|
phishingHTTPSNetAddress: net.TCPAddr{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Port: DefaultDevHTTPSPhishingPort,
|
|
},
|
|
database: Database{
|
|
Engine: DefaultAdministrationUseSqlite,
|
|
DSN: DefaultAdministrationDSN,
|
|
},
|
|
fileWriter: &file.FileWriter{},
|
|
LogPath: DefaultLogFilePath,
|
|
ErrLogPath: DefaultErrLogFilePath,
|
|
IPSecurity: IPSecurityConfig{
|
|
AdminAllowed: []string{},
|
|
TrustedProxies: []string{},
|
|
TrustedIPHeader: "",
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDevDefaultConfig returns a default config
|
|
func NewProductionDefaultConfig() *Config {
|
|
tlsHost := "localhost"
|
|
tlsAuto := DefaultAdminAutoTLS
|
|
publicCertPath := fmt.Sprintf(
|
|
"%s/%s",
|
|
data.DefaultAdminCertDir,
|
|
data.DefaultAdminPublicCertFileName,
|
|
)
|
|
privateCertPath := fmt.Sprintf(
|
|
"%s/%s",
|
|
data.DefaultAdminCertDir,
|
|
data.DefaultAdminPrivateCertFileName,
|
|
)
|
|
return &Config{
|
|
acme: ACME{
|
|
Email: DefaultACMEEmail,
|
|
},
|
|
tlsHost: tlsHost,
|
|
tlsAuto: tlsAuto,
|
|
tlsCertPath: publicCertPath,
|
|
tlsKeyPath: privateCertPath,
|
|
adminNetAddress: net.TCPAddr{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Port: DefaultProductionAdministrationPort,
|
|
},
|
|
phishingHTTPNetAddress: net.TCPAddr{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Port: DefaultProductionHTTPPhishingPort,
|
|
},
|
|
phishingHTTPSNetAddress: net.TCPAddr{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Port: DefaultProductionHTTPSPhishingPort,
|
|
},
|
|
|
|
database: Database{
|
|
Engine: DefaultAdministrationUseSqlite,
|
|
DSN: DefaultAdministrationDSN,
|
|
},
|
|
fileWriter: &file.FileWriter{},
|
|
IPSecurity: IPSecurityConfig{
|
|
AdminAllowed: []string{},
|
|
TrustedProxies: []string{},
|
|
TrustedIPHeader: "",
|
|
},
|
|
}
|
|
}
|
|
|
|
// ACMEEmail returns the acme email
|
|
func (c *Config) ACMEEmail() string {
|
|
return c.acme.Email
|
|
}
|
|
|
|
// SetACMEEmail sets the acme email
|
|
func (c *Config) SetACMEEmail(email string) {
|
|
c.acme.Email = email
|
|
}
|
|
|
|
// TLSHost returns the host to use for admin server
|
|
func (c *Config) TLSHost() string {
|
|
return c.tlsHost
|
|
}
|
|
|
|
// TLSAuto returns if ACME service should handle TLS for the admin server
|
|
func (c *Config) TLSAuto() bool {
|
|
return c.tlsAuto
|
|
}
|
|
|
|
// TLSCertPath returns the cert path
|
|
func (c *Config) TLSCertPath() string {
|
|
return c.tlsCertPath
|
|
}
|
|
|
|
// TLSKeyPath returns the private key
|
|
func (c *Config) TLSKeyPath() string {
|
|
return c.tlsKeyPath
|
|
}
|
|
|
|
// SetTLSHost sets the admin host
|
|
func (c *Config) SetTLSHost(host string) {
|
|
c.tlsHost = host
|
|
}
|
|
|
|
// SetTLSCertPath sets the TLS certificate path
|
|
func (c *Config) SetTLSCertPath(certPath string) {
|
|
c.tlsCertPath = certPath
|
|
}
|
|
|
|
// SetTLSKeyPath sets the TLS private key path
|
|
func (c *Config) SetTLSKeyPath(keyPath string) {
|
|
c.tlsKeyPath = keyPath
|
|
}
|
|
|
|
// SetTLSAuto sets if a ACME service should handle TLS for the admin server
|
|
func (c *Config) SetTLSAuto(auto bool) {
|
|
c.tlsAuto = auto
|
|
}
|
|
|
|
// SetAdminNetAddress sets the administration network address
|
|
func (c *Config) SetAdminNetAddress(adminNetAddress string) error {
|
|
newAddr, err := StringAddressToTCPAddr(adminNetAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.adminNetAddress = *newAddr
|
|
return nil
|
|
}
|
|
|
|
// SetPhishingHTTPNetAddress sets the phishing network address
|
|
func (c *Config) SetPhishingHTTPNetAddress(addr string) error {
|
|
newAddr, err := StringAddressToTCPAddr(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.phishingHTTPNetAddress = *newAddr
|
|
return nil
|
|
}
|
|
|
|
// SetPhishingHTTPNetAddress sets the phishing network address
|
|
func (c *Config) SetPhishingHTTPSNetAddress(addr string) error {
|
|
newAddr, err := StringAddressToTCPAddr(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.phishingHTTPSNetAddress = *newAddr
|
|
return nil
|
|
}
|
|
|
|
// SetDatabaseEngine sets the database engine
|
|
func (c *Config) SetDatabaseEngine(engine string) {
|
|
c.database.Engine = engine
|
|
}
|
|
|
|
// SetDatabaseDSN sets the database DSN
|
|
func (c *Config) SetDatabaseDSN(dsn string) {
|
|
c.database.DSN = dsn
|
|
}
|
|
|
|
// SetLogPath sets the log file path
|
|
func (c *Config) SetLogPath(path string) {
|
|
c.LogPath = path
|
|
}
|
|
|
|
// SetErrLogPath sets the error log file path
|
|
func (c *Config) SetErrLogPath(path string) {
|
|
c.ErrLogPath = path
|
|
}
|
|
|
|
// SetFileWriter sets the file writer
|
|
func (c *Config) SetFileWriter(fileWriter file.Writer) error {
|
|
if err := ValidateFileWriter(fileWriter); err != nil {
|
|
return fmt.Errorf("failed to set file writer on config: %w", err)
|
|
}
|
|
c.fileWriter = fileWriter
|
|
return nil
|
|
}
|
|
|
|
// Write writes the config to a writer
|
|
func (c *Config) WriteToFile(filepath string) error {
|
|
dto := c.ToDTO()
|
|
conf, err := json.MarshalIndent(dto, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
// Write the content to the writer
|
|
if _, err := c.fileWriter.Write(filepath, conf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
|
return fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StringAddressToTCPAddr converts a string address to a TCPAddr
|
|
func StringAddressToTCPAddr(address string) (*net.TCPAddr, error) {
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
ip := net.ParseIP(host)
|
|
if ip == nil {
|
|
return nil, ErrInvalidIP
|
|
}
|
|
// convert port to int
|
|
p, err := strconv.Atoi(port)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
if p < 0 || p > 65535 {
|
|
return nil, ErrInvalidPort
|
|
}
|
|
return &net.TCPAddr{
|
|
IP: ip,
|
|
Port: p,
|
|
}, nil
|
|
}
|
|
|
|
// FromMap creates a *Config from a DTO
|
|
func FromDTO(dto *ConfigDTO) (*Config, error) {
|
|
return NewConfig(
|
|
dto.ACME.Email,
|
|
dto.AdministrationServer.TLSHost,
|
|
dto.AdministrationServer.TLSAuto,
|
|
dto.AdministrationServer.TLSCertPath,
|
|
dto.AdministrationServer.TLSKeyPath,
|
|
dto.AdministrationServer.Address,
|
|
dto.PhishingServer.Http,
|
|
dto.PhishingServer.Https,
|
|
dto.Database,
|
|
file.FileWriter{},
|
|
dto.Log.Path,
|
|
dto.Log.ErrorPath,
|
|
dto.IPSecurity,
|
|
)
|
|
}
|
|
|
|
// ToDTO converts a *Config to a *ConfigDTO
|
|
func (c *Config) ToDTO() *ConfigDTO {
|
|
return &ConfigDTO{
|
|
ACME: ACME{
|
|
Email: c.acme.Email,
|
|
},
|
|
AdministrationServer: AdministrationServer{
|
|
TLSHost: c.TLSHost(),
|
|
TLSAuto: c.TLSAuto(),
|
|
TLSCertPath: c.TLSCertPath(),
|
|
TLSKeyPath: c.TLSKeyPath(),
|
|
Address: c.AdminNetAddress(),
|
|
},
|
|
PhishingServer: PhishingServer{
|
|
Http: c.phishingHTTPNetAddress.String(),
|
|
Https: c.phishingHTTPSNetAddress.String(),
|
|
},
|
|
Database: Database{
|
|
Engine: c.database.Engine,
|
|
DSN: c.database.DSN,
|
|
},
|
|
Log: Log{
|
|
Path: c.LogPath,
|
|
ErrorPath: c.ErrLogPath,
|
|
},
|
|
IPSecurity: c.IPSecurity,
|
|
}
|
|
}
|
|
|
|
// AdminNetAddress returns the administration network address
|
|
func (c *Config) AdminNetAddress() string {
|
|
return c.adminNetAddress.String()
|
|
}
|
|
|
|
// AdminNetAddressPort returns the administration network address port
|
|
func (c *Config) AdminNetAddressPort() int {
|
|
return c.adminNetAddress.Port
|
|
}
|
|
|
|
// PhishingHTTPNetAddress returns the phishing network address
|
|
func (c *Config) PhishingHTTPNetAddress() string {
|
|
return c.phishingHTTPNetAddress.String()
|
|
}
|
|
|
|
// PhishingHTTPNetAddressPort returns the phishing network address port
|
|
func (c *Config) PhishingHTTPNetAddressPort() int {
|
|
return c.phishingHTTPNetAddress.Port
|
|
}
|
|
|
|
// PhishingHTTPSNetAddress returns the phishing network address
|
|
func (c *Config) PhishingHTTPSNetAddress() string {
|
|
return c.phishingHTTPSNetAddress.String()
|
|
}
|
|
|
|
// PhishingHTTPSNetAddressPort returns the phishing network address port
|
|
func (c *Config) PhishingHTTPSNetAddressPort() int {
|
|
return c.phishingHTTPSNetAddress.Port
|
|
}
|
|
|
|
// Database returns the database
|
|
func (c *Config) Database() Database {
|
|
return c.database
|
|
}
|
|
|
|
// NewDTOFromFile creates a *ConfigDTO from a file
|
|
func NewDTOFromFile(filesystem fs.FS, path string) (*ConfigDTO, error) {
|
|
var conf ConfigDTO
|
|
f, err := filesystem.Open(path)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
dec := json.NewDecoder(f)
|
|
err = dec.Decode(&conf)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
return &conf, nil
|
|
}
|