Skip to content

Commit

Permalink
Merge pull request #26 from wollomatic/develop
Browse files Browse the repository at this point in the history
Add configuration through environment variables
  • Loading branch information
wollomatic authored Aug 10, 2024
2 parents b19b3d2 + 2cf1be3 commit 35e6259
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 44 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22.5-alpine3.20 AS build
FROM --platform=$BUILDPLATFORM golang:1.22.6-alpine3.20 AS build
WORKDIR /application
COPY . ./
ARG TARGETOS
Expand Down
46 changes: 29 additions & 17 deletions README.md

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions cmd/socket-proxy/handlehttprequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"errors"
"github.com/wollomatic/socket-proxy/internal/config"
"log/slog"
"net"
"net/http"
Expand All @@ -25,7 +24,7 @@ func handleHttpRequest(w http.ResponseWriter, r *http.Request) {
}

// check if the request is allowed
allowed, exists := config.AllowedRequests[r.Method]
allowed, exists := cfg.AllowedRequests[r.Method]
if !exists { // method not in map -> not allowed
communicateBlockedRequest(w, r, "method not allowed", http.StatusMethodNotAllowed)
return
Expand Down
9 changes: 5 additions & 4 deletions cmd/socket-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import (
)

const (
programUrl = "github.com/wollomatic/socket-proxy"
programUrl = "github.com/wollomatic/socket-proxy"
logAddSource = false // set to true to log the source position (file and line) of the log message
)

var (
Expand All @@ -37,7 +38,7 @@ func main() {

// setup logging
logOpts := &slog.HandlerOptions{
AddSource: config.LogSourcePosition,
AddSource: logAddSource,
Level: cfg.LogLevel,
}
var logger *slog.Logger
Expand All @@ -59,14 +60,14 @@ func main() {

// print request allow list
if cfg.LogJSON {
for method, regex := range config.AllowedRequests {
for method, regex := range cfg.AllowedRequests {
slog.Info("configured allowed request", "method", method, "regex", regex)
}
} else {
// don't use slog here, as we want to print the regexes as they are
// see https://github.com/wollomatic/socket-proxy/issues/11
fmt.Printf("Request allowlist:\n %-8s %s\n", "Method", "Regex")
for method, regex := range config.AllowedRequests {
for method, regex := range cfg.AllowedRequests {
fmt.Printf(" %-8s %s\n", method, regex)
}
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/wollomatic/socket-proxy

go 1.22.2
go 1.22.6
92 changes: 73 additions & 19 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,27 @@ import (
"log/slog"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
)

const LogSourcePosition = false // set to true to log the source position (file and line) of the log message

const (
var (
defaultAllowFrom = "127.0.0.1/32" // allowed IPs to connect to the proxy
defaultAllowHealthcheck = false // allow health check requests (HEAD http://localhost:55555/health)
defaultLogJSON = false // if true, log in JSON format
defaultLogLevel = "INFO" // log level as string
defaultListenIP = "127.0.0.1" // ip address to bind the server to
defaultProxyPort = 2375 // tcp port to listen on
defaultProxyPort = uint(2375) // tcp port to listen on
defaultSocketPath = "/var/run/docker.sock" // path to the unix socket
defaultShutdownGraceTime = uint(10) // Maximum time in seconds to wait for the server to shut down gracefully
defaultWatchdogInterval = uint(0) // watchdog interval in seconds (0 to disable)
defaultStopOnWatchdog = false // set to true to stop the program when the socket gets unavailable (otherwise log only)
)

type Config struct {
AllowedRequests map[string]*regexp.Regexp
AllowFrom string
AllowHealthcheck bool
LogJSON bool
Expand All @@ -38,19 +39,17 @@ type Config struct {
SocketPath string
}

var (
AllowedRequests map[string]*regexp.Regexp
)

// used for list of allowed requests
type methodRegex struct {
method string
regexString string
method string
regexStringFromEnv string
regexStringFromParam string
}

// mr is the allowlist of requests per http method
// default: regegString is empty, so regexCompiled stays nil and the request is blocked
// if regexString is set with a command line parameter, all requests matching the method and path matching the regex are allowed
// default: regexStringFromEnv and regexStringFromParam are empty, so regexCompiled stays nil and the request is blocked
// if regexStringParam is set with a command line parameter, all requests matching the method and path matching the regex are allowed
// else if regexStringEnv from Environment ist checked
var mr = []methodRegex{
{method: http.MethodGet},
{method: http.MethodHead},
Expand All @@ -70,6 +69,55 @@ func InitConfig() (*Config, error) {
proxyPort uint
logLevel string
)

if val, ok := os.LookupEnv("SP_ALLOWFROM"); ok && val != "" {
defaultAllowFrom = val
}
if val, ok := os.LookupEnv("SP_ALLOWHEALTHCHECK"); ok {
if parsedVal, err := strconv.ParseBool(val); err == nil {
defaultAllowHealthcheck = parsedVal
}
}
if val, ok := os.LookupEnv("SP_LOGJSON"); ok {
if parsedVal, err := strconv.ParseBool(val); err == nil {
defaultLogJSON = parsedVal
}
}
if val, ok := os.LookupEnv("SP_LISTENIP"); ok && val != "" {
defaultListenIP = val
}
if val, ok := os.LookupEnv("SP_LOGLEVEL"); ok && val != "" {
defaultLogLevel = val
}
if val, ok := os.LookupEnv("SP_PROXYPORT"); ok && val != "" {
if parsedVal, err := strconv.ParseUint(val, 10, 32); err == nil {
defaultProxyPort = uint(parsedVal)
}
}
if val, ok := os.LookupEnv("SP_SHUTDOWNGRACETIME"); ok && val != "" {
if parsedVal, err := strconv.ParseUint(val, 10, 32); err == nil {
defaultShutdownGraceTime = uint(parsedVal)
}
}
if val, ok := os.LookupEnv("SP_SOCKETPATH"); ok && val != "" {
defaultSocketPath = val
}
if val, ok := os.LookupEnv("SP_STOPONWATCHDOG"); ok {
if parsedVal, err := strconv.ParseBool(val); err == nil {
defaultStopOnWatchdog = parsedVal
}
}
if val, ok := os.LookupEnv("SP_WATCHDOGINTERVAL"); ok && val != "" {
if parsedVal, err := strconv.ParseUint(val, 10, 32); err == nil {
defaultWatchdogInterval = uint(parsedVal)
}
}
for i := 0; i < len(mr); i++ {
if val, ok := os.LookupEnv("SP_ALLOW_" + mr[i].method); ok && val != "" {
mr[i].regexStringFromEnv = val
}
}

flag.StringVar(&cfg.AllowFrom, "allowfrom", defaultAllowFrom, "allowed IPs or hostname to connect to the proxy")
flag.BoolVar(&cfg.AllowHealthcheck, "allowhealthcheck", defaultAllowHealthcheck, "allow health check requests (HEAD http://localhost:55555/health)")
flag.BoolVar(&cfg.LogJSON, "logjson", defaultLogJSON, "log in JSON format (otherwise log in plain text")
Expand All @@ -81,11 +129,11 @@ func InitConfig() (*Config, error) {
flag.BoolVar(&cfg.StopOnWatchdog, "stoponwatchdog", defaultStopOnWatchdog, "stop the program when the socket gets unavailable (otherwise log only)")
flag.UintVar(&cfg.WatchdogInterval, "watchdoginterval", defaultWatchdogInterval, "watchdog interval in seconds (0 to disable)")
for i := 0; i < len(mr); i++ {
flag.StringVar(&mr[i].regexString, "allow"+mr[i].method, mr[i].regexString, "regex for "+mr[i].method+" requests (not set means method is not allowed)")
flag.StringVar(&mr[i].regexStringFromParam, "allow"+mr[i].method, "", "regex for "+mr[i].method+" requests (not set means method is not allowed)")
}
flag.Parse()

// pcheck listenIP and proxyPort
// check listenIP and proxyPort
if net.ParseIP(listenIP) == nil {
return nil, fmt.Errorf("invalid IP \"%s\" for listenip", listenIP)
}
Expand All @@ -109,14 +157,20 @@ func InitConfig() (*Config, error) {
}

// compile regexes for allowed requests
AllowedRequests = make(map[string]*regexp.Regexp)
cfg.AllowedRequests = make(map[string]*regexp.Regexp)
for _, rx := range mr {
if rx.regexString != "" {
r, err := regexp.Compile("^" + rx.regexString + "$")
if rx.regexStringFromParam != "" {
r, err := regexp.Compile("^" + rx.regexStringFromParam + "$")
if err != nil {
return nil, fmt.Errorf("invalid regex \"%s\" for method %s in command line parameter: %s", rx.regexStringFromParam, rx.method, err)
}
cfg.AllowedRequests[rx.method] = r
} else if rx.regexStringFromEnv != "" {
r, err := regexp.Compile("^" + rx.regexStringFromEnv + "$")
if err != nil {
return nil, fmt.Errorf("invalid regex \"%s\" for method %s: %s", rx.regexString, rx.method, err)
return nil, fmt.Errorf("invalid regex \"%s\" for method %s in env variable: %s", rx.regexStringFromParam, rx.method, err)
}
AllowedRequests[rx.method] = r
cfg.AllowedRequests[rx.method] = r
}
}
return &cfg, nil
Expand Down

0 comments on commit 35e6259

Please sign in to comment.