Payload URL generation of webhooks ignores DRONE_HOST

When using the docker-compose.yml file shown below, Gogs is reachable at localhost:3000 on the host, and Drone at localhost:8000. As a result, when a repo is activated, the payload URL in the webhook is set to http://localhost:8000/hook?access_token=eyJhb.... Then, webhook requests are not properly processed. It can be manually modified to http://localhost:8000/hook?access_token=eyJhb..., but this is not an elegant solution.

I checked the sources of Drone, and I saw that the payload URL is defined in drone/server/repo.go#L76-L80:

link := fmt.Sprintf(
	"%s/hook?access_token=%s",
	httputil.GetURL(c.Request),
	sig,
)

Multiple parts of the http.Request are evaluated to set the URL. At drone/shared/httputil/httputil.go#L50-L67:

func GetHost(r *http.Request) string {
	switch {
	case len(r.Host) != 0:
		return r.Host
	case len(r.URL.Host) != 0:
		return r.URL.Host
	case len(r.Header.Get("X-Forwarded-For")) != 0:
		return r.Header.Get("X-Forwarded-For")
	case len(r.Header.Get("X-Host")) != 0:
		return r.Header.Get("X-Host")
	case len(r.Header.Get("XFF")) != 0:
		return r.Header.Get("XFF")
	case len(r.Header.Get("X-Real-IP")) != 0:
		return r.Header.Get("X-Real-IP")
	default:
		return "localhost:8080"
	}
}

Note that, in the use case below, the first case statement is enough: r.Host. Nevertheless, none of the cases above checks DRONE_HOST. In a use case without any reverse proxy, nor DNS server besides the embedded one in Docker, I think that DRONE_HOST is the only way to properly set the payload URL. This should contain a hostname which is reachable from the gogs container, instead of the route used by the host (which is where the request for activation is generated).

According to the configuration below, this modification to drone/server/repo.go#L76-L80 solves the problem:

link := fmt.Sprintf(
	"%s/hook?access_token=%s",
	Config.Server.Host,
	sig,
)

However, this modification involves getting rid of httputil.GetURL(c.Request), and I am sure that other use cases will break. Then, I’d like to ask for opinion before opening a PR. Which is the recommended procedure to decide when to process the request and when to use the variable?


docker-compose.yml:

version: '2'

services:
  gogs:
    container_name: gogs-srv
    image: gogs/gogs:latest
    ports: [ "127.0.0.1:3000:3000" ]
    volumes: [ "./data_gogs:/data" ]

  drone-server:
    depends_on: [ gogs ]
    image: drone/drone:elide
    restart: always
    ports: [ "127.0.0.1:8000:8000" ]
    volumes: [ "./data_drone:/var/lib/drone" ]
    environment:
      - DRONE_OPEN=true
      - DRONE_HOST=http://drone-server:8000
      - DRONE_GOGS=true
      - DRONE_GOGS_URL=http://gogs:3000
      - DRONE_SECRET=${DRONE_SECRET}
      - DRONE_NETWORK=net_build

  drone-agent:
    depends_on: [ drone-server ]
    image: drone/drone:latest
    command: agent
    restart: always
    volumes: [ "/var/run/docker.sock:/var/run/docker.sock" ]
    environment:
      - DRONE_SERVER=ws://drone-server:8000/ws/broker
      - DRONE_SECRET=${DRONE_SECRET}

networks:
  default:
    external:
      name: net_ci

Start procedure (script):

$ docker network create net_ci
$ docker network create net_build
$ docker-compose up
$ docker network connect net_build elide-gogs

Cleanup proceduce (script):

docker-compose rm
docker network prune

A side question:

In order to test the modification I compiled drone with go install github.com/drone/drone/drone. Then, I built a new image with drone/master/Dockerfie, but it wouldn’t work: drone-server_1 | standard_init_linux.go:178: exec user process caused "no such file or directory". I had to change FROM centurylink/ca-certs to FROM golang. Is this correct?

1 Like

DRONE_HOST was implemented as a last-minute fix for 0.6 because GetHost did not work for setting the commit status. This is because in many cases the agent was connecting using an internal docker hostname instead of the domain name / IP. So without the DRONE_HOST we see the following error:

error setting commit status

Since it is now mandatory (as of 0.6) to explicitly set DRONE_HOST I would expect that we end up removing GetHost completely from the codebase.

Since GetHost is only called by httputil.GetURL, I checked where this is used:

  • server/
    • repo.go
      • 78: httputil.GetURL(c.Request),
      • 186: remote.Deactivate(user, repo, httputil.GetURL(c.Request))
      • 204: host := httputil.GetURL(c.Request)
    • hook.go
      • 223: uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
      • 237: Link: httputil.GetURL(c.Request),
    • badge.go
      • 81: url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, builds[0].Number)
    • build.go
      • 210: uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
      • 224: Link: httputil.GetURL(c.Request),
      • 335: uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
      • 495: Link: httputil.GetURL(c.Request),
  • remote/
    • bitbucket/bitbucket.go
      • 43: redirect := httputil.GetURL(req)
    • gitlab/gitlab.go
      • 110: RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
      • 628: RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(r)),
    • github/github.go
      • 93: config := c.newConfig(httputil.GetURL(req))

Then, should I replace all of them with Config.Server.Host? Or, might it be better to just replace GetHost with the domain and port taken from Config.Server.Host, but keeping GetURL to detect the scheme?

1 Like

I’m currently having the same problem. Any news about it? Is a pull request planned or is there any workaround?

No news. The workaround explained above worked for me, but it was not added to any PR. The code base is still using GetURL: https://github.com/drone/drone/blob/master/server/repo.go#L81-L85

I just found out another workaround.
In my docker-compose file I use the aliases field in my network to expose the drone service inside the swarm with the same dns name I use from the outside, then I used the DRONE_SERVER_ADDR variable to start drone on the port 80.

drone-server:
image: drone/drone:0.8

ports:
  - 8080:80
  - 9000
#volumes:
#  - ./drone:/var/lib/drone/
restart: always
environment:
  - DRONE_OPEN=true
  - DRONE_HOST=http://drone-server
  - DRONE_SERVER_ADDR=:80
  - DRONE_GITEA=true
  - DRONE_GITEA_URL=http://gitea-server:3000
  - DRONE_SECRET=${DRONE_SECRET}
  - DRONE_DATABASE_DRIVER=mysql
  - DRONE_DATABASE_DATASOURCE=user:[email protected](drone-db:3306)/drone?parseTime=true
networks:
  gitea:
    aliases:
      - my.dns.name

I met similar issue with this
my drone server is running behind an https proxy. When I set DRONE_HOST=https://my-drone.com, and when I enable github repository in drone web UI, drone still create github webhook as http://my-drone.com/hook. Which does not work

is this issue working on? or should I create a Github issue?

You need to set X-Forwarded-For and X-Forwarded-Proto when running Drone behind a reverse proxy so that Drone can ascertain its address form the incoming HTTP request. We provide sample nginx, apache and caddy configurations that you can reference in our docs: http://docs.drone.io/setup-with-nginx/

I also provided additional details in this previous thread: