Whilst generally not supported by the OpenBSD project, there can be many benefits to setting up an OpenBSD install to be read-only. For example it might be for a router or point of sale device that may encounter power-outage and be unable to shutdown cleanly. If this were to happen for a standard install, an fsck(8) would happen on the next boot. Typically this process would not take too long, but if it detects errors and it cannot automatically solve them, the boot process may be interrupted. Not ideal for a router use-case.
I personally use this setup on a Raspberry Pi 3 to provide a simple SOCKS5 proxy (using the inbuilt OpenSSH) to lock some overly chatty Windows machines behind as well as providing a tunnel to a non-consumer UK ISP so I can access sites such as The Pirate Bay. So far it has proven to be remarkably robust.
In general the process is quite straight-forward. Any partition that needs write access during the typical operation of OpenBSD needs to be mounted using a memory file-system rather than using the physical disk. Fortunately OpenBSD provides mount_mfs(8) which serves this purpose nicely. These key partitions are /tmp, /var and /dev. If you are installing OpenBSD from scratch, you can skip creating the /tmp and /var partitions entirely since they will not be needed.
In order for OpenBSD to start using the memory partitions, the /etc/fstab file should be modified to remove the existing /var and /tmp mounts and instead mount using MFS instead. At this point we should also set all the original partitions read-only. For example this original file:
527591207f2daee9.b none swap sw
527591207f2daee9.a / ffs rw 1 1
527591207f2daee9.k /home ffs rw,nodev,nosuid 1 2
527591207f2daee9.d /tmp ffs rw,nodev,nosuid 1 2
527591207f2daee9.f /usr ffs rw,nodev 1 2
527591207f2daee9.g /usr/X11R6 ffs rw,nodev 1 2
527591207f2daee9.h /usr/local ffs rw,wxallowed,nodev 1 2
527591207f2daee9.j /usr/obj ffs rw,nodev,nosuid 1 2
527591207f2daee9.i /usr/src ffs rw,nodev,nosuid 1 2
527591207f2daee9.e /var ffs rw,nodev,nosuid 1 2
Should end up looking similar to the following (Notice the ro attribute has replaced rw on the disk partitions):
527591207f2daee9.b none swap sw
527591207f2daee9.a / ffs ro 1 1
527591207f2daee9.k /home ffs ro,nodev,nosuid 1 2
527591207f2daee9.f /usr ffs ro,nodev 1 2
527591207f2daee9.g /usr/X11R6 ffs ro,nodev 1 2
527591207f2daee9.h /usr/local ffs ro,wxallowed,nodev 1 2
527591207f2daee9.j /usr/obj ffs ro,nodev,nosuid 1 2
527591207f2daee9.i /usr/src ffs ro,nodev,nosuid 1 2
swap /tmp mfs rw 0 0
swap /dev mfs rw,-P/dev 0 0
swap /var mfs rw,-P/var,-s32m 0 0
The order of the swap entries are important. This is because the -P flag for /dev and /var is used to prepopulate the memory partition with a copy of the files on the phsical disk. It does this by first mounting it on /tmp before copying the files. This means that /tmp must be mounted first.
Due to the fact that initial files in the /var is only a little smaller than the default MFS size of 8 Megs, you should consider adjusting the size of this memory partition via the -s argument. In the example above, 32 Megs was specified. Remember that files are first copied into /tmp which means that also needs to be large enough to fit these temporary files. This may be important if you are hosting a large website in there.
With this completed, before we reboot the machine we need to clean up the original partitions a little to remove any special files (such as UNIX sockets) which cannot be correctly copied onto the memory partitions. Otherwise a warning will display in the system boot logs. These files can be removed using the following:
# rm /dev/log
# rm /dev/slaacd.sock
# rm /var/run/cron.sock
# rm /var/run/ntpd.sock
# rm /var/run/smtpd.sock
These special files will get recreated on the MFS partitions by their respective daemons upon next startup. You may also want to consider disabling some of them if they aren't relevant to the intended purpose for the applicance.
The final step that we need to do to prevent anything writing to the disk at boot is to unfortunately disable the the kernel relinking and Address Space Layout Randomization (ASLR). Both are great security features but unfortunately require modifying the disk each time the machine is booted. With some effort it could be possible to perform the task on the MFS partitions but the most immediate solution for now is to simply disable them. At least they have run once already.
Add the following to the /etc/rc.conf.local to disable ASLR:
library_aslr=NO
To disable the kernel relinking, change the /var/db/kernel.SHA256 to contain:
SHA256 (/bsd) = NO
At this point, you probably want to consider any additional changes to the install such as adding users, installing 3rd party packages, etc. When you are ready, restart the machine.
Whilst it is fairly satisfying to be able to pull out the plug and restart the machine without running the risk of losing data, this manner of safety unfortunately doesn't quite extend to security. For example it is still very possible for someone with root access to be able to remount the disks read/write and modify the data. However in many ways this is also convenient if you ever need to tweak the setup. In order to remount the root / partition, you can use the following command:
# mount -u -orw /
This will remount the root partition with the new write access. This can be set to read-only again by simply rebooting or via the -oro argument. Also note that you may want to set a few more partitions writable depending on the task. For example if you plan on adding a new user, you will need the /home partition writable to create their home directory.