We wanted to start using Rundeck more to replace an aging Jenkins system. However one thing was really missing from Rundeck, which was the ability to accept an incoming Webhook from Github. This boggled my mind for a bit, why isn’t this simply accepted. If we have a parameter called payload we’re good to go as a POST right. All I needed was something that could convert between the POST that Github sends as a Webhook and what Rundeck wants through their API.

I decided to solve this using nginx-njs which is a Javascript implementation inside nginx instead of using for example Lua.

What this eventually allows you to do is configure Github to use an URL like the following:

https://user:pass@rundeck.yourcool.domain/build/5fdf3803-d19d-4436-91d3-2d700904dbfb

# or using a name you mapped

https://user:pass@rundeck.yourcool.domain/buildByName/mycooljob

I hope this was helpful for someone trying to solve the same problem.

Setup

You’ll need nginx with njs support, their upstream repo’s for Debian have this out-of-the box. See here for how to get the repository setup.

apt-get install nginx nginx-module-njs

nginx

The following changes need to be made to /etc/nginx/nginx.conf

load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;

http {
    js_include /etc/nginx/webhook.js;
    js_set $webhook payload_convert;
    js_set $jobid getJobID;
    js_set $jobUUIDFromName getBuildByName;
}

And we need the vhost config, e.g. /etc/nginx/conf.d/rundeck.conf

server {
    listen 80 default_server; 
    listen [::]:80 default_server;

    # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    return 301 https://$host$request_uri;
}


server {
    listen       443 ssl;
    server_name  rundeck.yourcool.domain;

    #ssl_certificate yourcertshere;
    #ssl_certificate_key yourkeyhere;


    location /buildByName {
    	rewrite ^(/buildByName/.*) /build/$jobUUIDFromName last;
    }

    location /build {
    	auth_basic "Rundeck Build Server";
	# guide how to create this is here:
	# https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
	auth_basic_user_file /etc/nginx/htpasswd;

        mirror /_NULL;                    # Create a copy of the request to capture request body
        client_body_in_single_buffer on;  # Minimize memory copy operations on request body
        client_body_buffer_size      256k; # Largest body to keep in memory (before writing to file)
        client_max_body_size         256k;

        proxy_pass_request_headers off;
	proxy_pass_request_body off;
	proxy_set_body $webhook;

	# best create a fixed token for Rundeck to allow from here, can be done as described in:
	# https://docs.rundeck.com/docs/api/#token-authentication using the framework.properties
	proxy_set_header 'X-Rundeck-Auth-Token' 'yourtoken';

	proxy_set_header 'Accept' 'application/json';
	proxy_set_header 'Content-Type' 'application/json';

        proxy_pass http://localhost:4440/api/31/job/$jobid/run;
    }

    location = /_NULL {
        internal;
        return 204;
    }

    # actually proxy Rundeck
    location / {
        proxy_pass http://localhost:4440;
    }
}

And finally the /etc/nginx/webhook.js

// grab the payload from the incoming POST
function payload_convert(req) {
	// place it in a try/catch in case the JSON can't create the object
	try {
		if ( req.variables.request_body.length > 0 ) {
			var buf = {};
			buf['options'] = {};
			buf['options']['payload'] = req.variables.request_body;

			return JSON.stringify(buf);
		} else {
			req.return(500, "Body cannot be empty"); 
		}
	} catch (e) {
		// log the exception
		req.log(e);
		req.return(500, "Something went horribly wrong"); 
	}
}

// get the job id
function getJobID(req) {
	return req.uri.replace('/build/', '');
}

function getBuildByName(req) {
	// list of jobs and their uuids
	var jobs = {
		'mycooljob': '5fdf3803-d19d-4436-91d3-2d700904dbfb'
	};

	var jobName = req.uri.replace('/buildByName/', '');	
	
	return jobs[jobName];
}

Rundeck

Ensure you have a Token set you can always use by nginx, e.g. create /etc/rundeck/tokens.properties

Note: the user has to exist!

nginx: my-super-duper-token

And inside /etc/rundeck/framework.properties we add:

rundeck.tokens.file=/etc/rundeck/tokens.properties

Rundeck Job

Ensure your job accept an option called payload as a text field.