Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ vendor/
vendor/
go.work
/cmd/sandworm/sandworm
/sandworm.db
/sandworm.db-shm
/sandworm.db-wal
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,49 @@ Allow mode (default) — only the listed domains/CIDRs are reachable:
sandworm --port 2137 --domains "github.com,golang.org" --cidrs "10.0.0.0/8"
```

## Simple Token Translation Workflow

Start `sandworm` with SQLite-backed token translation enabled:

```bash
sandworm \
--port 2137 \
--domains "api.openai.com,api.github.com,github.com" \
--translate-tokens
```

Add bearer-token mappings for APIs that should receive translated secrets:

```bash
sandworm token add bearer --host api.openai.com \
--path /v1/ \
--secret "$OPENAI_API_KEY" \
--placeholder OPENAI_PLACEHOLDER

sandworm token add bearer --host api.github.com \
--secret "$GITHUB_TOKEN" \
--placeholder GITHUB_PLACEHOLDER
```

Generate a sourceable shell config with proxy env vars and the local CA:

```bash
sandworm token config --host 127.0.0.1 --port 2137 > sandworm-env.sh
source ./sandworm-env.sh
```

Clients can then use placeholders instead of real tokens:

```bash
curl https://api.openai.com/v1/models \
-H 'Authorization: Bearer OPENAI_PLACEHOLDER'

curl https://api.github.com/user \
-H 'Authorization: Bearer GITHUB_PLACEHOLDER'
```

`sandworm` rewrites the outbound requests using the real values stored in SQLite.

Block mode — everything is reachable except the listed domains/CIDRs:
```bash
sandworm --port 2137 --mode block --blocked-domains "facebook.com,*.ads.example.com"
Expand All @@ -75,6 +118,7 @@ Note: block mode cannot be used together with `--ip-proxy-range`.
- `--ip-proxy-range`: CIDR range for IP mode domain mapping
- `--ip-ports`: Comma-separated list of ports for IP mode
- `--dns-port`: DNS server port (requires IP mode)
- `--translate-tokens`: Enable SQLite-backed token translation using `./sandworm.db`

## Building

Expand Down
20 changes: 9 additions & 11 deletions cmd/sandworm/ipmode_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,15 @@ func runEtcHosts(args proxyArgs) error {
}
}

proxyServer := proxy.NewProxy(
0, /* port */
"", /* adminBind */
"allow", /* mode */
allowedDomains,
nil, /* allowedCIDRs */
nil, /* blockedDomains */
nil, /* blockedCIDRs */
args.IPProxyRange,
ipPorts,
0 /* dnsPort */)
proxyServer, err := proxy.NewProxy(proxy.Options{
Mode: "allow",
AllowedDomains: allowedDomains,
IPProxyRange: args.IPProxyRange,
IPPorts: ipPorts,
})
if err != nil {
return err
}

mappings := proxyServer.GetIPMappings()
if len(mappings) == 0 {
Expand Down
57 changes: 43 additions & 14 deletions cmd/sandworm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ to containers on an internal network, with configurable domain and CIDR filterin
}

addSharedFlags(rootCmd, &args)
rootCmd.AddCommand(tokenCommand())
addIPModeCommands(rootCmd)

if err := rootCmd.Execute(); err != nil {
Expand All @@ -38,18 +39,19 @@ to containers on an internal network, with configurable domain and CIDR filterin
}

type proxyArgs struct {
Port int
AdminEnabled bool
AdminBind string
Mode string
Domains string
CIDRs string
BlockedDomains string
BlockedCIDRs string
LogLevel string
IPProxyRange string
IPPorts string
DNSPort int
Port int
AdminEnabled bool
AdminBind string
Mode string
Domains string
CIDRs string
BlockedDomains string
BlockedCIDRs string
LogLevel string
IPProxyRange string
IPPorts string
DNSPort int
TranslateTokens bool
}

func addSharedFlags(cmd *cobra.Command, args *proxyArgs) {
Expand All @@ -63,7 +65,9 @@ func addSharedFlags(cmd *cobra.Command, args *proxyArgs) {
cmd.Flags().StringVar(&args.BlockedDomains, "blocked-domains", "", "Comma-separated list of blocked domains (block mode)")
cmd.Flags().StringVar(&args.BlockedCIDRs, "blocked-cidrs", "", "Comma-separated list of blocked CIDRs (block mode)")
cmd.Flags().StringVarP(&args.LogLevel, "log-level", "l", "info", "Log level (debug, info, warn, error)")
cmd.Flags().BoolVar(&args.TranslateTokens, "translate-tokens", false, "Enable SQLite-backed token translation using the default sandworm DB")
addIPModeFlags(cmd, args)

}

func validateArgs(args proxyArgs) error {
Expand Down Expand Up @@ -121,6 +125,9 @@ func runProxy(ctx context.Context, args proxyArgs) error {
slog.Warn("Deprecated flag --admin used; set --admin-bind explicitly (empty disables)",
"admin_bind", args.AdminBind)
}
if args.TranslateTokens && args.AdminBind == "" {
args.AdminBind = "127.0.0.1"
}

if err := validateArgs(args); err != nil {
return err
Expand Down Expand Up @@ -170,7 +177,27 @@ func runProxy(ctx context.Context, args proxyArgs) error {
}
}

proxyServer := proxy.NewProxy(args.Port, args.AdminBind, args.Mode, allowedDomains, allowedCIDRs, blockedDomains, blockedCIDRs, args.IPProxyRange, ipPorts, args.DNSPort)
tokenDBPath := ""
if args.TranslateTokens {
tokenDBPath = proxy.DefaultTokenDBPath
}

proxyServer, err := proxy.NewProxy(proxy.Options{
Port: args.Port,
AdminBind: args.AdminBind,
Mode: args.Mode,
AllowedDomains: allowedDomains,
AllowedCIDRs: allowedCIDRs,
BlockedDomains: blockedDomains,
BlockedCIDRs: blockedCIDRs,
IPProxyRange: args.IPProxyRange,
IPPorts: ipPorts,
DNSPort: args.DNSPort,
TokenDB: tokenDBPath,
})
if err != nil {
return err
}

ctx, cancel := context.WithCancel(ctx)
defer cancel()
Expand All @@ -187,7 +214,9 @@ func runProxy(ctx context.Context, args proxyArgs) error {
"domains", allowedDomains,
"cidrs", allowedCIDRs,
"blocked_domains", blockedDomains,
"blocked_cidrs", blockedCIDRs)
"blocked_cidrs", blockedCIDRs,
"translate_tokens", args.TranslateTokens,
"translate_tokens_db", tokenDBPath)

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
Expand Down
Loading