Ops Msg Slip

· anoduck's blog


1     __  __                            ___ _ _
2    |  \/  |___ ______ __ _ __ _ ___  / __| (_)_ __
3    | |\/| / -_|_-<_-</ _` / _` / -_) \__ \ | | '_ \
4    |_|  |_\___/__/__/\__,_\__, \___| |___/_|_| .__/
5                           |___/              |_|
6

Operation: Message Slip #

Using an old and not forgotten application to provide a secure and relatively anonymous contact form for our new website.

This was one of those ideas you mistakenly believe you can throw together in fifteen minutes, and then eventually abandon two hours later, not realizing how many different parts need to come together.

The Technology: Cosgo #

Cosgo is a self contained website contact form / server written in go, that allows webpage guests to message the site owners privately, and securely. As mentioned, it is completely self contained, meaning there is no server configuration that needs to be performed to get it up and running, it will need to be reverse proxied though, but we will save that for later. Users are allowed to encrypt their messages with gpg before sending them, and cosgo does employ a captcha to stop bots from flooding the server. Once messages are typed or pasted in the fields provided, they are then added to a mbox file hosted on the server. Which means you will not need to configure or provide an email server, which gives it a huge advantage security wise, because it completely eleminates the need for an smtp server.

For some reason, it completely went under everyone’s radar, and although it isn’t the most complex go based application, it is absolutely brilliant nonetheless. The code base could use some modernization, but still builds without any problems.

Preparing the the Build #

We are recommending using our fork of the project, because the original does not include the go.mod file used in modern go lang distributions, which is no biggie to create.

1git clone https://github.com/anoduck/cosgo && cd cosgo

It is important to modify the default templates before you build cosgo, as they are packaged along with libraries as part of the cosgo binary. The process of editing the default templates is as follows:

 1if ! [[ -d "static" ]]; then
 2	mkdir -p static
 3fi
 4cd templates
 5if [[ -f "default.html" ]]; then
 6	cp index.html default.backup.html
 7else
 8	cp index.html default.html
 9fi
10"$EDITOR" index.html

Once you have made necessary corrections to the index file, save it, close out the editor, and cd ../ back into the main cosgo repository folder. Lastly, if you have a particular favicon you would like to use, copy it into the static dir in cosgo’s root folder. Once you have done this, you should be ready to build.

Building Cosgo #

Building is pretty straight forward, as long as you have a working version of go and a copy of gnu make. At the time of writing this, the current version of cosgo is cosgo-v0.9.2.X3313695.

1make
2sudo cp bin/cosgo-v0.9.2.X3313695 /usr/local/bin/cosgo

Running cosgo --help will give you an idea of the different options available for use.

Configuration #

The bulk of the footwork to setting up a working cosgo server is in the configuration, which in our situation was not necessarily forseeable.

Creating a GPG public key for cosgo #

I cannot tell you how many times we have generated a GPG key, but however many times that is, it hasn’t been quite enough, because we always forget how we did it. A simple man gpg answers this question for us. Make sure you choose the highest suggested keysize, set it to expire in two years, and generate yourself a hefty password randomly for it.

1gpg --gen-key

Then express due diligence by generating the revocation certificates for it.

1gpg --output revoke.asc --gen-revoke "$KEY_ID"

Once the key and revocation certificate is created, it will be relatively easy to export it to ascii armored format

1gpg --armor --export "$KEY_ID"

Cosgo will need a secure 64bit randomly generated encryption key for it’s web cookie. For this, there are many methods to generate 64bits of data, but choosing openssl to do this for you is an excellent choice.

1openssl rand -base64 64

Configuring Cosgo itself #

Cosgo was written with the intent of running it as an unprivledged user, and in order to do that we will need to create that unprivledged user for it.

1adduser --home /home/cosgo --shell /sbin/nologin --ingroup nobody cosgo

Depending on your implementation you could use cosgo to generate a configuration file for you, but we do not want to do this. We will be running cosgo as a fastcgi service, therefore all configuration commands will be provided from the command line.

Getting it to fly online #

Our server implementation of choice is OpenBSD’s native httpd server. We have used it for a couple of years, if not close to a decade now. We have never encountered any reduction in performance, nor encountered a situation it could not handle, and it has always worked flawlessly without any security vulnerabilities we are aware of. Not too long ago, OpenBSD’s default httpd server was in fact Apache2. If a server software is good enough to convince the maintainers of OpenBSD to ditch Apache in favor of, we believe it is good enough for our use.

Preparing your httpd config #

Assuming you already have an httpd server configured and running, we will be adding cosgo to that server as a virtual server. So, in order to do this ensure the following line of code is included in your /etc/httpd.conf file. A quick sudo cat /etc/httpd.conf | grep include can show you if it is included or not. The line should read something like, include /etc/httpd.conf.d/*.conf. Which tell httpd to additionally load all configuration files located in the httpd.conf.d directory. Using this method to load additional virtual server configuration files will allow us to simply server management.

Creating Cosgo’s configuration file. #

Now open up /etc/httpd.conf.d/cosgo.conf in your text editor and begin to work out your configuration. First you will need to create a http configuration which will allow acme to run it’s challenge to generate your ssl certificate and keys. All other connections to the server through port 80 will be redirected to the https service on port 443.

server "cosgo.example.com" {
	alias www.cosgo.example.com
	listen on egress port 80
	location "/.well-known/acme-challenge/*" {
		root "/acme"
		request strip 2
	}
	location * {
		block return 302 "https://$HTTP_HOST$REQUEST_URI"
	}
}

Now it’t time for the remainder of the virtual server configuration. Much of the above configuration will be reused except for the port number and several required additions. Since cosgo is running as a fastcgi service, it might seem pointless to include a root directive, but it is required to host the challenge and response for letsencrypt ssl generation.

server "cosgo.example.com" {
	alias www.cosgo.example.com
	listen on egress tls port 443
	root "/htdocs/cosgo"
	directory { index index.html }
	tls {
		certificate "/etc/ssl/example.com.fullchain.pem"
		key "/etc/ssl/private/example.com.key"
	}
	location "/.well-known/acme-challenge/*" {
		root "/acme"
		request strip 2
	}
	location "/" {
		fastcgi {
			socket tcp 127.0.0.1 8080
		}
	}
	errdocs "/htdocs/errdocs"
	log { access "cosgo-access.log", error "cosgo-error.log" }
}

Adding cosgo to your acme-client configuration #

If you have not noticed by now, Cosgo will not be hosted from the top level domain, but rather a subdomain. Therefore it will use the same certificate as the top level domain, which means it only needs to be added to the acme-client configuration as an alternative name.

authority letsencrypt {
	api url "https://acme-v02.api.letsencrypt.org/directory"
	account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
	api url "https://acme-staging-v02.api.letsencrypt.org/directory"
	account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

domain example.com {
	alternative names { www.example.com, cosgo.example.com }
	domain key "/etc/ssl/private/example.com.key"
	domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
	sign with letsencrypt
}

Getting Cosgo to run #

Cosgo will need to run as a system service as the unprivledged user recreated previously.

; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
;  - Shell expansion ("~" or "$HOME") is not supported.  Environment
;    variables can be expanded using this syntax: "%(ENV_HOME)s".
;  - Quotes around values are not supported, except in the case of
;    the environment= options as shown below.
;  - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
;  - Command will be truncated if it looks like a config file comment, e.g.
;    "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".
;
; Warning:
;  Paths throughout this example file use /tmp because it is available on most
;  systems.  You will likely need to change these to locations more appropriate
;  for your system.  Some systems periodically delete older files in /tmp.
;  Notably, if the socket file defined in the [unix_http_server] section below
;  is deleted, supervisorctl will be unable to connect to supervisord.

; The sample program section below shows all possible program subsection values.
; Create one or more 'real' program: sections to be able to control them under
; supervisor.

[program:cosgo]
command=/usr/local/bin/cosgo -c /etc/cosgo/he.json	; the program (relative uses PATH, can take args)
process_name=cosgo-he 		; process_name expr (default %(program_name)s)
numprocs=1                    		; number of processes copies to start (def 1)
directory=/tmp						; directory to cwd to before exec (def no cwd)
;umask=022                     ; umask for process (default None)
;priority=999                  ; the relative start priority (default 999)
autostart=true                		; start at supervisord start (default: true)
;startsecs=10                  ; # of secs prog must stay up to be running (def. 1)
;startretries=3                ; max # of serial start failures when starting (default 3)
;autorestart=unexpected        ; when to restart if exited after running (def: unexpected)
;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT               ; signal used to kill process (default TERM)
;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
;killasgroup=false             ; SIGKILL the UNIX process group (def false)
;user=chrism                   ; setuid to this UNIX account to run the program
;redirect_stderr=true          ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)
;stdout_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false   ; emit events on stdout writes (default false)
stdout_syslog=false           ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)
;stderr_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false   ; emit events on stderr writes (default false)
stderr_syslog=true           ; send stderr to syslog with process name (default false)
;environment=A=1,B=2           ; process environment additions (def no adds)
;serverurl=AUTO                ; override serverurl computation (childutils)

; The sample eventlistener section below shows all possible eventlistener
; subsection values.  Create one or more 'real' eventlistener: sections to be
; able to handle event notifications sent by supervisord.

;[eventlistener:theeventlistenername]
;command=/bin/eventlistener    ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1                    ; number of processes copies to start (def 1)
;events=EVENT                  ; event notif. types to subscribe to (req'd)
;buffer_size=10                ; event buffer queue size (default 10)
;directory=/tmp                ; directory to cwd to before exec (def no cwd)
;umask=022                     ; umask for process (default None)
;priority=-1                   ; the relative start priority (default -1)
;autostart=true                ; start at supervisord start (default: true)
;startsecs=10                  ; # of secs prog must stay up to be running (def. 1)
;startretries=3                ; max # of serial start failures when starting (default 3)
;autorestart=unexpected        ; autorestart if exited after running (def: unexpected)
;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT               ; signal used to kill process (default TERM)
;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
;killasgroup=false             ; SIGKILL the UNIX process group (def false)
;user=chrism                   ; setuid to this UNIX account to run the program
;redirect_stderr=false         ; redirect_stderr=true is not allowed for eventlisteners
;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)
;stdout_events_enabled=false   ; emit events on stdout writes (default false)
;stdout_syslog=false           ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)
;stderr_events_enabled=false   ; emit events on stderr writes (default false)
;stderr_syslog=false           ; send stderr to syslog with process name (default false)
;environment=A=1,B=2           ; process environment additions
;serverurl=AUTO                ; override serverurl computation (childutils)

; The sample group section below shows all possible group values.  Create one
; or more 'real' group: sections to create "heterogeneous" process groups.

;[group:thegroupname]
;programs=progname1,progname2  ; each refers to 'x' in [program:x] definitions
;priority=999                  ; the relative start priority (default 999)