summaryrefslogtreecommitdiff
path: root/pkg/wgconf/wgconf.go
diff options
context:
space:
mode:
authorJames O'Doherty <james@theodohertyfamily.com>2026-05-29 19:30:26 -0400
committerJames O'Doherty <james@theodohertyfamily.com>2026-05-29 19:30:26 -0400
commitb1b68a4aa441d9ce39d05f85338e371a704dd601 (patch)
tree63491b88a18522eafddbd4b7525bb89bc2a04732 /pkg/wgconf/wgconf.go
parent70096b533d42b684ab13651aaae884047e01e43d (diff)
feat(cli,parser): support custom profile names and overhaul WireGuard .conf parser for robustness
- CLI: - Add optional `[name]` argument to `wg-wrap profile import <path> [name]` to allow overriding the imported profile name. If not provided, it falls back to the derived filename. - Update `README.md` command documentation to reflect custom profile names and list the `wg-wrap profile stop <name>` subcommand. - Expand `internal/cli/profile_test.go` to cover derived vs custom-named profile imports. - WG Configuration Parser: - Overhaul `pkg/wgconf/wgconf.go` to support case-insensitivity on section headers (e.g. `[peer]`, `[interface]`) and key names (e.g. `privatekey`, `allowedips`). - Implement robust trailing comment stripping (both `#` and `;`) while preserving inline comment-like characters in cryptographic keys (e.g. `key-with-hash-inside#123`) using whitespace-padded match logic. - Clean up and normalize leading/trailing spaces/tabs on parsed keys, values, and list elements (e.g. `AllowedIPs` and `DNS` fields). - Gracefully ignore unrecognized keys (e.g. `MTU`, `ListenPort`, `PresharedKey`) without returning errors. - Add comprehensive tests in `pkg/wgconf/wgconf_test.go` covering inline/block comments, formatting variations, unrecognized keys, and case-insensitivity.
Diffstat (limited to 'pkg/wgconf/wgconf.go')
-rw-r--r--pkg/wgconf/wgconf.go55
1 files changed, 42 insertions, 13 deletions
diff --git a/pkg/wgconf/wgconf.go b/pkg/wgconf/wgconf.go
index 2615892..36434ba 100644
--- a/pkg/wgconf/wgconf.go
+++ b/pkg/wgconf/wgconf.go
@@ -22,6 +22,23 @@ type Peer struct {
AllowedIPs []string
}
+// stripComment removes inline and block comments from a line, keeping spaces intact otherwise.
+func stripComment(line string) string {
+ line = strings.TrimSpace(line)
+ if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
+ return ""
+ }
+
+ // Find the first occurrence of '#' or ';' preceded by a whitespace character.
+ // This protects characters like '#' if they are part of a key/value with no leading whitespace.
+ for i := 1; i < len(line); i++ {
+ if (line[i] == '#' || line[i] == ';') && (line[i-1] == ' ' || line[i-1] == '\t') {
+ return strings.TrimSpace(line[:i])
+ }
+ }
+ return line
+}
+
// Parse reads a WireGuard .conf file and returns a Config struct.
func Parse(path string) (*Config, error) {
file, err := os.Open(path)
@@ -40,18 +57,23 @@ func Parse(path string) (*Config, error) {
scanner := bufio.NewScanner(file)
for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if line == "" || strings.HasPrefix(line, "#") {
+ line := stripComment(scanner.Text())
+ if line == "" {
continue
}
- if strings.HasPrefix(line, "[") {
- section := strings.Trim(line, "[]")
- if section == "Peer" {
+ if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
+ section := strings.ToLower(strings.Trim(line, "[] \t"))
+ if section == "peer" {
if currentPeer != nil {
cfg.Peers = append(cfg.Peers, *currentPeer)
}
currentPeer = &Peer{}
+ } else {
+ if currentPeer != nil {
+ cfg.Peers = append(cfg.Peers, *currentPeer)
+ currentPeer = nil
+ }
}
continue
}
@@ -61,25 +83,32 @@ func Parse(path string) (*Config, error) {
return nil, fmt.Errorf("invalid line format: %s", line)
}
- key := strings.TrimSpace(parts[0])
+ key := strings.ToLower(strings.TrimSpace(parts[0]))
val := strings.TrimSpace(parts[1])
if currentPeer != nil {
switch key {
- case "PublicKey":
+ case "publickey":
currentPeer.PublicKey = val
- case "Endpoint":
+ case "endpoint":
currentPeer.Endpoint = val
- case "AllowedIPs":
- currentPeer.AllowedIPs = strings.Split(val, ",")
+ case "allowedips":
+ var ips []string
+ for _, ip := range strings.Split(val, ",") {
+ trimmed := strings.TrimSpace(ip)
+ if trimmed != "" {
+ ips = append(ips, trimmed)
+ }
+ }
+ currentPeer.AllowedIPs = ips
}
} else {
switch key {
- case "PrivateKey":
+ case "privatekey":
cfg.PrivateKey = val
- case "Address":
+ case "address":
cfg.Address = val
- case "DNS":
+ case "dns":
cfg.DNS = val
}
}