package wan import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "io" "net/http" "net/netip" "os" "strings" "time" ) type Client struct { URL string Key string Secret string HTTP *http.Client } func New(url, directory string) (*Client, error) { key, err := os.ReadFile(directory + "/key") if err != nil { return nil, err } secret, err := os.ReadFile(directory + "/secret") if err != nil { return nil, err } ca, err := os.ReadFile(directory + "/ca.crt") if err != nil { return nil, err } roots := x509.NewCertPool() if !roots.AppendCertsFromPEM(ca) { return nil, fmt.Errorf("invalid OPNsense CA") } return &Client{URL: url, Key: strings.TrimSpace(string(key)), Secret: strings.TrimSpace(string(secret)), HTTP: &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}}, }}, nil } func (c *Client) Address(ctx context.Context) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(c.URL, "/")+"/api/interfaces/overview/interfaces_info", nil) if err != nil { return "", err } req.SetBasicAuth(c.Key, c.Secret) resp, err := c.HTTP.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("OPNsense HTTP %d", resp.StatusCode) } var response struct { Rows []struct { Identifier string `json:"identifier"` Description string `json:"description"` IPv4 []struct { Address string `json:"ipaddr"` } `json:"ipv4"` } `json:"rows"` } if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&response); err != nil { return "", err } var found []string for _, row := range response.Rows { if row.Identifier != "WAN" && row.Description != "WAN" { continue } for _, entry := range row.IPv4 { value := strings.Split(entry.Address, "/")[0] ip, err := netip.ParseAddr(value) if err == nil && ip.Is4() && ip.IsGlobalUnicast() && !ip.IsPrivate() { found = append(found, ip.String()) } } } if len(found) != 1 { return "", fmt.Errorf("expected one public WAN IPv4") } return found[0], nil }