add support for rulesets in JSON format for frontend use
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"ladder/proxychain"
|
"ladder/proxychain"
|
||||||
rx "ladder/proxychain/requestmodifiers"
|
rx "ladder/proxychain/requestmodifiers"
|
||||||
tx "ladder/proxychain/responsemodifiers"
|
tx "ladder/proxychain/responsemodifiers"
|
||||||
@@ -61,12 +60,7 @@ func NewProxySiteHandler(opts *ProxyOptions) fiber.Handler {
|
|||||||
|
|
||||||
// load ruleset
|
// load ruleset
|
||||||
rule, exists := rs.GetRule(proxychain.Request.URL)
|
rule, exists := rs.GetRule(proxychain.Request.URL)
|
||||||
fmt.Println("============")
|
|
||||||
fmt.Println(proxychain.Request.URL)
|
|
||||||
fmt.Println(rs)
|
|
||||||
fmt.Println("============")
|
|
||||||
if exists {
|
if exists {
|
||||||
fmt.Println("===========EXISTS=")
|
|
||||||
proxychain.AddOnceRequestModifications(rule.RequestModifications...)
|
proxychain.AddOnceRequestModifications(rule.RequestModifications...)
|
||||||
proxychain.AddOnceResponseModifications(rule.ResponseModifications...)
|
proxychain.AddOnceResponseModifications(rule.ResponseModifications...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func HandleRulesetMerge(rulesetPath string, mergeRulesets bool, output *os.File)
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - An error if YAML conversion or file writing fails, otherwise nil.
|
// - An error if YAML conversion or file writing fails, otherwise nil.
|
||||||
func yamlMerge(rs ruleset_v2.Ruleset, output io.Writer) error {
|
func yamlMerge(rs ruleset_v2.Ruleset, output io.Writer) error {
|
||||||
yaml, err := rs.Yaml()
|
yaml, err := rs.YAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ruleset_v2
|
package ruleset_v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -134,5 +135,9 @@ func (rule *Rule) MarshalYAML() (interface{}, error) {
|
|||||||
ResponseModifications: rule._rsms,
|
ResponseModifications: rule._rsms,
|
||||||
}
|
}
|
||||||
|
|
||||||
return yaml.Marshal(aux)
|
var b bytes.Buffer
|
||||||
|
y := yaml.NewEncoder(&b)
|
||||||
|
y.SetIndent(2)
|
||||||
|
err := y.Encode(aux)
|
||||||
|
return b.String(), err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ruleset_v2
|
package ruleset_v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,13 +13,14 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
//"encoding/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IRuleset interface {
|
type IRuleset interface {
|
||||||
HasRule(url url.URL) bool
|
HasRule(url url.URL) bool
|
||||||
GetRule(url url.URL) (rule Rule, exists bool)
|
GetRule(url url.URL) (rule Rule, exists bool)
|
||||||
|
YAML() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ruleset struct {
|
type Ruleset struct {
|
||||||
@@ -63,6 +65,7 @@ func (rs *Ruleset) MarshalYAML() (interface{}, error) {
|
|||||||
RequestModifications []_rqm `yaml:"requestmodifications"`
|
RequestModifications []_rqm `yaml:"requestmodifications"`
|
||||||
ResponseModifications []_rsm `yaml:"responsemodifications"`
|
ResponseModifications []_rsm `yaml:"responsemodifications"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Aux struct {
|
type Aux struct {
|
||||||
Rules []AuxRule `yaml:"rules"`
|
Rules []AuxRule `yaml:"rules"`
|
||||||
}
|
}
|
||||||
@@ -78,10 +81,68 @@ func (rs *Ruleset) MarshalYAML() (interface{}, error) {
|
|||||||
aux.Rules = append(aux.Rules, auxRule)
|
aux.Rules = append(aux.Rules, auxRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := yaml.Marshal(&aux)
|
var b bytes.Buffer
|
||||||
return out, err
|
y := yaml.NewEncoder(&b)
|
||||||
|
y.SetIndent(2)
|
||||||
|
err := y.Encode(&aux)
|
||||||
|
|
||||||
|
return b.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
func (rs *Ruleset) UnmarshalJSON(data []byte) error {
|
||||||
|
type AuxRuleset struct {
|
||||||
|
Rules []Rule `json:"rules"`
|
||||||
|
}
|
||||||
|
ar := &AuxRuleset{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, ar); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rs._rulemap = make(map[string]*Rule)
|
||||||
|
rs.Rules = ar.Rules
|
||||||
|
|
||||||
|
for i, rule := range rs.Rules {
|
||||||
|
rulePtr := &rs.Rules[i]
|
||||||
|
for _, domain := range rule.Domains {
|
||||||
|
rs._rulemap[domain] = rulePtr
|
||||||
|
if !strings.HasPrefix(domain, "www.") {
|
||||||
|
rs._rulemap["www."+domain] = rulePtr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Ruleset) MarshalJSON() ([]byte, error) {
|
||||||
|
type AuxRule struct {
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
RequestModifications []_rqm `json:"requestmodifications"`
|
||||||
|
ResponseModifications []_rsm `json:"responsemodifications"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Aux struct {
|
||||||
|
Rules []AuxRule `json:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
aux := Aux{}
|
||||||
|
for _, rule := range rs.Rules {
|
||||||
|
auxRule := AuxRule{
|
||||||
|
Domains: rule.Domains,
|
||||||
|
RequestModifications: rule._rqms,
|
||||||
|
ResponseModifications: rule._rsms,
|
||||||
|
}
|
||||||
|
aux.Rules = append(aux.Rules, auxRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(aux)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================
|
||||||
|
|
||||||
func (rs Ruleset) GetRule(url *url.URL) (rule *Rule, exists bool) {
|
func (rs Ruleset) GetRule(url *url.URL) (rule *Rule, exists bool) {
|
||||||
rule, exists = rs._rulemap[url.Hostname()]
|
rule, exists = rs._rulemap[url.Hostname()]
|
||||||
return rule, exists
|
return rule, exists
|
||||||
@@ -165,6 +226,9 @@ func (rs *Ruleset) loadRulesFromLocalDir(path string) error {
|
|||||||
|
|
||||||
// create a map of pointers to rules loaded above based on domain string keys
|
// create a map of pointers to rules loaded above based on domain string keys
|
||||||
// this way we don't have two copies of the rule in ruleset
|
// this way we don't have two copies of the rule in ruleset
|
||||||
|
if rs._rulemap == nil {
|
||||||
|
rs._rulemap = make(map[string]*Rule)
|
||||||
|
}
|
||||||
for i, rule := range rs.Rules {
|
for i, rule := range rs.Rules {
|
||||||
rulePtr := &rs.Rules[i]
|
rulePtr := &rs.Rules[i]
|
||||||
for _, domain := range rule.Domains {
|
for _, domain := range rule.Domains {
|
||||||
@@ -185,17 +249,22 @@ func (rs *Ruleset) loadRulesFromLocalDir(path string) error {
|
|||||||
// loadRulesFromLocalFile loads rules from a local YAML file specified by the path.
|
// loadRulesFromLocalFile loads rules from a local YAML file specified by the path.
|
||||||
// Returns an error if the file cannot be read or if there's a syntax error in the YAML.
|
// Returns an error if the file cannot be read or if there's a syntax error in the YAML.
|
||||||
func (rs *Ruleset) loadRulesFromLocalFile(path string) error {
|
func (rs *Ruleset) loadRulesFromLocalFile(path string) error {
|
||||||
yamlFile, err := os.ReadFile(path)
|
file, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := fmt.Errorf("failed to read rules from local file: '%s'", path)
|
e := fmt.Errorf("failed to read rules from local file: '%s'", path)
|
||||||
return errors.Join(e, err)
|
return errors.Join(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.Unmarshal(yamlFile, rs)
|
isJSON := strings.HasSuffix(path, ".json")
|
||||||
|
if isJSON {
|
||||||
|
err = json.Unmarshal(file, rs)
|
||||||
|
} else {
|
||||||
|
err = yaml.Unmarshal(file, rs)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := fmt.Errorf("failed to load rules from local file, possible syntax error in '%s' - %s", path, err)
|
e := fmt.Errorf("failed to load rules from local file, possible syntax error in '%s' - %s", path, err)
|
||||||
debugPrintRule(string(yamlFile), e)
|
debugPrintRule(string(file), e)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +301,12 @@ func (rs *Ruleset) loadRulesFromRemoteFile(rulesURL string) error {
|
|||||||
reader = resp.Body
|
reader = resp.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isJSON := strings.HasSuffix(rulesURL, ".json") || resp.Header.Get("content-type") == "application/json"
|
||||||
|
if isJSON {
|
||||||
|
err = json.NewDecoder(reader).Decode(&rs)
|
||||||
|
} else {
|
||||||
err = yaml.NewDecoder(reader).Decode(&rs)
|
err = yaml.NewDecoder(reader).Decode(&rs)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load rules from remote url '%s' with status code '%s' and possible syntax error - %s", rulesURL, resp.Status, err)
|
return fmt.Errorf("failed to load rules from remote url '%s' with status code '%s' and possible syntax error - %s", rulesURL, resp.Status, err)
|
||||||
@@ -243,14 +317,29 @@ func (rs *Ruleset) loadRulesFromRemoteFile(rulesURL string) error {
|
|||||||
|
|
||||||
// ================= utility methods ==========================
|
// ================= utility methods ==========================
|
||||||
|
|
||||||
// Yaml returns the ruleset as a Yaml string
|
// YAML returns the ruleset as a Yaml string
|
||||||
func (rs *Ruleset) Yaml() (string, error) {
|
func (rs *Ruleset) YAML() (string, error) {
|
||||||
y, err := yaml.Marshal(rs)
|
y, err := yaml.Marshal(rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(y), nil
|
// for some reason, MarshalYAML seems to turn everything into a string block
|
||||||
|
// this is a workaround. Don't call yaml.Marshal directly, instead call this helper method.
|
||||||
|
x := strings.ReplaceAll(string(y), "\n ", "\n")
|
||||||
|
x = strings.Replace(x, "|\n", "", 1)
|
||||||
|
fmt.Println(x)
|
||||||
|
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAML returns the ruleset as a JSON string
|
||||||
|
func (rs *Ruleset) JSON() (string, error) {
|
||||||
|
j, err := json.Marshal(rs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(j), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domains extracts and returns a slice of all domains present in the RuleSet.
|
// Domains extracts and returns a slice of all domains present in the RuleSet.
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
package ruleset_v2
|
package ruleset_v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
//"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
//"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validYAML = `
|
validYAML = `rules:
|
||||||
rules:
|
|
||||||
- domains:
|
- domains:
|
||||||
- example.com
|
- example.com
|
||||||
- www.example.com
|
- www.example.com
|
||||||
@@ -51,6 +55,32 @@ rules:
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestYAMLUnmarshal(t *testing.T) {
|
||||||
|
rs, err := loadRuleFromString(validYAML)
|
||||||
|
fmt.Println(validYAML)
|
||||||
|
assert.NoError(t, err, "expected no error loading valid yml")
|
||||||
|
yml, err := rs.YAML()
|
||||||
|
assert.NoError(t, err, "expected no error marshalling ruleset")
|
||||||
|
|
||||||
|
rs2, err := loadRuleFromString(yml)
|
||||||
|
assert.NoError(t, err, "expected no error loading yaml marshalled -> unmarshalled -> marshalled ruleset")
|
||||||
|
assert.Equal(t, 1, rs2.Count(), "expected one rule to be returned after marshalled -> unmarshalled -> marshalled ruleset")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONUnmarshal(t *testing.T) {
|
||||||
|
rs, err := loadRuleFromString(validYAML)
|
||||||
|
assert.NoError(t, err, "expected no error loading valid yml")
|
||||||
|
j, err := json.Marshal(&rs)
|
||||||
|
assert.NoError(t, err, "expected no error marshalling ruleset to json")
|
||||||
|
|
||||||
|
fmt.Println(string(j))
|
||||||
|
|
||||||
|
rs2, err := loadRuleFromString(string(j))
|
||||||
|
assert.NoError(t, err, "expected no error loading JSON marshalled -> unmarshalled -> marshalled ruleset")
|
||||||
|
|
||||||
|
assert.Equal(t, 1, rs2.Count(), "expected one rule to be returned after JSON marshalled -> unmarshalled -> marshalled ruleset")
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadRulesFromRemoteFile(t *testing.T) {
|
func TestLoadRulesFromRemoteFile(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
defer app.Shutdown()
|
defer app.Shutdown()
|
||||||
@@ -103,13 +133,18 @@ func TestLoadRulesFromRemoteFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadRuleFromString(yaml string) (Ruleset, error) {
|
func loadRuleFromString(yamlOrJSON string) (Ruleset, error) {
|
||||||
// Create a temporary file and load it
|
// Create a temporary file and load it
|
||||||
tmpFile, _ := os.CreateTemp("", "ruleset*.yaml")
|
var tmpFile *os.File
|
||||||
|
if strings.HasPrefix(yamlOrJSON, "{") {
|
||||||
|
tmpFile, _ = os.CreateTemp("", "ruleset*.json")
|
||||||
|
} else {
|
||||||
|
tmpFile, _ = os.CreateTemp("", "ruleset*.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
defer os.Remove(tmpFile.Name())
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
tmpFile.WriteString(yaml)
|
tmpFile.WriteString(yamlOrJSON)
|
||||||
|
|
||||||
rs := Ruleset{
|
rs := Ruleset{
|
||||||
_rulemap: map[string]*Rule{},
|
_rulemap: map[string]*Rule{},
|
||||||
|
|||||||
Reference in New Issue
Block a user