I am creating a receiver for webhooks in Go, this is my first application in Go.
I have tested the application locally and it works over there. But now I have deployed it on my Ubuntu server in a Docker container behind an NGINX proxy (the proxy is outside of Docker). The pingHandler
works, and the gitlabHandler
can send the 403 message. But if the token is valid I will always see a 502 message and the NGINX log tells me:
*1115 upstream prematurely closed connection while reading response header from upstream, client: X.X.X.X, server: myserver.home.example, request: "POST /webhookreceiver/gitlab HTTP/1.1", upstream: "http://127.0.0.1:7080/gitlab", host: "myserver.home.example"
Also when I send an invalid JSON payload it will still give the same error message, so if I understand the error correctly my Go application closes the connection somewhere before line 72-76. I am guessing something is wrong with line 65?
For most other people having this issue it can be solved by increasing the timeout because their requests are too large, but in my case I am testing with a JSON message of only a few bytes.
main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
)
type ServerConfig struct {
Https bool `json:"https"`
Cert string `json:"cert"`
Key string `json:"key"`
Gitlabtoken string `json:"gitlab_token"`
}
type SplunkConfig struct {
Token string
Url string
Metadata map[string]string
}
type SplunkEvent struct {
Host string `json:"host"`
Sourcetype string `json:"sourcetype"`
Index string `json:"index"`
Source string `json:"source"`
Event map[string]interface{} `json:"event"`
}
var splunk_config SplunkConfig
var server_config ServerConfig
type middleware func(next http.HandlerFunc) http.HandlerFunc
func withLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("IP %s", r.RemoteAddr)
next.ServeHTTP(w, r)
}
}
func chainMiddleware(mw ...middleware) middleware {
return func(final http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
last := final
for i := len(mw) - 1; i >= 0; i-- {
last = mw[i](last)
}
last(w, r)
}
}
}
func gitlabHandler(w http.ResponseWriter, req *http.Request) {
// Check if GitLab token is present and correct
gitlab_header := req.Header.Get("X-Gitlab-Token")
if gitlab_header == server_config.Gitlabtoken {
// Create SplunkEvent to send to Splunk
body, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error reading body"))
return
}
var event map[string]interface{}
err = json.Unmarshal(body, &event)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("400 - Request cannot be parsed"))
return
}
se := SplunkEvent{
Host: splunk_config.Metadata["host"],
Sourcetype: splunk_config.Metadata["sourcetype"],
Index: splunk_config.Metadata["index"],
Source: "gitlab",
Event: event}
j, err := json.Marshal(se)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error creating forwarding call"))
return
}
// Send SplunkEvent to Splunk
b := bytes.NewBuffer(j)
client := &http.Client{
Timeout: time.Second * 30,
}
req, err := http.NewRequest("POST", splunk_config.Url, b)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error creating request"))
return
}
req.Header.Add("Authorization", "Splunk "+splunk_config.Token)
resp, err := client.Do(req)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error sending request"))
return
}
// Check response
bod, err := ioutil.ReadAll(resp.Body)
if strings.Contains(string(bod), "Success") {
log.Println("Received and succesfully processed request")
w.WriteHeader(http.StatusOK)
w.Write([]byte("200 - OK"))
return
} else {
log.Println(string(bod))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error sending to Splunk"))
return
}
defer resp.Body.Close()
} else {
log.Println("Incorrect Gitlab token")
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("403 - Forbidden"))
return
}
}
func pingHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received ping call")
fmt.Fprint(w, "{\"check\": \"online\"}")
}
func main() {
// Setup logging
file, err := os.OpenFile("webhookreceiver.log", os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("Error opening log file: " + err.Error())
}
defer file.Close()
log.SetOutput(file)
// Load Splunk config
data, err := ioutil.ReadFile("configs/splunk.json")
if err != nil {
log.Fatal("Error reading splunk_config.json: " + err.Error())
}
err = json.Unmarshal(data, &splunk_config)
if err != nil {
log.Fatal("Error on unmarshal of Splunk config: " + err.Error())
}
// Load server config
data, err = ioutil.ReadFile("configs/server.json")
if err != nil {
log.Fatal("Error reading server_config.json: " + err.Error())
}
err = json.Unmarshal(data, &server_config)
if err != nil {
log.Fatal("Error on unmarshal of Server config: " + err.Error())
}
// Start server
log.Println("Starting server")
mw := chainMiddleware(withLogging)
http.Handle("/gitlab", mw(gitlabHandler))
http.HandleFunc("/ping", mw(pingHandler))
if server_config.Https {
log.Fatal(http.ListenAndServeTLS(":7443", server_config.Cert, server_config.Key, nil))
} else {
log.Fatal(http.ListenAndServe(":7080", nil))
}
}
Relevant bit of NGINX config
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /blabla/fullchain.pem;
ssl_certificate_key /blabla/privkey.pem;
client_max_body_size 3M;
add_header Strict-Transport-Security "max-age=31536000" always;
location /webhookreceiver/ {
proxy_pass http://127.0.0.1:7080/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Also the logging of my Go application is working locally but not inside of my Docker container, the log file is being created but it stays empty. So at this point I don't have any messages from there, unless someone knows why that is ;-)