diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de4846d..856284f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v1 with: - go-version: 1.12 + go-version: 1.13 - name: Check out code uses: actions/checkout@v1 - name: Install dependency diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fa3f3c8..de8a51e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,21 +5,12 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - name: Set up Go - uses: actions/setup-go@v1 - with: - go-version: 1.12 - name: Check out code uses: actions/checkout@v1 - - name: Install dependency - run: | - go mod download - - name: Lint Go Code - run: | - export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14 - go get -u github.com/golangci/golangci-lint/cmd/golangci-lint - golangci-lint run ./... + uses: docker://golangci/golangci-lint:latest + with: + args: golangci-lint run ./... test: name: Test @@ -28,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v1 with: - go-version: 1.12 + go-version: 1.13 - name: Check out code uses: actions/checkout@v1 - name: Install dependency diff --git a/docs/configuration.md b/docs/configuration.md index 96bbc51..0f0ab1e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3,33 +3,37 @@ The HTTP Broadcaster follows [the twelve-factor app methodology](https://12factor.net/) and is configurable using [environment variables](https://en.wikipedia.org/wiki/Environment_variable): -### Configuration +## Configuration -| Variable | Required/Default | Description | -|-------------------------------|------------------|-------------| -| `AGENT_ENDPOINT` | _undefined_ | the address to broadcast requests to (example: `127.0.0.1:6800`). When not defined, the broadcaster will only listen on requests. `SERVER_ADDR` or `AGENT_ENDPOINT` is required. | -| `AGENT_RETRY_DELAY` | `60s` | maximum duration for retrying the replay of the request. | -| `DEBUG` | `0` | set to `1` to enable the debug mode (prints recovery stack traces). | -| `HUB_ENDPOINT` | **required** | the address of the the mercure hub to push and fetch messages (example: `https://example.com/hub`). | -| `HUB_GUARD_TOKEN` | =`HUB_TOPIC` | the token used to prevent infinite loop (in case an agent broadcast request to iteself). | -| `HUB_PUBLISH_TOKEN` | =`HUB_TOKEN` | valid JWT token to allow publishing. | -| `HUB_SUBSCRIBE_TOKEN` | =`HUB_TOKEN` | valid JWT token to allow subscribing. | -| `HUB_TIMEOUT` | `5s` | maximum duration for pushing the message into the HUB, set to `0s` to disable. | -| `HUB_TOKEN` | **required** | valid JWT token to allow both publishing and subscribing. On could use [this example](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJodHRwLWJyb2FkY2FzdCJdLCJwdWJsaXNoIjpbImh0dHAtYnJvYWRjYXN0Il19fQ._CEt9vXo2zGHSRTmd6LkoOYEtT014m75AVBn9KfA2Go) to generate a new one| -| `HUB_TOPIC` | `http-broadcast` | name of the Mercure's topic to exchange messages. This parameter can also be defined with by the queryString of `HUB_ENDPOINT`. example `HUB_ENDPOINT=https://example.com/hub?topic=my_topic`. | -| `LOG_FORMAT` | `text` | the log format, can be `json`, `fluentd` or `text`. | -| `LOG_LEVEL` | `info` | the log verbosity, can be `trace`, `debug`, `info`, `warn`, `error`, `fatal`. | -| `SERVER_ADDR` | _undefined_ | the address to listen on (example: `0.0.0.0:6081`). When not defined, the broadcaster will only pusblish requests. `SERVER_ADDR` or `AGENT_ENDPOINT` is required. | -| `SERVER_CORS_ALLOWED_ORIGINS` | _undefined_ | a comma separated list of allowed CORS origins, can be `*` for all. | -| `SERVER_INSECURE` | =`DEBUG` | trust everyone in [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). | -| `SERVER_READ_TIMEOUT` | `0s` | maximum duration before timing out writes of the response, set to `0s` to disable, example: `2m`. | -| `SERVER_TLS_ACME_ADDR` | `:http` | the address use by the acme server to listen on (example: `0.0.0.0:8080`). | -| `SERVER_TLS_ACME_CERT_DIR` | _undefined_ | the directory where to store Let's Encrypt certificates. | -| `SERVER_TLS_ACME_HOSTS` | _undefined_ | a comma separated list of hosts for which Let's Encrypt certificates must be issued. | -| `SERVER_TLS_CERT_FILE` | _undefined_ | a cert file (to use a custom certificate). | -| `SERVER_TLS_KEY_FILE` | _undefined_ | a key file (to use a custom certificate). | -| `SERVER_TRUSTED_IPS` | _undefined_ | list of trusted ips which lead to remote client address replacement in [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). | -| `SERVER_WRITE_TIMEOUT` | `0s` | maximum duration for reading the entire request, including the body, set to `0s` to disable, example: `2m`. | +| Variable | Required/Default | Description | +|-------------------------------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `AGENT_ENDPOINT` | _undefined_ | the address to broadcast requests to (example: `127.0.0.1:6800`). When not defined, the broadcaster will only listen on requests. `SERVER_ADDR` or `AGENT_ENDPOINT` is required. | +| `AGENT_RETRY_DELAY` | `60s` | maximum duration for retrying the replay of the request. | +| `DEBUG` | `0` | set to `1` to enable the debug mode (prints recovery stack traces). | +| `HUB_ENDPOINT` | **required** | the address of the the mercure hub to push and fetch messages (example: `https://example.com/hub`). | +| `HUB_GUARD_TOKEN` | =`HUB_TOPIC` | the token used to prevent infinite loop (in case an agent broadcast request to iteself). | +| `HUB_PUBLISH_TOKEN` | =`HUB_TOKEN` | valid JWT token to allow publishing. | +| `HUB_SUBSCRIBE_TOKEN` | =`HUB_TOKEN` | valid JWT token to allow subscribing. | +| `HUB_TARGET` | _undefined_ | name of the mercure's target. Used to secure the communication between the hub and the agent (example `http-broadcast`). This parameter can also be defined with by the queryString of `HUB_ENDPOINT`. example `HUB_ENDPOINT=https://example.com/hub?target=my_target`. | +| `HUB_TIMEOUT` | `5s` | maximum duration for pushing the message into the HUB, set to `0s` to disable. | +| `HUB_TOKEN` | **required** | valid JWT token to allow both publishing and subscribing. On could use this [JWT example] to generate a new one. | +| `HUB_TOPIC` | `http-broadcast` | name of the Mercure's topic to exchange messages. This parameter can also be defined with by the queryString of `HUB_ENDPOINT`. example `HUB_ENDPOINT=https://example.com/hub?topic=my_topic`. | +| `LOG_FORMAT` | `text` | the log format, can be `json`, `fluentd` or `text`. | +| `LOG_LEVEL` | `info` | the log verbosity, can be `trace`, `debug`, `info`, `warn`, `error`, `fatal`. | +| `SERVER_ADDR` | _undefined_ | the address to listen on (example: `0.0.0.0:6081`). When not defined, the broadcaster will only pusblish requests. `SERVER_ADDR` or `AGENT_ENDPOINT` is required. | +| `SERVER_CORS_ALLOWED_ORIGINS` | _undefined_ | a comma separated list of allowed CORS origins, can be `*` for all. | +| `SERVER_INSECURE` | =`DEBUG` | trust everyone in [ProxyProtocol]. | +| `SERVER_READ_TIMEOUT` | `0s` | maximum duration before timing out writes of the response, set to `0s` to disable, example: `2m`. | +| `SERVER_TLS_ACME_ADDR` | `:http` | the address use by the acme server to listen on (example: `0.0.0.0:8080`). | +| `SERVER_TLS_ACME_CERT_DIR` | _undefined_ | the directory where to store Let's Encrypt certificates. | +| `SERVER_TLS_ACME_HOSTS` | _undefined_ | a comma separated list of hosts for which Let's Encrypt certificates must be issued. | +| `SERVER_TLS_CERT_FILE` | _undefined_ | a cert file (to use a custom certificate). | +| `SERVER_TLS_KEY_FILE` | _undefined_ | a key file (to use a custom certificate). | +| `SERVER_TRUSTED_IPS` | _undefined_ | list of trusted ips which lead to remote client address replacement in [ProxyProtocol]. | +| `SERVER_WRITE_TIMEOUT` | `0s` | maximum duration for reading the entire request, including the body, set to `0s` to disable, example: `2m`. + +[JWT example]: https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJteV90YXJnZXQiXSwicHVibGlzaCI6WyJteV90YXJnZXQiXX19.qiIYEsa9ikJAr5U4kGe97sqQsXjxD7_FmWbXaIJKXvk "JWT example" +[ProxyProtocol]: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt "ProxyProtocol" ## Next step diff --git a/pkg/config/options.go b/pkg/config/options.go index 3d9c2e6..1daf1aa 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -52,10 +52,12 @@ type HubOptions struct { SubscribeToken string Timeout time.Duration Topic string + Target string } // NewOptionsFromEnv creates a new option instance from environment // It returns an error if mandatory env env vars are missing +//nolint:gocognit func NewOptionsFromEnv() (*Options, error) { agentRetryDelay, err := time.ParseDuration(getEnv("AGENT_RETRY_DELAY", "60s")) if err != nil { @@ -102,6 +104,17 @@ func NewOptionsFromEnv() (*Options, error) { hubTopic = "http-broadcast" } + hubTarget := os.Getenv("HUB_TARGET") + if hubEndpoint != nil { + if hubTarget == "" { + hubTarget = hubEndpoint.Query().Get("target") + } + + q := hubEndpoint.Query() + q.Del("target") + hubEndpoint.RawQuery = q.Encode() + } + options := &Options{ Debug: getEnv("DEBUG", "0") == "1", Agent: AgentOptions{ @@ -115,6 +128,7 @@ func NewOptionsFromEnv() (*Options, error) { SubscribeToken: getEnv("HUB_SUBSCRIBE_TOKEN", os.Getenv("HUB_TOKEN")), Timeout: hubTimeout, Topic: hubTopic, + Target: hubTarget, }, Server: ServerOptions{ Addr: os.Getenv("SERVER_ADDR"), diff --git a/pkg/config/options_test.go b/pkg/config/options_test.go index ab969ad..f065157 100644 --- a/pkg/config/options_test.go +++ b/pkg/config/options_test.go @@ -21,7 +21,8 @@ func TestNewOptionsFormNew(t *testing.T) { "HUB_SUBSCRIBE_TOKEN": "sub_token", "HUB_TIMEOUT": "1m", "HUB_TOKEN": "token", - "HUB_TOPIC": "HUB_TOPIC", + "HUB_TOPIC": "my_topic", + "HUB_TARGET": "my_target", "LOG_FORMAT": "json", "LOG_LEVEL": "warn", "SERVER_ADDR": "0.0.0.0:81", @@ -54,7 +55,8 @@ func TestNewOptionsFormNew(t *testing.T) { PublishToken: "pub_token", SubscribeToken: "sub_token", Timeout: 1 * time.Minute, - Topic: "HUB_TOPIC", + Topic: "my_topic", + Target: "my_target", }, Server: ServerOptions{ Addr: "0.0.0.0:81", diff --git a/pkg/server/handler.go b/pkg/server/handler.go index 9b9306e..9a7bc8d 100644 --- a/pkg/server/handler.go +++ b/pkg/server/handler.go @@ -32,10 +32,11 @@ func (s *Server) handle(w http.ResponseWriter, r *http.Request) { } // building Hub Request - log.WithFields(log.Fields{"data": string(data), "topic": s.options.Hub.Topic, "hub": s.options.Hub.Endpoint}).Debug("Server: Pushing message to HUB") + log.WithFields(log.Fields{"data": string(data), "topic": s.options.Hub.Topic, "target": s.options.Hub.Target, "hub": s.options.Hub.Endpoint}).Debug("Server: Pushing message to HUB") form := url.Values{} form.Set("topic", s.options.Hub.Topic) + form.Set("target", s.options.Hub.Target) form.Set("data", string(data)) formData := form.Encode() diff --git a/pkg/server/handler_test.go b/pkg/server/handler_test.go index b5d161b..0bb0df7 100644 --- a/pkg/server/handler_test.go +++ b/pkg/server/handler_test.go @@ -31,6 +31,7 @@ func TestHandle(t *testing.T) { Hub: config.HubOptions{ Endpoint: parseSafeURL(httpServer.URL), Topic: "my-topic", + Target: "my-target", GuardToken: "-", }, }) @@ -56,6 +57,7 @@ func TestHandle(t *testing.T) { require.NoError(t, err) assert.Equal(t, "my-topic", form.Get("topic")) + assert.Equal(t, "my-target", form.Get("target")) assert.Equal(t, `{"Method":"POST","Host":"127.0.0.1:8002","Path":"/","Header":{"Accept-Encoding":["gzip"],"Content-Length":["5"],"Content-Type":["text/plain"],"User-Agent":["Go-http-client/1.1"],"X-Forwarded-Host":["127.0.0.1:8002"],"X-Forwarded-Port":["8002"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["`+hostname+`"],"X-Httpbroadcast-Guard":["-"],"X-Real-Ip":["127.0.0.1"]},"Body":"SGVsbG8="}`, form.Get("data")) }