feat(infra): auto-commit con 86 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 19:38:15 +02:00
parent de9bfec498
commit fe65c5e527
85 changed files with 11840 additions and 92 deletions
+134
View File
@@ -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 165535.
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
}