diff --git a/cmd/brick/handlers.go b/cmd/brick/handlers.go index 8c70b734..eb873b6b 100644 --- a/cmd/brick/handlers.go +++ b/cmd/brick/handlers.go @@ -22,13 +22,16 @@ import ( "fmt" "io" "io/ioutil" + "net" "net/http" + "strings" "time" "github.com/apex/log" "github.com/atc0005/brick/internal/events" "github.com/atc0005/brick/internal/files" + "github.com/atc0005/brick/internal/textutils" ) // API endpoint patterns supported by this application @@ -87,6 +90,8 @@ func frontPageHandler(w http.ResponseWriter, r *http.Request) { } func disableUserHandler( + requireTrustedPayloadSender bool, + trustedPayloadSenders []string, reportedUserEventsLog *files.ReportedUserEventsLog, disabledUsers *files.DisabledUsers, ignoredSources files.IgnoredSources, @@ -103,6 +108,45 @@ func disableUserHandler( // fmt.Fprintf(mw, "disableUserHandler endpoint hit\n") log.Debug("disableUserHandler handler hit") + // If a list of trusted IPs is not provided by the sysadmin, the + // default behavior is to accept payloads from all IP Addresses. This + // behavior/logic is balanced by configuring the trusted IP Addresses + // list to include only 127.0.0.1 by default in the starter config + // file. + if requireTrustedPayloadSender { + + // get bare sender IP + remoteIPAddr, _, ipHostSplitErr := net.SplitHostPort(events.GetIP(r)) + if ipHostSplitErr != nil { + errMsg := fmt.Sprintf( + "remote IP %q is not in IP:port format: %v", + remoteIPAddr, + ipHostSplitErr, + ) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + fmt.Fprint(w, errMsg) + return + } + + // confirm that sender IP is in the trusted senders list + if !textutils.InList(remoteIPAddr, trustedPayloadSenders) { + errMsg := fmt.Sprintf( + "rejecting payload; remote IP %q is not in the trusted payload senders list: %v", + remoteIPAddr, + trustedPayloadSenders, + ) + log.WithFields(log.Fields{ + "url_path": r.URL.Path, + "http_method": r.Method, + "remote_ip_addr": remoteIPAddr, + "trusted_payload_senders": strings.Join(trustedPayloadSenders, ", "), + }).Error(errMsg) + http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + fmt.Fprint(w, errMsg) + return + } + } + if r.Method != http.MethodPost { log.WithFields(log.Fields{ diff --git a/cmd/brick/main.go b/cmd/brick/main.go index 73a999b1..cb7d3f8b 100644 --- a/cmd/brick/main.go +++ b/cmd/brick/main.go @@ -159,6 +159,8 @@ func main() { mux.HandleFunc( apiV1DisableUserEndpointPattern, disableUserHandler( + appConfig.RequireTrustedPayloadSender(), + appConfig.TrustedIPAddresses(), reportedUserEventsLog, disabledUsers, ignoredSources, diff --git a/contrib/brick/config.example.toml b/contrib/brick/config.example.toml index 3c630827..54a1ccd1 100644 --- a/contrib/brick/config.example.toml +++ b/contrib/brick/config.example.toml @@ -30,6 +30,12 @@ local_tcp_port = 8000 # Local IP Address that this application should listen on for incoming HTTP requests. local_ip_address = "localhost" +# One or many single IP Addresses which are trusted for payload submission. If +# this is defined, all other sender IPs are ignored. If this is not defined, +# payloads are accepted from all IP Addresses not otherwise rejected by +# local/remote firewall rules. +trusted_ip_addresses = ["127.0.0.1"] + [logging] diff --git a/docs/configure.md b/docs/configure.md index 337c4b70..ca3dab13 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -40,6 +40,7 @@ environment variables) and use the configuration file for the other settings. | `ignore-lookup-errors` | No | `false` | No | `true`, `false` | Whether application should continue if attempts to lookup existing disabled or ignored status for a username or IP Address fail. This is needed if you do not pre-create files used by this application ahead of time. WARNING: Because this can mask errors, you should probably only use it briefly when this application is first deployed, then later disable the setting once all files are in place. | | `port` | No | `8000` | No | *valid TCP port number* | TCP port that this application should listen on for incoming HTTP requests. Tip: Use an unreserved port between 1024:49151 (inclusive) for the best results. | | `ip-address` | No | `localhost` | No | *valid fqdn, local name or IP Address* | Local IP Address that this application should listen on for incoming HTTP requests. | +| `trusted-ip-addresses` | No | `127.0.0.1` | No | *one or many valid fqdn or IP Addresses* | One or many single IP Addresses which are trusted for payload submission. If this is defined, all other sender IPs are ignored. If this is not defined, payloads are accepted from all IP Addresses not otherwise rejected by local/remote firewall rules. | | `log-level` | No | `info` | No | `fatal`, `error`, `warn`, `info`, `debug` | Log message priority filter. Log messages with a lower level are ignored. | | `log-output` | No | `stdout` | No | `stdout`, `stderr` | Log messages are written to this output target. | | `log-format` | No | `text` | No | `cli`, `json`, `logfmt`, `text`, `discard` | Use the specified `apex/log` package "handler" to output log messages in that handler's format. | @@ -82,6 +83,7 @@ Arguments](#command-line-arguments) table for more information. | `ignore-lookup-errors` | `BRICK_IGNORE_LOOKUP_ERRORS` | | `BRICK_IGNORE_LOOKUP_ERRORS="false"` | | `port` | `BRICK_LOCAL_TCP_PORT` | | `BRICK_LOCAL_TCP_PORT="8000"` | | `ip-address` | `BRICK_LOCAL_IP_ADDRESS` | | `BRICK_LOCAL_IP_ADDRESS="localhost"` | +| `trusted-ip-addresses` | `BRICK_TRUSTED_IP_ADDRESSES` | | `BRICK_TRUSTED_IP_ADDRESSES="127.0.0.1"` | | `log-level` | `BRICK_LOG_LEVEL` | | `BRICK_LOG_LEVEL="info"` | | `log-output` | `BRICK_LOG_OUTPUT` | | `BRICK_LOG_OUTPUT="stdout"` | | `log-format` | `BRICK_LOG_FORMAT` | | `BRICK_LOG_FORMAT="text"` | @@ -124,6 +126,7 @@ settings. | `ignore-lookup-errors` | `ignore_lookup_errors` | | | | `port` | `local_tcp_port` | `network` | | | `ip-address` | `local_ip_address` | `network` | | +| `trusted-ip-addresses` | `trusted_ip_addresses` | `network` | [Multi-line array](https://github.com/toml-lang/toml#user-content-array) | | `log-level` | `level` | `logging` | | | `log-format` | `format` | `logging` | | | `log-output` | `output` | `logging` | | diff --git a/docs/deploy.md b/docs/deploy.md index 8ad812f9..49ebdab9 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -177,6 +177,9 @@ version. 1. Copy the starter/template configuration file from `contrib/brick/config.example.toml` and modify accordingly using the [configuration](configure.md) guide. +1. Configure one or more trusted IP Addresses + - If you skip this step, only payload submissions from `127.0.0.1` will be + accepted (per the default setting in the config file). 1. Decide whether you will enable automatic sessions termination or use `fail2ban`. See the [fail2ban](fail2ban.md) doc and the [configuration](configure.md) guide for more information. diff --git a/internal/config/constants.go b/internal/config/constants.go index 73574800..2ab28728 100644 --- a/internal/config/constants.go +++ b/internal/config/constants.go @@ -60,11 +60,12 @@ var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z // Default (flag, config file, etc) settings if not overridden by user input const ( - defaultLocalTCPPort int = 8000 - defaultLocalIP string = "localhost" - defaultLogLevel string = "info" - defaultLogOutput string = "stdout" - defaultLogFormat string = "text" + defaultLocalTCPPort int = 8000 + defaultLocalIP string = "localhost" + defaultTrustedIPAddress string = "127.0.0.1" + defaultLogLevel string = "info" + defaultLogOutput string = "stdout" + defaultLogFormat string = "text" // This application does not assume a specific path for the configuration // file, so we default to an empty string if the user does not specify a diff --git a/internal/config/getters.go b/internal/config/getters.go index 0f18a6ed..2d6a2c14 100644 --- a/internal/config/getters.go +++ b/internal/config/getters.go @@ -97,6 +97,27 @@ func (c Config) LocalIPAddress() string { } } +// RequireTrustedPayloadSender indicates whether the sysadmin specified a list +// of IP Addresses to trust for payload submission. +func (c Config) RequireTrustedPayloadSender() bool { + return c.cliConfig.Network.TrustedIPAddresses != nil || + c.fileConfig.Network.TrustedIPAddresses != nil +} + +// TrustedIPAddresses returns the user-provided list of IP Addresses that +// should be trusted to receive payloads or the the default value if not +// provided. CLI flag values take precedence if provided. +func (c Config) TrustedIPAddresses() []string { + switch { + case c.cliConfig.Network.TrustedIPAddresses != nil: + return c.cliConfig.Network.TrustedIPAddresses + case c.fileConfig.Network.TrustedIPAddresses != nil: + return c.fileConfig.Network.TrustedIPAddresses + default: + return []string{defaultTrustedIPAddress} + } +} + // DisabledUsersFile returns the user-provided path to the EZproxy include // file where this application should write disabled user accounts or the // default value if not provided. CLI flag values take precedence if provided. diff --git a/internal/config/types.go b/internal/config/types.go index d6d689af..e0ad20e8 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -48,6 +48,12 @@ type Network struct { // LocalIPAddress is the IP Address that this application should listen on // for incoming requests LocalIPAddress *string `toml:"local_ip_address" arg:"--ip-address,env:BRICK_LOCAL_IP_ADDRESS" help:"Local IP Address that this application should listen on for incoming HTTP requests."` + + // TrustedIPAddresses is the collection of single IP Addresses which are + // trusted for payload submission. If this is defined, all other sender + // IPs are ignored. If this is not defined, payloads are accepted from all + // IP Addresses not otherwise rejected by local/remote firewall rules. + TrustedIPAddresses []string `toml:"trusted_ip_addresses" arg:"--trusted-ip-addresses,env:BRICK_TRUSTED_IP_ADDRESSES" help:"One or many single IP Addresses which are trusted for payload submission. If this is defined, all other sender IPs are ignored. If this is not defined, payloads are accepted from all IP Addresses not otherwise rejected by local/remote firewall rules."` } // Logging is a collection of logging-related settings provided via CLI and diff --git a/internal/config/validate.go b/internal/config/validate.go index 016f0295..6bb5e830 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -18,6 +18,7 @@ package config import ( "fmt" + "net" "github.com/apex/log" @@ -142,6 +143,22 @@ func validate(c Config) error { return fmt.Errorf("local IP Address not provided") } + // the default value is 127.0.0.1, so if the user specifies a value we + // should have at least one item in the slice + switch { + case len(c.TrustedIPAddresses()) < 1: + return fmt.Errorf("empty list of trusted IP Addesses provided") + default: + for _, ipAddr := range c.TrustedIPAddresses() { + if net.ParseIP(ipAddr) == nil { + return fmt.Errorf( + "invalid IP Address %q provided for trusted IPs list", + ipAddr, + ) + } + } + } + switch c.LogLevel() { case LogLevelFatal: case LogLevelError: