CI / changes (push) Successful in 12s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 46s
CI / go (push) Successful in 1m15s
CI / bird2 (push) Successful in 18s
CI / release (push) Successful in 3m59s
Introduced a comprehensive firewall blocklist feature, allowing for the management of firewall clients and their associated rules. This includes endpoints for enrolling clients, listing clients and rules, and reporting apply statuses. Enhanced the API to support firewall operations, including the ability to handle block/accept policies. Updated the documentation to reflect these changes and added necessary components in the web UI for better user interaction. Additionally, modified the agent server to support firewall failover and integrated firewall functionality into the existing architecture.
171 lines
5.1 KiB
Go
171 lines
5.1 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// SpeakerMeta holds well-known keys from bgp_speaker.meta_json.
|
|
type SpeakerMeta struct {
|
|
AgentDomain string `json:"agent_domain,omitempty"`
|
|
AgentSecret string `json:"agent_secret,omitempty"`
|
|
AgentPort int `json:"agent_port,omitempty"`
|
|
NodeIPv4 string `json:"node_ipv4,omitempty"`
|
|
BirdBgpSourceIPv4 string `json:"bird_bgp_source_ipv4,omitempty"`
|
|
BirdBgpSourceIPv6 string `json:"bird_bgp_source_ipv6,omitempty"`
|
|
NodeEnrolledAt string `json:"node_enrolled_at,omitempty"`
|
|
LastDispatchAt string `json:"last_dispatch_at,omitempty"`
|
|
LastDispatchError string `json:"last_dispatch_error,omitempty"`
|
|
LastDispatchStatus string `json:"last_dispatch_status,omitempty"`
|
|
SyncStatus string `json:"sync_status,omitempty"`
|
|
FirewallFailover bool `json:"firewall_failover,omitempty"`
|
|
LastFirewallReplicateAt string `json:"last_firewall_replicate_at,omitempty"`
|
|
LastFirewallReplicateStatus string `json:"last_firewall_replicate_status,omitempty"`
|
|
LastFirewallReplicateError string `json:"last_firewall_replicate_error,omitempty"`
|
|
}
|
|
|
|
// ParseSpeakerMeta decodes meta_json object; unknown keys are ignored.
|
|
func ParseSpeakerMeta(metaJSON string) SpeakerMeta {
|
|
raw := strings.TrimSpace(metaJSON)
|
|
if raw == "" || raw == "{}" {
|
|
return SpeakerMeta{}
|
|
}
|
|
var m SpeakerMeta
|
|
_ = json.Unmarshal([]byte(raw), &m)
|
|
if m.AgentPort == 0 {
|
|
m.AgentPort = 8443
|
|
}
|
|
return m
|
|
}
|
|
|
|
// SpeakerMetaJSON marshals SpeakerMeta to a JSON object string.
|
|
func SpeakerMetaJSON(m SpeakerMeta) string {
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return "{}"
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// MergeSpeakerMetaJSON merges patch into existing meta_json string.
|
|
func MergeSpeakerMetaJSON(existing string, patch SpeakerMeta) string {
|
|
cur := ParseSpeakerMeta(existing)
|
|
if patch.AgentDomain != "" {
|
|
cur.AgentDomain = patch.AgentDomain
|
|
}
|
|
if patch.AgentSecret != "" {
|
|
cur.AgentSecret = patch.AgentSecret
|
|
}
|
|
if patch.AgentPort != 0 {
|
|
cur.AgentPort = patch.AgentPort
|
|
}
|
|
if patch.NodeIPv4 != "" {
|
|
cur.NodeIPv4 = patch.NodeIPv4
|
|
}
|
|
if patch.BirdBgpSourceIPv4 != "" {
|
|
cur.BirdBgpSourceIPv4 = patch.BirdBgpSourceIPv4
|
|
}
|
|
if patch.BirdBgpSourceIPv6 != "" {
|
|
cur.BirdBgpSourceIPv6 = patch.BirdBgpSourceIPv6
|
|
}
|
|
if patch.NodeEnrolledAt != "" {
|
|
cur.NodeEnrolledAt = patch.NodeEnrolledAt
|
|
}
|
|
if patch.LastDispatchAt != "" {
|
|
cur.LastDispatchAt = patch.LastDispatchAt
|
|
}
|
|
if patch.LastDispatchStatus == "ok" {
|
|
cur.LastDispatchError = ""
|
|
} else if patch.LastDispatchError != "" {
|
|
cur.LastDispatchError = patch.LastDispatchError
|
|
}
|
|
if patch.LastDispatchStatus != "" {
|
|
cur.LastDispatchStatus = patch.LastDispatchStatus
|
|
}
|
|
if patch.SyncStatus != "" {
|
|
cur.SyncStatus = patch.SyncStatus
|
|
}
|
|
if patch.FirewallFailover {
|
|
cur.FirewallFailover = true
|
|
}
|
|
if patch.LastFirewallReplicateAt != "" {
|
|
cur.LastFirewallReplicateAt = patch.LastFirewallReplicateAt
|
|
}
|
|
if patch.LastFirewallReplicateStatus == "ok" {
|
|
cur.LastFirewallReplicateError = ""
|
|
} else if patch.LastFirewallReplicateError != "" {
|
|
cur.LastFirewallReplicateError = patch.LastFirewallReplicateError
|
|
}
|
|
if patch.LastFirewallReplicateStatus != "" {
|
|
cur.LastFirewallReplicateStatus = patch.LastFirewallReplicateStatus
|
|
}
|
|
return SpeakerMetaJSON(cur)
|
|
}
|
|
|
|
// IPv4FromEndpoint extracts an IPv4 from endpoint URL host when present.
|
|
func IPv4FromEndpoint(endpoint string) string {
|
|
ep := strings.TrimSpace(endpoint)
|
|
if ep == "" {
|
|
return ""
|
|
}
|
|
if !strings.Contains(ep, "://") {
|
|
ep = "https://" + ep
|
|
}
|
|
u, err := url.Parse(ep)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
host := strings.TrimSpace(u.Hostname())
|
|
if host == "" {
|
|
return ""
|
|
}
|
|
if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {
|
|
return ip.String()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ValidIPv4 reports whether s is a dotted-quad IPv4 address.
|
|
func ValidIPv4(s string) bool {
|
|
ip := net.ParseIP(strings.TrimSpace(s))
|
|
return ip != nil && ip.To4() != nil
|
|
}
|
|
|
|
// AgentSyncURL returns HTTPS sync URL for a speaker with agent_domain configured.
|
|
func AgentSyncURL(meta SpeakerMeta) string {
|
|
domain := strings.TrimSpace(meta.AgentDomain)
|
|
if domain == "" {
|
|
return ""
|
|
}
|
|
return "https://" + strings.TrimSuffix(domain, "/") + "/v1/agent/sync"
|
|
}
|
|
|
|
// AgentHealthURL returns HTTPS health URL for agent_domain.
|
|
func AgentHealthURL(meta SpeakerMeta) string {
|
|
return agentHTTPSURL(meta, "/v1/agent/health")
|
|
}
|
|
|
|
// AgentBirdProtocolsURL returns HTTPS bird protocols URL for agent_domain.
|
|
func AgentBirdProtocolsURL(meta SpeakerMeta) string {
|
|
return agentHTTPSURL(meta, "/v1/agent/bird/protocols")
|
|
}
|
|
|
|
func agentHTTPSURL(meta SpeakerMeta, path string) string {
|
|
domain := strings.TrimSpace(meta.AgentDomain)
|
|
if domain == "" {
|
|
return ""
|
|
}
|
|
return "https://" + strings.TrimSuffix(domain, "/") + path
|
|
}
|
|
|
|
// SpeakerNeedsRemoteDispatch reports whether deploy_apply should wake this speaker via agent HTTP.
|
|
func SpeakerNeedsRemoteDispatch(role string, meta SpeakerMeta) bool {
|
|
r := strings.ToLower(strings.TrimSpace(role))
|
|
if r == "master" {
|
|
return false
|
|
}
|
|
return strings.TrimSpace(meta.AgentDomain) != "" && strings.TrimSpace(meta.AgentSecret) != ""
|
|
}
|