From e71ff601d7b2432f80d5bd0810220476f04e9d5f Mon Sep 17 00:00:00 2001 From: Farhan Khan Date: Thu, 2 Apr 2026 01:53:56 -0400 Subject: [PATCH] initial commit --- go.mod | 3 + policy-allow.go | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ structures.go | 25 +++++++ 3 files changed, 220 insertions(+) create mode 100644 go.mod create mode 100644 policy-allow.go create mode 100644 structures.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9bb7935 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module libpolicy + +go 1.25.0 diff --git a/policy-allow.go b/policy-allow.go new file mode 100644 index 0000000..37f1a2c --- /dev/null +++ b/policy-allow.go @@ -0,0 +1,192 @@ +package libpolicy + +import ( + "log" + "net" + "net/netip" + "strings" +) + +func contains(slice []string, val string) bool { + for _, v := range slice { + if v == val { + return true + } + } + return false +} + +type Effect uint8 + +const ( + Allow Effect = iota + Deny + NoMatch +) + +func IsIPInCIDR(ipStr, cidrStr string) bool { + // Parse the IP address + cidrStr = normalizeCIDR(cidrStr) + log.Println("Parsing IP:", ipStr, " against CIDR:", cidrStr) + ip := net.ParseIP(ipStr) + if ip == nil { + return false // Invalid IP + } + + // Parse the CIDR + _, ipnet, err := net.ParseCIDR(cidrStr) + if err != nil { + return false // Invalid CIDR + } + + // net.IPNet.Contains() handles IPv4/IPv6 correctly, including IPv4-mapped IPv6 + return ipnet.Contains(ip) +} + +func normalizeCIDR(cidrStr string) string { + cidrStr = strings.TrimSpace(cidrStr) + if cidrStr == "" { + return "" + } + + // Already has a mask suffix → use as-is + if strings.Contains(cidrStr, "/") { + return cidrStr + } + + // Try to parse as IP address to determine version + addr, err := netip.ParseAddr(cidrStr) + if err != nil { + return "" // Invalid + } + + if addr.Is4() || addr.Is4In6() { + return cidrStr + "/32" + } + return cidrStr + "/128" +} + +func verifyRequest(request *Request, policy *Policy) (Effect, string) { + //conditionReview = []Condition{} + var effect Effect + effect = NoMatch + statementid := "NoMatch" + + for _, condition := range policy.Conditions { + + log.Println("Checking Statement: ", condition.StatementID) + + // Check the principal + if !contains(condition.Principal, request.Principal) { + log.Println("No match on principal") + log.Println("Checked condition principal:", condition.Principal) + log.Println("Against: request principal:", request.Principal) + continue + } + + if !contains(condition.Actions, request.Action) { + log.Println("No match on actions") + log.Println("Checked condition actions:", condition.Actions) + log.Println("Against: request action:", request.Action) + continue + } + + // Source Check + sourceMatch := false + for _, source := range condition.Source { + if IsIPInCIDR(request.SourceIPAddress, source) { + sourceMatch = true + } + } + + if sourceMatch == false { + log.Println("No match on source") + log.Println("Checked condition source:", condition.Source) + log.Println("Against: request source:", request.SourceIPAddress) + continue + } + + if condition.Effect == "allow" { + effect = Allow + log.Println("Allowed per ") + log.Printf("Request is allowed by condition: %s\n", condition.StatementID) + effect = Allow + statementid = condition.StatementID + } else if condition.Effect == "deny" { + // Immediately deny upon explicit deny + log.Printf("Request is denied by condition: %s\n", condition.StatementID) + return Deny, condition.StatementID + } + + } + + return effect, statementid +} + +/* +func loadRequestFile(filename string) (*Request, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open '%s' %w", filename, err) + } + defer file.Close() + + var request Request + if err := json.NewDecoder(file).Decode(&request); err != nil { + return nil, fmt.Errorf("failed to parse json from '%s': %w", filename, err) + } + + fmt.Printf("Successfully loaded request from %s\n", filename) + return &request, nil +} + +func loadPolicyFile(filename string) (*Policy, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file '%s': %w", filename, err) + } + defer file.Close() + + var policy Policy + if err := json.NewDecoder(file).Decode(&policy); err != nil { + return nil, fmt.Errorf("failed to parse JSON from '%s': %w", filename, err) + } + + fmt.Printf("Successfully loaded policy from %s\n", filename) + return &policy, nil +} +*/ +/* +func main() { + policy, err := loadPolicyFile("policy.json") + if err != nil { + fmt.Println("Error: %v", err) + os.Exit(1) + } + fmt.Println("Policy structure loaded successfully:") + pretty, _ := json.MarshalIndent(policy, "", "\t") + fmt.Println(string(pretty)) + + request, err := loadRequestFile("request.json") + if err != nil { + fmt.Println("Error: %v\n", err) + os.Exit(1) + } + fmt.Println("Loaded Request successfully\n") + pretty, _ = json.MarshalIndent(request, "", "\t") + fmt.Println(string(pretty)) + + effect, statementid := verifyRequest(request, policy) + + fmt.Println("") + if effect == NoMatch { + fmt.Println("No matching conditions found. Defaulting to Deny.") + } else if effect == Allow { + fmt.Println("Request is allowed based on matching conditions.") + } else { + fmt.Println("Request is denied based on matching conditions.") + } + println("statementid:", statementid) + +} +*/ diff --git a/structures.go b/structures.go new file mode 100644 index 0000000..f9a025d --- /dev/null +++ b/structures.go @@ -0,0 +1,25 @@ +package libpolicy + +// Policy represents the top-level structure of your custom policy.json +type Policy struct { + Name string `json:"name"` + Description string `json:"description"` + Conditions []Condition `json:"conditions"` +} + +// Condition represents each item in the "conditions" array +type Condition struct { + StatementID string `json:"statementid"` + Principal []string `json:"principals"` + Actions []string `json:"actions"` + Source []string `json:"source"` + Effect string `json:"effect"` + Operator string `json:"operator"` +} + +type Request struct { + Principal string `json:"principal"` + SourceIPAddress string `json:"source_ipaddress"` + Action string `json:"action"` + Target string `json:"target"` +}