Skip to content

Commit

Permalink
Add first logic for parsing extended config
Browse files Browse the repository at this point in the history
  • Loading branch information
simbados committed Feb 12, 2024
1 parent 129e1e3 commit b22e48a
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 89 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Reimplementation of node-safe with capability to sandbox every script with custom settings
# Reimplementation of node-safe with capability to sandbox every script with custom profiles
- Reimplementation of node-safe (https://github.com/berstend/node-safe) in go
# Table of Content
1. [Status](#status)
Expand All @@ -15,6 +15,7 @@
- v0.1.0 coming soon

## TODOs
Sorted by priority
- [x] Glob support of ../
- [x] Add support for opening config files with sb and default editor
- [x] Implement init method
Expand All @@ -31,15 +32,18 @@
- [x] Print command that is run when passing args to sandbox-exec
- [x] Always apply __root-config__ of root config for binary and show in sb -s command
- [x] Merge local and root config
- [x] Vigilant mode, ask at the end to proceed with command and config
- [x] Possibility to add overall config json file to apply to all commands (discuss if good idea?) - Will be solved with extend keyword
- [ ] Extend other config file, e.g. commonNode.json could be applied to npm.json, npx.json
- [ ] Look for "__extend__" key while parsing. Limit to 2 parent configs
- [ ] Validate json config files, if anything can not be parsed return error (e.g. no array provided)
- [x] No array/bool provided
- [ ] Config keys duplicated (can lead to bug that only first config is applied, disallow double config keys)
- [ ] Extend README with complete instructions
- [ ] More testing for critical parts of the tool
- [ ] Add support for removing config files with sb
- [ ] Possibility to add overall config json file to apply to all commands (discuss if good idea?)
- [ ] Option for only applying root or local config ```-c local``` ```-c root```
- [ ] Add TLDR; for README
- [x] Vigilant mode, ask at the end to proceed with command and config
- [ ] Option for only applying root or local config ```-c local -c root```
- [ ] More testing for critical parts of the tool

## Installation
**Building from Source**
Expand Down
2 changes: 1 addition & 1 deletion cmd/sb/sb.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func checkCliOptions(context *types.Context, commands []string) {
} else if currentOption == "--init" || currentOption == "-i" {
util.Init(&context.Paths)
} else if currentOption == "--edit" || currentOption == "-e" {
util.EditFile(commands, context.Paths)
util.EditFile(commands, &context.Paths)
} else if currentOption == "--show" || currentOption == "-s" {
if len(commands) != 1 {
log.LogErr(`
Expand Down
1 change: 1 addition & 0 deletions internal/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const ConfigRepo = "/.sb-config"
const LocalConfigPath = "/.sb-config"

const RootConfigKey = "__root-config__"
const ExtendsConfigKey = "__extends__"

const DEV_MODE = "DEV_MODE"

Expand Down
2 changes: 1 addition & 1 deletion internal/util/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"sb/internal/types"
)

func EditFile(commands []string, paths types.Paths) {
func EditFile(commands []string, paths *types.Paths) {
if len(commands) != 2 {
showError()
}
Expand Down
3 changes: 2 additions & 1 deletion internal/util/expandPaths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ func TestExpandDotsError(t *testing.T) {
{"[wd]/../../../../hello", "(literal \"/Users/test/hello\")"},
{"../../../../hello", "(literal \"/Users/test/hello\")"},
{"what/is/this../../../../", "(literal \"/Users/test/hello\")"},
{"what/is/this../../../../", "(literal \"/Users/test/hello\")"},
{"./what/is/this", "(literal \"/Users/test/hello\")"},
}

paths := types.Paths{LocalConfigPath: "Users/test/sb", HomePath: "/Users/test", RootConfigPath: "Users/test", WorkingDir: "/Users/test/sb", BinPath: "/usr/bin", BinaryPath: "/usr/bin/ls"}
Expand All @@ -33,7 +35,6 @@ func TestExpandPathSuccess(t *testing.T) {
{"[home]/whatsup", "(literal \"/Users/test/whatsup\")"},
{"[bin]/whatsup", "(literal \"/usr/bin/whatsup\")"},
{"[wd]/whatsup", "(literal \"/Users/test/sb/whatsup\")"},
{"./whatsup", "(literal \"/Users/test/sb/whatsup\")"},
{"/Users/test/**", "(regex #\"/Users/test/(.+)\")"},
{"/Users/**/test", "(regex #\"/Users/(.+\\/)?test\")"},
{"/Users/test/*.js", "(regex #\"/Users/test/[^\\/]*\\.js\")"},
Expand Down
64 changes: 0 additions & 64 deletions internal/util/parseCliOptions.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package util

import (
"errors"
"path/filepath"
"regexp"
"sb/internal/log"
"sb/internal/types"
"strings"
Expand Down Expand Up @@ -101,64 +98,3 @@ func parseCliConfigParam(s string) (string, string) {
}
return split[0], ""
}

const dotReplace = "\\."
const dotRegex = `\.`
const globEndRegex = `\*\*$`
const globEndReplace = `(.+)`
const globMiddleRegex = `\*\*\/`
const globMiddleReplace = `(.+\/)?`
const globSingleRegex = `\*`

const globSingleReplace = `[^\/]*`

func buildSymbolToPathMatching(paths *types.Paths) map[string]string {
return map[string]string{
"~": paths.HomePath,
"[home]": paths.HomePath,
"[wd]": paths.WorkingDir,
"[bin]": paths.BinPath,
"[target]": paths.BinaryPath,
}
}

func expandPaths(paths *types.Paths, value string) (string, error) {
initialPath := value
matching := buildSymbolToPathMatching(paths)
for key, path := range matching {
value = strings.ReplaceAll(value, key, path)
}
if strings.HasPrefix(value, "../") {
value = paths.HomePath + "/" + value
}
if strings.Contains(value, "../") {
splits := strings.Split(value, "/")[1:]
for index := 0; index < len(splits); index++ {
if splits[index] == ".." {
if index+1 > len(splits) || index-1 < 0 {
return "", errors.New("Can not resolve path of: " + initialPath + " in one of your config files or cli args")
}
splits = append(splits[0:index-1], splits[index+1:]...)
index = -1
}
}
value = "/" + filepath.Join(splits...)
}
if strings.HasPrefix(value, "./") {
value = strings.ReplaceAll(value, "./", paths.WorkingDir+"/")
}
if strings.Contains(value, "*") || strings.Contains(value, ".") {
value = regexp.MustCompile(dotRegex).ReplaceAllString(value, dotReplace)
if val, err := regexp.Compile(globEndRegex); err == nil && val.MatchString(value) {
value = val.ReplaceAllString(value, globEndReplace)
} else if val, err := regexp.Compile(globMiddleRegex); err == nil && val.MatchString(value) {
value = val.ReplaceAllString(value, globMiddleReplace)
} else if val, err := regexp.Compile(globSingleRegex); err == nil && val.MatchString(value) {
value = val.ReplaceAllString(value, globSingleReplace)
}
value = "(regex #\"" + value + "\")"
} else {
value = "(literal \"" + value + "\")"
}
return value, nil
}
55 changes: 39 additions & 16 deletions internal/util/parseConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ func LocalConfigPath(paths *types.Paths, binaryName string) (string, bool) {
func ConfigFileParsing(context *types.Context) *types.SbConfig {
localConfig := extractLocalConfig(context)
rootConfig := extractRootConfig(context)
return mergeConfig(localConfig, rootConfig)
return mergeConfigs(localConfig, rootConfig)
}

func extractLocalConfig(context *types.Context) *types.SbConfig {
localConfigPath, localConfigExists := LocalConfigPath(&context.Paths, context.Config.BinaryName)
if localConfigExists {
context.Paths.LocalConfigPath = localConfigPath
localConfig := parseJsonConfig(&context.Paths, localConfigPath, context.Config.Commands)
localConfig := parseJsonConfig(&context.Paths, localConfigPath, context.Config.Commands, 1)
log.LogDebug("Using local config file at path ", localConfigPath)
return localConfig
} else {
Expand All @@ -72,7 +72,7 @@ func extractLocalConfig(context *types.Context) *types.SbConfig {
func extractRootConfig(context *types.Context) *types.SbConfig {
binaryGlobalConfigPath, exists := doesRootConfigExists(context)
if exists {
globalConfig := parseJsonConfig(&context.Paths, binaryGlobalConfigPath, context.Config.Commands)
globalConfig := parseJsonConfig(&context.Paths, binaryGlobalConfigPath, context.Config.Commands, 1)
return globalConfig
}
return nil
Expand All @@ -95,11 +95,14 @@ func doesRootConfigExists(context *types.Context) (string, bool) {
return "", false
}

func parseJsonConfig(paths *types.Paths, path string, commands []string) *types.SbConfig {
func parseJsonConfig(paths *types.Paths, path string, commands []string, depth int) *types.SbConfig {
if depth > 3 {
log.LogErr("Nesting of the json config is only allowed for a depth of 2")
}
mapping := buildCommandMap(commands)
mapping[types.RootConfigKey] = true
var configs []*types.SbConfig
configJson := ParseJson(path)
configs := parseExtendedConfig(paths, path, commands, depth, configJson)
for key, val := range configJson {
var command string
if strings.Contains(key, "*") {
Expand All @@ -108,15 +111,36 @@ func parseJsonConfig(paths *types.Paths, path string, commands []string) *types.
command = key
}
if exists := mapping[command]; exists {
configs = parseOptionsForCommand(paths, path, val, configs)
configs = append(configs, parseOptionsForCommand(paths, path, val))
} else {
log.LogDev(fmt.Sprintf("No config found for key: %v in path %v", key, path))
}
}
if len(configs) == 0 {
log.LogErr(fmt.Sprintf("You have a config file at path %v, but no keys were found", path))
}
return mergeConfig(configs...)
return mergeConfigs(configs...)
}

func parseExtendedConfig(paths *types.Paths, path string, commands []string, depth int, configJson map[string]interface{}) []*types.SbConfig {
var configs []*types.SbConfig
if value, exists := configJson[types.ExtendsConfigKey]; exists {
if extendPath, isString := value.(string); isString {
exists, err := DoesPathExist(extendPath)
if err != nil {
log.LogErr(err)
}
if exists {
log.LogDebug("Extending config with config of path: ", extendPath)
configs = append(configs, parseJsonConfig(paths, extendPath, commands, depth+1))
} else {
log.LogWarn("Path which was provided for extending the config does not exists: ", path)
}
} else {
log.LogWarn(types.ExtendsConfigKey, " key is not a string at path", extendPath)
}
}
return configs
}

func buildCommandMap(commands []string) map[string]bool {
Expand All @@ -134,18 +158,17 @@ func buildCommandMap(commands []string) map[string]bool {
return mapping
}

func parseOptionsForCommand(paths *types.Paths, path string, val interface{}, configs []*types.SbConfig) []*types.SbConfig {
permissions, err := parseNextJsonLevel(val)
if err {
func parseOptionsForCommand(paths *types.Paths, path string, val interface{}) *types.SbConfig {
permissions, valid := parseNextJsonLevel(val)
if !valid {
log.LogErr("Malformed root config json, please check your config at path: ", path)
}
configs = append(configs, parseConfigIntoStruct(paths, permissions, path))
return configs
return parseConfigIntoStruct(paths, permissions, path)
}

func parseNextJsonLevel(config interface{}) (map[string]interface{}, bool) {
rootConf, ok := config.(map[string]interface{})
return rootConf, !ok
return rootConf, ok
}

func parseConfigIntoStruct(paths *types.Paths, binaryConfig map[string]interface{}, path string) *types.SbConfig {
Expand Down Expand Up @@ -175,14 +198,14 @@ func parseConfigIntoStruct(paths *types.Paths, binaryConfig map[string]interface
sbConfig.Process = parseIfExists(paths, process, path, "process")
}
if netOutExists {
if value, exists := netOut.(bool); exists {
if value, valid := netOut.(bool); valid {
sbConfig.NetworkOutbound = &types.BoolOrNil{Value: value}
} else {
log.LogErr(fmt.Sprintf("Your net-out config at path %v, is not a boolean value", path))
}
}
if netInExists {
if value, exists := netIn.(bool); exists {
if value, valid := netIn.(bool); valid {
sbConfig.NetworkInbound = &types.BoolOrNil{Value: value}
} else {
log.LogErr(fmt.Sprintf("Your net-in config at path %v, is not a boolean value", path))
Expand Down Expand Up @@ -216,7 +239,7 @@ func convertJsonArrayToStringArray(paths *types.Paths, jsonArray []interface{})
return valueStrings
}

func mergeConfig(configToMerge ...*types.SbConfig) *types.SbConfig {
func mergeConfigs(configToMerge ...*types.SbConfig) *types.SbConfig {
newConfig := &types.SbConfig{}
for _, config := range configToMerge {
if config != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/util/parseRootBinaryConfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func TestRootConfig(t *testing.T) {
var commands = []string{"run", "some"}
paths := types.Paths{LocalConfigPath: "Users/test/sb", HomePath: "/Users/test", RootConfigPath: "Users/test", WorkingDir: "/Users/test/sb", BinPath: "/usr/bin", BinaryPath: "/usr/bin/ls"}
config := parseJsonConfig(&paths, "./test.json", commands)
config := parseJsonConfig(&paths, "./test.json", commands, 1)
if len(config.Write) != 2 {
t.Errorf("parseJsonConfig should have 2 entries for write but was %v", config.Write)
}
Expand Down
69 changes: 69 additions & 0 deletions internal/util/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package util

import (
"errors"
"path/filepath"
"regexp"
"sb/internal/types"
"strings"
)

const dotReplace = "\\."
const dotRegex = `\.`
const globEndRegex = `\*\*$`
const globEndReplace = `(.+)`
const globMiddleRegex = `\*\*\/`
const globMiddleReplace = `(.+\/)?`
const globSingleRegex = `\*`

const globSingleReplace = `[^\/]*`

func buildSymbolToPathMatching(paths *types.Paths) map[string]string {
return map[string]string{
"~": paths.HomePath,
"[home]": paths.HomePath,
"[wd]": paths.WorkingDir,
"[bin]": paths.BinPath,
"[target]": paths.BinaryPath,
"[local]": paths.LocalConfigPath,
"[root]": paths.RootConfigPath,
}
}

func expandPaths(paths *types.Paths, value string) (string, error) {
initialPath := value
matching := buildSymbolToPathMatching(paths)
for key, path := range matching {
value = strings.ReplaceAll(value, key, path)
}
if strings.HasPrefix(value, "../") || strings.HasPrefix(value, "./") {
return "", errors.New("no relative path allowed in config, use an identifier like [wd] or [home], for more information consult the documentation")
}
if strings.Contains(value, "../") {
splits := strings.Split(value, "/")[1:]
for index := 0; index < len(splits); index++ {
if splits[index] == ".." {
if index+1 > len(splits) || index-1 < 0 {
return "", errors.New("Can not resolve path of: " + initialPath + " in one of your config files or cli args")
}
splits = append(splits[0:index-1], splits[index+1:]...)
index = -1
}
}
value = "/" + filepath.Join(splits...)
}
if strings.Contains(value, "*") || strings.Contains(value, ".") {
value = regexp.MustCompile(dotRegex).ReplaceAllString(value, dotReplace)
if val, err := regexp.Compile(globEndRegex); err == nil && val.MatchString(value) {
value = val.ReplaceAllString(value, globEndReplace)
} else if val, err := regexp.Compile(globMiddleRegex); err == nil && val.MatchString(value) {
value = val.ReplaceAllString(value, globMiddleReplace)
} else if val, err := regexp.Compile(globSingleRegex); err == nil && val.MatchString(value) {
value = val.ReplaceAllString(value, globSingleReplace)
}
value = "(regex #\"" + value + "\")"
} else {
value = "(literal \"" + value + "\")"
}
return value, nil
}

0 comments on commit b22e48a

Please sign in to comment.