Files
elmprodvpn/selective-vpn-api/app/transport_policy_ownership.go

104 lines
3.0 KiB
Go

package app
import (
"fmt"
"sort"
"strings"
"time"
)
func detectTransportOwnerSwitchConflicts(current, next []TransportPolicyIntent) []TransportConflictRecord {
curOwners := map[string]string{}
for _, raw := range current {
norm, key, _, err := normalizeTransportIntent(raw)
if err != nil || key == "" || strings.TrimSpace(norm.ClientID) == "" {
continue
}
curOwners[key] = norm.ClientID
}
if len(curOwners) == 0 {
return nil
}
conflicts := make([]TransportConflictRecord, 0, 4)
for _, raw := range next {
norm, key, _, err := normalizeTransportIntent(raw)
if err != nil || key == "" || strings.TrimSpace(norm.ClientID) == "" {
continue
}
prevOwner, exists := curOwners[key]
if !exists || prevOwner == norm.ClientID {
continue
}
conflicts = append(conflicts, TransportConflictRecord{
Key: key,
Type: "owner_switch",
Severity: "block",
Owners: []string{prevOwner, norm.ClientID},
Reason: fmt.Sprintf(
"selector owner switch %s -> %s requires explicit override",
prevOwner,
norm.ClientID,
),
SuggestedResolution: "use force_override + confirm token to switch owner",
})
}
return dedupeTransportConflicts(conflicts)
}
func transportOwnershipNeedsRebuild(policyRevision int64, owners TransportOwnershipState, planDigest string) bool {
if owners.PolicyRevision != policyRevision {
return true
}
expected := strings.TrimSpace(planDigest)
if expected == "" {
return false
}
if strings.TrimSpace(owners.PlanDigest) != expected {
return true
}
return false
}
func buildTransportOwnershipStateFromPlan(plan TransportPolicyCompilePlan, policyRevision int64) TransportOwnershipState {
now := time.Now().UTC().Format(time.RFC3339)
items := make([]TransportOwnershipRecord, 0, plan.RuleCount)
seen := map[string]struct{}{}
for _, iface := range plan.Interfaces {
for _, rule := range iface.Rules {
key := strings.TrimSpace(rule.SelectorType) + ":" + strings.TrimSpace(rule.SelectorValue)
if key == ":" {
continue
}
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
items = append(items, TransportOwnershipRecord{
Key: key,
SelectorType: strings.TrimSpace(rule.SelectorType),
SelectorValue: strings.TrimSpace(rule.SelectorValue),
ClientID: strings.TrimSpace(rule.ClientID),
ClientKind: strings.TrimSpace(rule.ClientKind),
OwnerScope: strings.TrimSpace(rule.OwnerScope),
IfaceID: strings.TrimSpace(iface.IfaceID),
RoutingTable: strings.TrimSpace(iface.RoutingTable),
MarkHex: strings.TrimSpace(rule.MarkHex),
PriorityBase: rule.PriorityBase,
Mode: strings.TrimSpace(rule.Mode),
Priority: rule.Priority,
UpdatedAt: now,
})
}
}
sort.Slice(items, func(i, j int) bool { return items[i].Key < items[j].Key })
return TransportOwnershipState{
Version: transportStateVersion,
UpdatedAt: now,
PolicyRevision: policyRevision,
PlanDigest: digestTransportPolicyCompilePlan(plan),
Count: len(items),
Items: items,
}
}