feat(infra): auto-commit con 86 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
// wgEndpointRe matches "host:port" where host is a hostname or IP and port is 1–65535.
|
||||
var wgEndpointRe = regexp.MustCompile(`^[a-zA-Z0-9._\-]+:\d{1,5}$`)
|
||||
|
||||
// WGClientConfigGen generates the wg0.conf content for a WireGuard peer (client)
|
||||
// and a unicode-block QR string suitable for mobile enrollment via Element or a terminal.
|
||||
//
|
||||
// Pure: no I/O, fully deterministic given the inputs. Returns error on invalid inputs.
|
||||
func WGClientConfigGen(in WGClientConfigInput) (WGClientConfig, error) {
|
||||
if err := validateWGClientInput(in); err != nil {
|
||||
return WGClientConfig{}, err
|
||||
}
|
||||
|
||||
ka := in.PersistentKA
|
||||
if ka == 0 {
|
||||
ka = 25
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
// [Interface] section
|
||||
b.WriteString("[Interface]\n")
|
||||
fmt.Fprintf(&b, "PrivateKey = %s\n", in.DevicePrivateKey)
|
||||
fmt.Fprintf(&b, "Address = %s\n", in.DeviceAddress)
|
||||
if in.DNS != "" {
|
||||
fmt.Fprintf(&b, "DNS = %s\n", in.DNS)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
// [Peer] section (hub)
|
||||
b.WriteString("[Peer]\n")
|
||||
fmt.Fprintf(&b, "PublicKey = %s\n", in.HubPublicKey)
|
||||
if in.PresharedKey != "" {
|
||||
fmt.Fprintf(&b, "PresharedKey = %s\n", in.PresharedKey)
|
||||
}
|
||||
fmt.Fprintf(&b, "Endpoint = %s\n", in.HubEndpoint)
|
||||
fmt.Fprintf(&b, "AllowedIPs = %s\n", in.HubAllowedIPs)
|
||||
fmt.Fprintf(&b, "PersistentKeepalive = %d\n", ka)
|
||||
|
||||
ini := b.String()
|
||||
|
||||
qr, err := qrcode.New(ini, qrcode.Medium)
|
||||
if err != nil {
|
||||
return WGClientConfig{}, fmt.Errorf("wg_client_config: qr encode: %w", err)
|
||||
}
|
||||
|
||||
return WGClientConfig{
|
||||
INI: ini,
|
||||
QR: qr.ToString(false),
|
||||
Filename: "wg0.conf",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateWGClientInput checks all required fields for correctness.
|
||||
func validateWGClientInput(in WGClientConfigInput) error {
|
||||
if err := validateWGBase64Key("DevicePrivateKey", in.DevicePrivateKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateWGBase64Key("HubPublicKey", in.HubPublicKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.PresharedKey != "" {
|
||||
if err := validateWGBase64Key("PresharedKey", in.PresharedKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Validate DeviceAddress (CIDR)
|
||||
if _, _, err := net.ParseCIDR(in.DeviceAddress); err != nil {
|
||||
return fmt.Errorf("wg_client_config: DeviceAddress %q is not a valid CIDR: %w", in.DeviceAddress, err)
|
||||
}
|
||||
|
||||
// Validate HubAllowedIPs (comma-separated CIDRs)
|
||||
for _, cidr := range strings.Split(in.HubAllowedIPs, ",") {
|
||||
cidr = strings.TrimSpace(cidr)
|
||||
if cidr == "" {
|
||||
continue
|
||||
}
|
||||
if _, _, err := net.ParseCIDR(cidr); err != nil {
|
||||
return fmt.Errorf("wg_client_config: HubAllowedIPs entry %q is not a valid CIDR: %w", cidr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate HubEndpoint
|
||||
if !wgEndpointRe.MatchString(in.HubEndpoint) {
|
||||
return fmt.Errorf("wg_client_config: HubEndpoint %q must be host:port", in.HubEndpoint)
|
||||
}
|
||||
parts := strings.SplitN(in.HubEndpoint, ":", 2)
|
||||
port := 0
|
||||
if _, err := fmt.Sscanf(parts[1], "%d", &port); err != nil || port < 1 || port > 65535 {
|
||||
return fmt.Errorf("wg_client_config: HubEndpoint port %q out of range 1-65535", parts[1])
|
||||
}
|
||||
|
||||
if in.DevicePrivateKey == "" {
|
||||
return fmt.Errorf("wg_client_config: DevicePrivateKey is required")
|
||||
}
|
||||
if in.HubPublicKey == "" {
|
||||
return fmt.Errorf("wg_client_config: HubPublicKey is required")
|
||||
}
|
||||
if in.HubEndpoint == "" {
|
||||
return fmt.Errorf("wg_client_config: HubEndpoint is required")
|
||||
}
|
||||
if in.HubAllowedIPs == "" {
|
||||
return fmt.Errorf("wg_client_config: HubAllowedIPs is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateWGBase64Key checks that a WireGuard key is a valid 32-byte base64-encoded string (44 chars).
|
||||
func validateWGBase64Key(field, key string) error {
|
||||
if len(key) != 44 {
|
||||
return fmt.Errorf("wg_client_config: %s must be 44 base64 chars, got %d", field, len(key))
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wg_client_config: %s is not valid base64: %w", field, err)
|
||||
}
|
||||
if len(decoded) != 32 {
|
||||
return fmt.Errorf("wg_client_config: %s must decode to 32 bytes, got %d", field, len(decoded))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user