Files
fn_registry/functions/infra/wg_client_config.go
T
egutierrez 621e8895c9 feat(infra): auto-commit con 86 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:38:15 +02:00

135 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}