Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package weblug for openSUSE:Factory checked in at 2023-06-07 23:07:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/weblug (Old) and /work/SRC/openSUSE:Factory/.weblug.new.15902 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "weblug" Wed Jun 7 23:07:27 2023 rev:2 rq:1091161 version:0.4 Changes: -------- --- /work/SRC/openSUSE:Factory/weblug/weblug.changes 2023-05-03 12:56:56.911716598 +0200 +++ /work/SRC/openSUSE:Factory/.weblug.new.15902/weblug.changes 2023-06-07 23:08:01.435553065 +0200 @@ -1,0 +2,12 @@ +Tue Jun 6 09:44:24 UTC 2023 - Felix Niederwanger <[email protected]> + +* Update to version 0.4 + +This release contains mostly security updates: + +* Add environment sanitation +* Document caveats when running as root +* Add possibility for dropping privileges +* Add openSUSE specific configuration file that runs as 'nobody' by default (fixes bsc#1211197) + +------------------------------------------------------------------- Old: ---- weblug-0.3.obscpio New: ---- weblug-0.4.obscpio weblug.yml ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ weblug.spec ++++++ --- /var/tmp/diff_new_pack.bFDk7U/_old 2023-06-07 23:08:02.519559359 +0200 +++ /var/tmp/diff_new_pack.bFDk7U/_new 2023-06-07 23:08:02.523559383 +0200 @@ -17,13 +17,14 @@ Name: weblug -Version: 0.3 +Version: 0.4 Release: 0 Summary: Simple webhook receiver program License: MIT URL: https://codeberg.org/grisu48/weblug Source: weblug-%{version}.tar.gz Source1: vendor.tar.gz +Source10: weblug.yml BuildRequires: golang-packaging BuildRequires: go1.18 %{go_nostrip} @@ -44,6 +45,8 @@ install -Dpm0644 weblug.service %{buildroot}%{_unitdir}/weblug.service # configuration file (/etc/weblug.yml) mkdir -p %{buildroot}%{_sysconfdir} +# use custom weblug config +cp -avL %{SOURCE10} weblug.yml install -m 600 weblug.yml %{buildroot}%{_sysconfdir}/weblug.yml # man page install -Dm 644 doc/weblug.8 %{buildroot}/%{_mandir}/man8/weblug.8 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.bFDk7U/_old 2023-06-07 23:08:02.567559638 +0200 +++ /var/tmp/diff_new_pack.bFDk7U/_new 2023-06-07 23:08:02.571559662 +0200 @@ -3,7 +3,7 @@ <param name="url">[email protected]:grisu48/weblug.git</param> <param name="scm">git</param> <param name="revision">main</param> - <param name="version">0.3</param> + <param name="version">0.4</param> <param name="versionrewrite-pattern">v(.*)</param> </service> <service name="tar" mode="buildtime"/> ++++++ vendor.tar.gz ++++++ ++++++ weblug-0.3.obscpio -> weblug-0.4.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/Makefile new/weblug-0.4/Makefile --- old/weblug-0.3/Makefile 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/Makefile 2023-06-06 10:50:57.000000000 +0200 @@ -1,6 +1,8 @@ default: all all: weblug +.PHONY: test + weblug: cmd/weblug/*.go go build -o weblug $^ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/README.md new/weblug-0.4/README.md --- old/weblug-0.3/README.md 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/README.md 2023-06-06 10:50:57.000000000 +0200 @@ -1,10 +1,10 @@ # weblug -Simple webhook receiver program. `weblug` is is a configurable webhook receiver that allows users to define custom programs and script to be executed when a webhook is triggered. +Webhook receiver program. `weblug` is is a configurable webhook receiver that allows users to define custom programs and script to be executed when a webhook is triggered. -The configuration happens via a simple [yaml file](weblug.yml). +The configuration happens via a [yaml file](weblug.yml). Read the [usage caveats](#caveats)! -`weblug` supports multiple webhooks, limitations for concurrent web hooks to be executed, background execution and running webhooks as separate user (`uid`/`gid`). And all of this in a tidy, easy-to-use yaml file! +`weblug` supports multiple webhooks, limitations for concurrent web hooks to be executed, background execution and running webhooks as separate user (`uid`/`gid`). ## Usage @@ -16,6 +16,22 @@ `weblug` can run as any user, however for custom `uid`/`gid` webhooks, the program needs to run as root. +### Caveats + +1. `weblug` does not support https encryption! + +weblug is expected to run behind a http reverse proxy (e.g. `apache` or `nginx`) which handles transport encryption. The program it self does not support https, nor are there any plans to implement this in the near future. + +CAVE: Don't expose secrets and credentials by running this without any transport encryption! + +2. Do not run this without reverse proxy + +`weblug` relies on the standart go http implementation. To avoid a whole class of securtiy concerns, `weblug` should never run on the open internet without a http reverse proxy. + +3. `weblug` runs as root, when using custom UID/GIDs + +In it's current implementation, `weblug` requires to remain running as root without dropping privileges when using custom UID/GIDs. This is a current limitation that will be hopefully resolved soon. + ## Build make # Build weblug diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/cmd/weblug/config.go new/weblug-0.4/cmd/weblug/config.go --- old/weblug-0.3/cmd/weblug/config.go 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/cmd/weblug/config.go 2023-06-06 10:50:57.000000000 +0200 @@ -14,6 +14,8 @@ type ConfigSettings struct { BindAddress string `yaml:"bind"` // Bind address for the webserver + UID int `yaml:"uid"` // Custom user ID or 0, if not being used + GID int `yaml:"gid"` // Custom group ID or 0, if not being used } func (cf *Config) SetDefaults() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/cmd/weblug/hook.go new/weblug-0.4/cmd/weblug/hook.go --- old/weblug-0.3/cmd/weblug/hook.go 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/cmd/weblug/hook.go 2023-06-06 10:50:57.000000000 +0200 @@ -10,18 +10,19 @@ ) type Hook struct { - Name string `yaml:"name"` // name of the hook - Route string `yaml:"route"` // http route - Command string `yaml:"command"` // Actual command to execute - Background bool `yaml:"background"` // Run in background - Concurrency int `yaml:"concurrency"` // Number of allowed concurrent runs - concurrentRuns int32 // Number of current concurrent runs - UID int `yaml:"uid"` // UID to use when running the command - GID int `yaml:"gid"` // GID to use when running the command - Output bool `yaml:"output"` // Print program output - AllowAddresses []string `yaml:"allowed"` // Addresses that are explicitly allowed - BlockedAddresses []string `yaml:"blocked"` // Addresses that are explicitly blocked - HttpBasicAuth BasicAuth `yaml:"basic_auth"` // Optional requires http basic auth + Name string `yaml:"name"` // name of the hook + Route string `yaml:"route"` // http route + Command string `yaml:"command"` // Actual command to execute + Background bool `yaml:"background"` // Run in background + Concurrency int `yaml:"concurrency"` // Number of allowed concurrent runs + concurrentRuns int32 // Number of current concurrent runs + UID int `yaml:"uid"` // UID to use when running the command + GID int `yaml:"gid"` // GID to use when running the command + Output bool `yaml:"output"` // Print program output + AllowAddresses []string `yaml:"allowed"` // Addresses that are explicitly allowed + BlockedAddresses []string `yaml:"blocked"` // Addresses that are explicitly blocked + HttpBasicAuth BasicAuth `yaml:"basic_auth"` // Optional requires http basic auth + Env map[string]string `yaml:"env"` // Optional environment variables } type BasicAuth struct { @@ -89,6 +90,13 @@ cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(hook.UID), Gid: uint32(hook.GID)} } + cmd.Env = make([]string, 0) + if hook.Env != nil { + // Build environment variable list as expected by cmd.Env + for k, v := range hook.Env { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + } if hook.Output { buf, ret := cmd.Output() fmt.Println(string(buf)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/cmd/weblug/weblug.go new/weblug-0.4/cmd/weblug/weblug.go --- old/weblug-0.3/cmd/weblug/weblug.go 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/cmd/weblug/weblug.go 2023-06-06 10:50:57.000000000 +0200 @@ -17,7 +17,7 @@ type Handler func(http.ResponseWriter, *http.Request) func usage() { - fmt.Println("weblug is a simple webhook receiver") + fmt.Println("weblug is a webhook receiver") fmt.Printf("Usage: %s [OPTIONS] YAML1[,YAML2...]\n\n", os.Args[0]) fmt.Println("OPTIONS") fmt.Println(" -h, --help Print this help message") @@ -38,7 +38,11 @@ // Perform sanity check on hooks func sanityCheckHooks(hooks []Hook) error { - uid := os.Getuid() + // Check UID and GID settings. When hooks have their own UID and GID settings, we need the main program to run as root (required for setgid/setuid) + uid := cf.Settings.UID + if uid == 0 { + uid = os.Getuid() + } for _, hook := range hooks { // If a hook sets a custom uid or gid, ensure we're running as root, otherwise print a warning if hook.UID != 0 && uid != 0 { @@ -81,6 +85,20 @@ os.Exit(3) } + // Drop privileges? + if cf.Settings.GID != 0 { + if err := syscall.Setgid(cf.Settings.GID); err != nil { + fmt.Fprintf(os.Stderr, "setgid failed: %s\n", err) + os.Exit(1) + } + } + if cf.Settings.UID != 0 { + if err := syscall.Setuid(cf.Settings.UID); err != nil { + fmt.Fprintf(os.Stderr, "setuid failed: %s\n", err) + os.Exit(1) + } + } + // Create default handlers http.HandleFunc("/health", createHealthHandler()) http.HandleFunc("/health.json", createHealthHandler()) @@ -197,7 +215,7 @@ func createDefaultHandler() Handler { return func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - fmt.Fprintf(w, "weblug - simple webhook handler") + fmt.Fprintf(w, "weblug - webhook receiver program\nSee https://codeberg.org/grisu48/weblug\n") } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/doc/weblug.8 new/weblug-0.4/doc/weblug.8 --- old/weblug-0.3/doc/weblug.8 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/doc/weblug.8 2023-06-06 10:50:57.000000000 +0200 @@ -1,6 +1,6 @@ ." Manpage for weblug -." Contact [email protected] to correct errors and/or typos. -.TH weblug 8 "27 Apr 2023" "1.0" "weblug man page" +." Write me an email <[email protected]> if you find errors and/or typos. Thank you! :-) +.TH weblug 8 "28 May 2023" "1.0" "weblug man page" .SH NAME weblug - Simple webhook receiver program @@ -18,6 +18,16 @@ .B -h|--help Print help message +.SH CAVEATS + +1. weblug should always run behind a http reverse proxy to avoid a whole class of security issues by using the standart go webserver implementation. + +2. weblug does not support transport encryption (https). To protect access credentials/tokens, it must run behind a http reverse proxy with configured transport encryption. + +3. weblug should not be exposed to the public internet. + +4. Custom UID/GIDs for webhook require weblug to run as root. + .SH CONFIGURATION FILES .TP @@ -36,6 +46,10 @@ .br .B " bind: ":2088" # bind to all addresses .br +.B " uid: 0 # run under specified user id +.br +.B " gid: 0 # run under specified group id +.br .B "# hook definitions. A hook needs to define the HTTP endpoint ("route") and the command .br .B "# See the following examples for more possible options. @@ -52,6 +66,14 @@ .br .B " concurrency: 2 # At most 2 parallel processes are allowed .br +.B " env: # Define environment variables +.br +.B " KEY1: "VALUE1" +.br +.B " KEY2: "VALUE2" +.br +.br +.br .br .B " - name: 'hook two' .br diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/test/blackbox.sh new/weblug-0.4/test/blackbox.sh --- old/weblug-0.3/test/blackbox.sh 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/test/blackbox.sh 2023-06-06 10:50:57.000000000 +0200 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -ex # Blackbox tests for weblug cleanup() { @@ -14,16 +14,19 @@ sleep 3 fi +# Secret environment variable, which must be removed by env sanitation. +export SECRET1="top5ecret" rm -f testfile ../weblug test.yaml & +../weblug test_uid.yaml & sleep 1 ## Check touch webhook, which creates "testfile" echo -e "\n\nChecking 'testfile' webhook ... " -curl http://127.0.0.1:2088/webhooks/touch +curl --fail http://127.0.0.1:2088/webhooks/touch if [[ ! -f testfile ]]; then echo "Testfile doesn't exist after running webhook touch" exit 1 @@ -34,15 +37,15 @@ echo -e "\n\nChecking 'background' webhook ... " -timeout 2 curl http://127.0.0.1:2088/webhooks/background +timeout 2 curl --fail http://127.0.0.1:2088/webhooks/background ## Check concurrency webhook, that allows only 2 requests at the same time (but sleeps for 5 seconds) echo -e "\n\nChecking 'concurrency' webhook ... " -timeout 10 curl http://127.0.0.1:2088/3 & -timeout 10 curl http://127.0.0.1:2088/3 & -! timeout 2 curl http://127.0.0.1:2088/3 +timeout 10 curl --fail http://127.0.0.1:2088/3 & +timeout 10 curl --fail http://127.0.0.1:2088/3 & +! timeout 2 curl --fail http://127.0.0.1:2088/3 ## Check UID and GID webhooks, but only if we're root @@ -50,10 +53,22 @@ # Skip this test, if we're not root if [[ $EUID == 0 || $UID == 0 ]]; then - curl http://127.0.0.1:2088/webhooks/uid - curl http://127.0.0.1:2088/webhooks/gid + curl --fail http://127.0.0.1:2088/webhooks/uid + curl --fail http://127.0.0.1:2088/webhooks/gid else echo "Cannot UID and GID webhook tests, because we're not running as root" fi +## Check environment variables + +timeout 10 curl --fail 'http://127.0.0.1:2088/env' + + +## Check UID/GID handling + +# Ensure weblug is running with UID=65534 +pgrep -u 65534 weblug +# Ensure weblug2 (test_uid) is actually running +timeout 10 curl --fail 'http://127.0.0.1:2089/uid' + echo -e "\n\nall good" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/test/checkenv new/weblug-0.4/test/checkenv --- old/weblug-0.3/test/checkenv 1970-01-01 01:00:00.000000000 +0100 +++ new/weblug-0.4/test/checkenv 2023-06-06 10:50:57.000000000 +0200 @@ -0,0 +1,30 @@ +#!/bin/bash -e +# Script to test for environment variable sanitation + +set -o pipefail + +if [[ $PUBLIC1 != "one" ]]; then + echo "PUBLIC1 variable not valid" + exit 1 +fi +if [[ $PUBLIC2 != "two" ]]; then + echo "PUBLIC2 variable not valid" + exit 1 +fi + +if env | grep 'SECRET1' >/dev/null; then + echo "SECRET1 variable is set but it should not be" + echo "Environment sanitation failed" + exit 1 +fi + +# There must never be more than 10 variables set +# Some variables will be set by bash at startup (e.g. PWD), so we are never in a +# pristine environment. However, more than 10 variables indicates something's off +# with the env sanitation +if [[ `env | wc -l` -ge 10 ]]; then + echo "More than 10 env variables detected" + exit 1 +fi + +echo "all good" \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/test/test.yaml new/weblug-0.4/test/test.yaml --- old/weblug-0.3/test/test.yaml 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/test/test.yaml 2023-06-06 10:50:57.000000000 +0200 @@ -25,3 +25,10 @@ uid: 10 gid: 10 output: True + - name: 'environment variables' + route: '/env' + command: "bash ./checkenv" + output: True + env: + PUBLIC1: "one" + PUBLIC2: "two" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/test/test_uid.yaml new/weblug-0.4/test/test_uid.yaml --- old/weblug-0.3/test/test_uid.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/weblug-0.4/test/test_uid.yaml 2023-06-06 10:50:57.000000000 +0200 @@ -0,0 +1,11 @@ +--- +settings: + bind: "127.0.0.1:2089" # bind address for webserver + uid: 65534 + gid: 65534 + +hooks: + - name: 'uid hook' + route: "/uid" + command: "id -u" + output: True \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/weblug.spec new/weblug-0.4/weblug.spec --- old/weblug-0.3/weblug.spec 1970-01-01 01:00:00.000000000 +0100 +++ new/weblug-0.4/weblug.spec 2023-06-06 10:50:57.000000000 +0200 @@ -0,0 +1,58 @@ +# +# spec file for package weblug +# + + +Name: weblug +Version: 0.3 +Release: 0 +Summary: Simple webhook receiver program +License: MIT +URL: https://codeberg.org/grisu48/weblug +Source: weblug-%{version}.tar.gz +Source1: vendor.tar.gz +BuildRequires: golang-packaging +BuildRequires: go1.18 +%{go_nostrip} +%{systemd_ordering} + +%description +Simple webhook receiver program + +%prep +%autosetup -D -a 1 + +%build +make all GOARGS="-mod vendor -buildmode pie" + +%install +install -Dm 755 weblug %{buildroot}/%{_bindir}/weblug +# systemd unit +install -Dpm0644 weblug.service %{buildroot}%{_unitdir}/weblug.service +# configuration file (/etc/weblug.yml) +mkdir -p %{buildroot}%{_sysconfdir} +install -m 600 weblug.yml %{buildroot}%{_sysconfdir}/weblug.yml +# man page +install -Dm 644 doc/weblug.8 %{buildroot}/%{_mandir}/man8/weblug.8 + +%pre +%service_add_pre weblug.service + +%preun +%service_del_preun weblug.service + +%post +%service_add_post weblug.service + +%postun +%service_del_postun weblug.service + +%files +%doc README.md +%license LICENSE +%{_bindir}/weblug +%{_unitdir}/weblug.service +%config %{_sysconfdir}/weblug.yml +%{_mandir}/man8/weblug.8%{?ext_man} + +%changelog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weblug-0.3/weblug.yml new/weblug-0.4/weblug.yml --- old/weblug-0.3/weblug.yml 2023-04-27 13:37:50.000000000 +0200 +++ new/weblug-0.4/weblug.yml 2023-06-06 10:50:57.000000000 +0200 @@ -4,6 +4,10 @@ settings: #bind: "127.0.0.1:2088" # bind address for webserver bind: ":2088" # bind to all addresses + # Note: Due to current limitations, weblug needs to run as root when you use custom uid,gid settings per webhook + # This is a known issue, see https://codeberg.org/grisu48/weblug/issues/9 + uid: 0 # run under specified user id + gid: 0 # run under specified group id # hook definitions. A hook needs to define the HTTP endpoint ("route") and the command # See the following examples for more possible options. @@ -13,6 +17,9 @@ command: "sleep 5" background: True # Terminate http request immediately concurrency: 2 # At most 2 parallel processes are allowed + env: # Define environment variables + KEY1: "VALUE1" + KEY2: "VALUE2" - name: 'hook two' route: "/webhooks/2" ++++++ weblug.obsinfo ++++++ --- /var/tmp/diff_new_pack.bFDk7U/_old 2023-06-07 23:08:02.739560637 +0200 +++ /var/tmp/diff_new_pack.bFDk7U/_new 2023-06-07 23:08:02.743560660 +0200 @@ -1,5 +1,5 @@ name: weblug -version: 0.3 -mtime: 1682595470 -commit: a7f5abb57b051eae6d63ff556f9fb921946cc7e6 +version: 0.4 +mtime: 1686041457 +commit: cec58caffe57a92ccb01d1886692b4064c6b013a ++++++ weblug.yml ++++++ --- ## Weblug configuration file settings: bind: "127.0.0.1:2088" # bind address for webserver # Run as nobody - Deactive this if you need custom uid/gid for webhooks uid: 65534 gid: 65534 # hook definitions. A hook needs to define the HTTP endpoint ("route") and the command # See the following examples for more possible options. hooks: - name: 'hook one' route: "/webhooks/1" command: "true" # Replace with the command you want to execute background: True # Terminate http request immediately concurrency: 2 # At most 2 parallel processes are allowed output: False # If enabled, prints program output to console env: # Define environment variables KEY1: "VALUE1" KEY2: "VALUE2" # Allow only requests from localhost allowed: ["127.0.0.1/8", "::1/128"] - name: 'hook two' route: "/webhooks/restricted/5" command: "true" # Allow everything, except those two subnets blocked: ["192.168.0.0/16", "10.0.0.0/8"] # Also require basic auth for this webhook basic_auth: # Username is optional. If defined, the following username must match # If not defined, any user will be accepted username: 'user' # Password is obligatory to enable basic_auth. If defined, a request must authenticate with the given password (cleartext) password: 'password'
