19 KiB
Fear and Loathing in OpenBSD, or my experiences with OpenBSD
The other day I woke up and I thought "I'm going to migrate my server to OpenBSD for absolutely no reason". And so I did. The operating system have been a pain and a pleasure simultaneously, and in this page I intend to give my experiences with it.
I will be updating this page as I have more experiences with OpenBSD. So add this page to your booksmarks!
Last update. 2022-06-09
For questions or comments on this article feel free to reach me out at teru-sama [at] riseup [dot] net
Installation
The installation process was pretty straightforward, it was just enter, enter, enter, altough I had to connect an ethernet cable for it to download some necessary firmware (so I could use the network card) and thus, the wireless connection.
Setting up services
httpd(8)
The website you're in is the website I care the most, kill-9 can wait because that's only a website in which i complain about things. Complaining about everything is not good because in this life, well, in Ozzy Osbourne words, "Learn how to love and forget how to hate". So I'd rather focus on ebin.city and suragu.net for the time.
OpenBSD ships with the httpd
web server. A really simple and very
secure http server. The main config file is /etc/httpd.conf
which
has a very simple, human readable syntax.
One of the features of this webserver is that chroots to a
directory. Meaning that, to the web server, anything before the given
directory (/var/www
by default), does not exist. So if a vulnerability
is found, the attacker can't do much things, as the attacker can't go
beyond /var/www
.
slowcgi(8)
OpenBSD comes out-of-the-box with a FastCGI implementation, which is
very simple to use. you only have to add fastcgi
at the desired site
in httpd.conf
. The following configuration file is enough to
execute CGI scripts.
server "suragu.net" {
listen on * port 80
root "sites/suragu.net"
location "/*.cgi" {
fastcgi
root "sites/suragu.net"
}
}
But not so fast! Remember that httpd runs in a chroot? Well, your CGI
apps won't work by default, because the chroot lacks the binaries that
are necessary to execute the program. So if your CGI appliaction is a
perl script, you'll have to do something like cp /usr/bin/perl
/var/www/bin
. If your Perl script uses third-party modules, you'll
have to copy them to anything that is in @INC
, so
/usr/var/www/usr/lib/perl5
or something like that. Also your chroot
will lack all the core utils. I just installed plan9port and copied
the files to /var/www/bin
, which is more than enough.
As mentioned before. I haven't been able to setup werc in httpd. Not sure why. I might try to reinstall it some other day. But I guess I'd have to use another server to do that.
As of 2022-06-02 I got werc to werk under httpd. The config file is a bit weird, but it worked. Altough I had to install an older werc version because the most recent one had some issues. This is the config that worked. Thanks to solene in IRC for the pattern!
server "kill-9.xyz" {
alias "www.kill-9.xyz"
listen on 127.0.0.1 port 1340
listen on * tls port 443
tls {
certificate "/etc/ssl/kill9cert.pem"
key "/etc/ssl/kill9key.pem"
}
# If there's a dot in the URL (i.e. a file extension, don't run it as
# a CGI script.)
location match "%s*%.%s*" {
root "/werc/sites/kill-9.xyz"
no fastcgi
}
location match "/" {
fastcgi param SCRIPT_NAME "/werc/bin/werc.rc"
fastcgi param SCRIPT_FILENAME "/werc/bin/werc.rc"
fastcgi param DOCUMENT_ROOT "/werc/sites/kill-9.xyz"
}
root "/werc/sites/kill-9.xyz/"
}
And then I tried to install cgit. When clonning from httpd I got an
error I've never got before. Something like "Recieved HTTP/0.9 when
not allowed". I don't know what the hell httpd(8)
meant by that. But
it was fixed by nuking the repos and pushing them again from
scratch. I guess it had something to do with file corruption. But you
have to be crazy on acid to think that sending HTTP/0.9 is a good way
to tell a file is corrupted.
Darknets
Not much difference from how you'd install Tor & I2P in a Linux
machine. Just install the tor and i2pd packages using pkg_add(1)
and
configure them normally. I had to copy the old private keys from my
old machine to the new server. But that was not a big issue. I also
migrated from the classic I2P, written in Java to i2pd. Not because I
dislike the Java I2P, I think it is, along with Freenet, the only good
software written in Java. I just wanted to keep the server as light as
possible. And i2pd is way lighter than Java I2P.
Programming in OpenBSD
OpenBSD claims to be the most secure UNIX out there. This is probably true as it has much features, such as the W^X thing. Which means memory can be written or executed, but not both. Basically an attacker could not execute a buffer overflow attack. Because he can only write. But not execute it. This thing was introdouced in 2003.
pledge(2)
OpenBSD takes security very seriously. This was the reason to be of
the pledge()
syscall. Pledge does, as defined by the manpage:
"restrict system operations". This is the prototype:
pledge(const char *promises, const char *execpromises);
Check the manpage for the possible promises. This is a quick example:
#include <stdio.h> /* printf() */
#include <unistd.h> /* pledge() */
#include <sys/stat.h> /* chmod() */
int
main(void)
{
/* stdio promise allows basic input output operations. Check the
,* manpage for the syscalls this promise allows. */
pledge("stdio", "");
chmod("/etc/passwd",0644);
return 0;
/* This code will crash at runtime. the "stdio" promise doesn't
,* allow the chmod() syscall. */
}
Running that code will prodouce the following output:
Abort trap (core dumped)
This is very useful, imagine that for example, some injects, somehow,
malicious code in your cat(1)
program. Sending data to a remote
server. As cat didn't pledge("inet",...)
, cat won't be able to
create a connection.
unveil(2)
I like this syscall more than pledge(2)
according to the manpage:
"unveil parts of a restricted filesystem view" This means that, except
for the file specified in the unveil()
calls, wont' exist for the
program. Consider the following code and its output:
#include <unistd.h> /* read(), write(), unveil() */
#include <fcntl.h> /* open() and flags */
#include <string.h> /* strerror() */
#include <errno.h> /* errno variable */
#include <stdio.h> /* fprintf() */
int
main(void)
{
/* This program can exclusively open /etc/httpd.conf for reading. */
unveil("/etc/httpd.conf","r");
/* This call disables further calls to unveil() */
unveil(NULL, NULL);
/* What happens if we try to open another file? */
int fd = open("/etc/passwd", O_RDONLY);
if(fd == -1) {
fprintf(stderr,"Error opening file: %s\n",
strerror(errno));
_exit(-1);
}
char buf[8192];
int bytes = read(fd, buf, 8192);
write(STDOUT_FILENO, buf, bytes);
return 0;
}
Output: Error opening file: No such file or directory
Yes, my /etc/passwd file exists.
perl(1)
Perl is the only scripting language that OpenBSD ships. And they have
their reasons you can read here. This means that Perl comes with
support for the OpenBSD weird features. This means that you can call
pledge(2)
and unveil(2)
from your Perl scripts! Here's an example
of that:
#!/usr/bin/perl
# The syscalls come as modules, so you have to import them, the
# subroutines are exported by default.
use OpenBSD::Pledge;
use OpenBSD::Unveil;
# The manpage says that, without the "stdio" promise, perl is useless,
# so it is called by default no matter what you do.
pledge("inet rpath wpath unix"); # Some example promises...
unveil("/etc/httpd.conf","r");
unveil("/etc/pf.conf","r");
unveil(); # Restrict further calls to unveil()
strlcpy(3) and strlcat(3)
The well known strncpy(3)
and strncat(3)
functions copy no more
than n
characters, but these functions are not guaranteed to add the
'\0' at the end of the string. strlcpy(3)
and strlcat(3)
guarante
that the string ends with '\0'
Makefiles
For some reasons, I wanted to rewrite the Makefile of one of my
programs. And I discovered that BSD make is much better than GNU
make. With GNU Make you have to declare pattern rules. And weird
syntax. And you have to write similar makefiles for each program. In
OpenBSD this is not necessary because makefile has some kind of
"templates". This is a perfectly working makefile, with clean
and
install
targets
PROG = sakisafecli
SRCS += funcs.c sakisafecli.c
MAN = sakisafecli.1 sakisafeclirc.5
LDADD = -lssl -lz -lpthread -lnghttp2 -lcurl -lconfig -lcrypto -L/usr/local/lib
CPPFLAGS = -I/usr/local/include
BINDIR=/usr/local/bin
.include <bsd.prog.mk>
This makefile also works in Linux, but using the bmake
command
instead of make
. It also works in FreeBSD but you'd have to repleace
CPPFLAGS with CFLAGS.
Libraries
OpenBSD, unlike every Linux distribution out there, thinks about everyone. And when you download a library through the package manager, it will install the shared objects (for dynamic linking), the header files (which means, no -dev/-devel packages) AND the .a files. For static linking!
Software and the power it holds
OpenBSD comes with a lot of software that should be enough for your
normal tasks. But, it's not like OpenBSD grabbed some code and put it
in the code, no, they wrote their own versions of popular
software. And "ported" them to OpenBSD, so the software that comes
with the operating system uses the security features, they call
pledge()
. and stuff like that.
mg(1)
This is a Emacs clone. For the people who, for any reason, can't (or
don't want) to run GNU Emacs. This clone is pretty complete, the only
thing it lacks, regarding emacs, is emacs lisp support and syntax
highlighting. But this is a good nano(1)
, ed(1)
or vi(1)
repleacement.
signify(1)
GNU Privacy Guard is kinda heavy, and we don't have any other decent
OpenPGP implementation. This is the reason of why the OpenBSD devs
created signify(1)
, a tool to cryptographically sign and verify
files and messages. And this is the way OpenBSD images are
verified. It's pretty simple to use:
# Generate pub and sec key. They have to have the same name. Only
# changing the file extension
$ signify -G -c "raoul's signify key" -p raoul.pub -s raoul.sec
# Sign a file/message
$ echo "Hello world!" > message.txt
$ signify -S -s raoul.sec -m message.txt
# Verify file/message
$ signify -V -p raoul.pub -m message.txt
# Further examples in the manpage.
tmux(1)
Tmux, the legendary terminal multiplexer, that is way better than screen, was initially developed for OpenBSD. I don't think I have to talk a lot about tmux because everyone knows it. tmux in OpenBSD comes with all the security features too.
doas(1)
This is a repleacement for sudo that has been developed by OpenBSD. it
has also emerged in the linux community. Altough it works best in
OpenBSD. I have some issues getting doas to work in Debian, but not in
Void Linux. doas is very simple to configure. No need to add yourself
to a group or anything like that. You can simply add this to
/etc/doas.conf
permit nopass keepenv raoul as groq
# Allow user raoul to execute commands as groq. Keeping all the
# environment variables.
# raoul can't execute commands as any user that is not groq.
permit nopass keepenv qrog
# qrog can execute commands as any user.
openrsync(1)
Sometimes you want to syncronize files between your computers and
servers. And then you realize cp(1)
is kinda bad for that and tar
isslow. Then you discover rsync
and that just works. But this wasn't
the case for the OpenBSD guys, they wanted a rsync implementation
under the BSD license. So they wrote =openrsync=. This works just like
rsync and, according to the manpage: "openrsync is compatible with
rsync protocol version 27 as supported by the samba.org implementation
of rsync". Meaning that if you don't have openrsync in other server,
it will just worke, and vice versa. This is an example of usage of
openrsync.
openrsync --rsync-path=openrsync -av Xanopticon remote_server:/var/www/files/Music
As I don't have rsync
installed in the remote server, but I have
openrsync
, I specify that the path of rsync
is openrsync
. This
way it just works.
acme-client(1)
Today I recieved a mail telling me that the kill -9 certificate
expired. "Fuck's sake" — I inmediatly thought. "I have to renew
it". But I was not going to install certbot in this OpenBSD server. So
I had to find a way. I remembered that OpenBSD ships with
acme-client
. A program that helps you to generate your certificates
for TLS connections. And they can be signed with the Let's Encrypt
certificate authority. The config file is pretty simple and
intuitive. You can copy and paste it from
/etc/examples/acme-client.conf
and only configure the revelant part
that would be your domain. In my case I have it like this:
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
domain suragu.net {
alternative names { "www.suragu.net", "files.suragu.net" }
domain key "/etc/ssl/suragu.key" ecdsa
domain full chain certificate "/etc/ssl/suragu.crt"
sign with letsencrypt
}
I used to use wildcard certificates. That were valid to any suragu.net
subdomain. But I couldn't make them work in acme-client
. But as
acme-client
is less of a pain in the ass than certbot. I can
certainly just modify the configuration file each time I create a new
subdomain. And that doesn't happen too often.
Documentation
OpenBSD takes documentation very seriously. So seriously, if a manpage
is lacking in a sys util, it's considered a bug. So everything that
comes with your OpenBSD installation is very well documented. Config
files have their own manpages section, the section 5. so you can learn
how to write httpd config files by running man httpd.conf
This is
something more developers should do. There's also the /etc/examples
directory which contains examples of most config files that you'd want
to setup. Those file are commented and everything. But /etc/examples
always lacks the config file I want or doesn't help at all.
Backups
I am a self-proclaimed good sysadmin. This means I should be able to
do backups and restore them. Not gonna lie, before OpenBSD i haven't
had any backup. Though I have heard that you can do incremental
backups with tar(1)
. I guess this could be useful. But OpenBSD comes
with it's features and things. These tools are dump(8)
and
restore(8)
. Those were a bit confusing to me until I learned how to
use them properly. You can read the manpages for dump(8)">dump(8)
and
restore(8)">restore(8)
which explains pretty well how to use the software. At
least that's what should have happened. Because it didn't. For some
reason OpenBSD insists in using tapes in 2022. So yeah, apparently the
-a flag is mandatory these days. You can use this command to backup a
directory, /etc
in this case.
# 0 means it is a level 0 backup. Next backup should be level 1, then
# 2... Read the manpage for more details.
$ doas dump -0uaf backup_etc.dump /etc
This will take some time depending how big the directory is. /etc
is
usually not too big so this example will not take a lot of time.
After 1 hour of wondering why my backup wasn't working, i discovered
that restore(8)
takes everything as relative paths. Meaning that it
will restore to the directory you're in, so if you do restore
-xfbackup_etc.dump /etc/httpd.conf
, it will restore it to
$PWD/etc/httpd.conf
, not to /etc/httpd.conf
. So you should cd to /
when restoring backups, something like this:
cd /
restore -xf /var/backups/backup_etc.dump /etc/
Network
First I tried to use the wifi card my computer came with. But for some
reason it kept sayin wpi0: device timeout
. Leaving my computer
without connection. So I had to connect the Ethernet cable. And I
thought that that would solve the connection problem. But today I woke
up and my computer did not have internet connection. But it had LAN
connection. Not sure what happened. And well, that's the reason of why
my site was down. I'll try to fix it.
Firewall
OpenBSD comes with a firewall, called pf, which stands for Packet Filter. As every other software developed by OpenBSD, it uses its simple config file.
I used the firewall to deny ssh requests from every IP address except my local network (that is, 192.168.0.0/16) and from my static IPv6 address.
At first the rules were not working for the IPv6 address, because I don't have IPv6 at home, I use a WireGuard interface for that for that, and pf didn't know that. So I had to specify that those rules should also apply to the wireguard interface, like this:
pass in on {egress wg0} ...
And that just worked.
Yiou can also limit the connection of an user. For example, the following line will disable all the connection for the user 'groq':
# Block outcoming connections to user raoul.
block return out proto {tcp udp} user raoul
# Block incoming connection to user raoul. Not sure how useful this is.
block return in proto {tcp udp} user raoul
And I've also added some IP addresses that have tried to exploit,
ehem, WordPress vulnerabilities in my webserver. I added them to a
file, /etc/spammers
. And used a pf(8)
feature to block all of
them. And I also wanted that ssh would be disabled for everyone except
for the machines in the Local Area Network. So only people in my
network could ssh to my server. pf.conf has a very readable syntax,
which makes this very easy:
# Good/Dreaded IP ranges
table <localnet> const { 192.168.0.0/16 }
table <spammers> const file "/etc/spammers"
# Block spammers requests to the server. Also requests to spammers.
block in on { egress wg0 } from <spammers> to any
# Allow SSH access from the LAN
block return in log proto tcp from any to port ssh pass in on egress
proto tcp from <localnet> to port 22