💾 Archived View for thrig.me › tech › openbsd › pubnix captured on 2024-06-16 at 12:29:08. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
Pubnix is short for "public unix" or a unix system that offers free or sometimes low cost accounts along with various other services. Pubnix are similar to BBS or Bulletin Board Systems, little isolated islands in a larger network, though BBS generally were isolated, and maybe only allowed one user to use the system at a time (as there was only one phone line) while a pubnix may not be isolated much if at all from the wider internet, and typically allow multiple users access at the same time. Also, there are probably not any long distance fees.
A problem is that there are many ways a pubnix could be setup and maintained, and the best (or maybe least bad) way will depend on both technology and politics. Technology depends on such factors as how many systems are involved, and therefore whether complications such as LDAP, NFS, or configuration management come into play. Fleet management instead of pet management becomes important as the number of systems goes up, or how quickly can you spin up a new instance or recover from backups? Politics addresses social issues such as moderation and dispute resolution. There may also be bills to pay? Some issues (spam, malware) have feet in both technology and politics, and may need to be addressed by both. There can be a lot of work here, especially if the system is very open to "the public", is generous in allowing access to remote systems, offers the ability to run arbitrary code, and attracts the sorts who cause problems. There can also be security issues, as you probably do not want to be handing out root, unless the system is on a very restricted network. Those sorts of systems usually go by the name of honeypot, and serve a different purpose.
Also much of this software will be bespoke, unless you can find an off-the-shelf pubnix implementation that more or less meets your needs. I've decided not to look for such, and will instead try to implement something from scratch for a small OpenBSD system, so nothing complicated like LDAP will be involved. The broad outline should be similar on other unix, though the details such as security and user account management will differ, and there are many design decisions to make along the way. And I'm writing this as I go along. With that in mind…
https://thrig.me/src/openbsd-pubnix.git
A pubnix should offer automated user account creation, as playing a game of 20 questions is perhaps a barrier too high for entry, and then you'd have to manually enter the user accounts, hopefully not make any typos, etc. This could be a legacy web or Gemini CGI program, but here I will instead focus on the command line—that is, a system you can interact with over SSH (or telnet, a modem, line printer, etc).
A wrapper program will be necessary to audit user input and to make suitable user addition calls, possibly first allowing for the request to be reviewed, and maybe also involving an additional verification of an email address before the account goes live. Given SSH, the wrapper program will need to be the login program for a particular account, or one option among others offered by a generic service account. A generic service account will avoid a proliferation of single-use accounts, so let's try that route. This means there will need to be either a menu or command line interface to interact with, and ideally some means to easily "wire up" new functions such as account verification and whatever else we may need.
Another wrinkle is that the service account will most likely not have a password on it, though you may want one (and maybe also a SSH key) advertised somewhere. A password would be a barrier to entry for new users (or worse a SSH public key) especially those not good at unix nor the command line, but may help keep bots away from the service menu. It may also be beneficial to use a public blacklist to prevent known compromised hosts from connecting to the system via a firewall table, though this does risk blocking legitimate users. As you can see, we have only just gotten started, and there are already various things to worry about, and tradeoffs to be made.
Assuming the service account is to have no authentication, this requires a custom authenticator as detailed in login.conf(5) on OpenBSD 7.5. Other versions may vary, so do check the fine manual for details that may have changed between releases.
# cat /usr/libexec/auth/login_-noauth #!/bin/sh echo >&3 authorize # chmod +x /usr/libexec/auth/login_-noauth
Then somewhere in /etc/login.conf an entry that uses this authenticator:
pubnixnoauth:\ :auth=-noauth:\ :shell=/usr/bin/false:\ :tc=default:
Testing should be done on a system that is not accessible from the internet; we'll change the shell to something else later. Next is to modify /etc/ssh/sshd_config to allow for no authentication for the account we'll be creating in the next step. Be sure to restart sshd.
Match User service PermitEmptyPasswords yes AuthenticationMethods none X11Forwarding no AllowTcpForwarding no AllowAgentForwarding no AllowStreamLocalForwarding no
And finally, create an account that uses the "pubnix" login class. You may also want a script that undoes all these changes, or to use configuration management, or to mess around on a throwaway virt when just starting out. Here we create a "service" account.
# useradd -d / -L pubnixnoauth -c 'Pubnix Menu' service # getent passwd service service:*:1002:1002:Pubnix Menu:/:/bin/ksh
Then we test that there is no password prompt, and that the exit status is 1, which may be from /usr/bin/false, but could be due to something else failing. A better do-nothing test shell might exit with an unusual exit status word such as 42.
$ ssh service@localhost ... Connection to 127.0.0.1 closed. $ echo $? 1
If something goes awry, review everything, and the various man pages involved: login.conf(5), sshd_config(5), ensure that everything has been restarted, etc. It is very easy to miss some little thing, which is why this probably should be done with configuration management.
The next step is to have a login "shell" that offers a menu of choices that do things, but not arbitrary shell access such as provided by ksh(1).
As mentioned above we want a program to present a menu or respond to various commands (new account, verify account, etc) and to have a means to easily extend the program with new functionality. One approach is to have a binary (or script) that does everything. Another is to have a program that executes various other programs as need be, which means those programs can be written in any language and can be changed without affecting the parent menu. There are advantages and disadvantages to these different approaches. The menu could use curses for a more interactive system, or could accept lines read from the client, which is much simpler. The program or programs should use pledge to make unauthorized access to the system a bit harder, and should not call another program that allows the execution of arbitrary code. We may also want to time the connection out after some amount of inactivity.
Yet another problem is that the service account has no root access, and something running as root will be necessary to add and possibly edit user accounts. There are several ways to solve this problem, including to instead use LDAP to store the accounts, or to allow the service account limited root access via doas(1), or to have code run periodically as root see if the service account has added new entries to a file or database. Again, there are tradeoffs here. Limited doas(1) access might be exploited, while a periodic (or triggered, for example by an inode change notification) root job is more complicated, may break and require manual intervention, and requires that whatever the service account wrote be parsed back into a usable form. A periodic root job does allow for account review and approval more easily than directly creating a new account, as the "new accounts" file would have things to approve, or there could be a database field that indicates the same.
In the interests of simplicity, we'll have the service account directly create new accounts, but to only allow new accounts a limited menu of choices, not a full unix shell. Systems that grant full shell access and remote host access may want more up-front checks before granting a new account. A problem here is that a spammer can quickly ruin the reputation of your IP address and cause grief for your Internet Service Provider and many other folks on the internet if a new account can send email, host services, or access remote systems by default.
We'll need a (fake, for now) shell for the users, and also probably a unique login class and group for them to belong to. Users and group IDs are often kept in ranges, here arbitrarily starting at 5000.
# mkdir /usr/local/pubnix/bin # printf '#!/bin/sh\necho todo\n' > /usr/local/pubnix/bin/ush # chmod +x /usr/local/pubnix/bin/ush # grep pubnix /etc/shells /usr/local/pubnix/bin/ush # grep pubnix /etc/group pubnix:*:5000: # perl -00 -ne '/^pubnix:/ and print' /etc/login.conf pubnix:\ :maxproc-max=32:\ :maxproc-cur=16:\ :tc=default:
Opinions vary as to whether users should all belong to a common group, or instead have a username specific group, or to have both a username specific group and belong to some common group. Here we will have their login group (the one in the passwd file) be pubnix, and not give them a user-specific group. Other use cases may favor having user-specific groups, though putting them all in one group is simple and allows for firewall rules to match on that group.
The code was judged too long to include here, so refer to "mkaccount.c" and "menu.pl" in the git repository that was linked near the top of this document. The menu program offers a few choices to the user, notably that the "new" command asks for a username and calls mkaccount. The service account is not allowed to call a user addition program directly, as with doas(1) there is no way to restrict what arguments can be given, so instead mkaccount is called. mkaccount also generates a random password that the user will need to use to login with.
$ grep mkaccount /etc/doas.conf permit nopass service as root cmd /usr/local/pubnix/sbin/mkaccount
This is the "shell" given to pubnix users by default. It pretty much only allows them to change their password, though more functionality would not be difficult to add. Be aware that giving them more commands to run may allow them to escape to a real shell. For example, many text editors or mail clients allow for shell escape or arbitrary code to be run. This may not be ideal if you are trying to offer minimal access to the system.
#!/usr/bin/perl # ush.pl - a small user shell use 5.36.0; use OpenBSD::Pledge; use OpenBSD::Unveil; $SIG{$_} = sub { say "ouch!" } for qw(INT QUIT); STDOUT->autoflush(1); pledge qw(exec proc stdio unveil) or die "pledge: $!"; unveil '/usr/bin/passwd', 'x' or die "unveil: $!"; unveil or die "unveil: $!"; sub prompt ($message) { print $message; chomp( my $response = readline STDIN ); return $response; } my ( %actions, %aliases ); %actions = ( enough => sub { say 640 }, help => sub { say "Commands: ", join ' ', sort keys %actions }, passwd => sub { system '/usr/bin/passwd' }, quit => sub { exit }, ); $aliases{$_} = $actions{quit} for qw(exit bye); $actions{help}->(); while (1) { my $do = prompt '> '; if ( exists $aliases{$do} ) { $aliases{$do}->(); } elsif ( exists $actions{$do} ) { $actions{$do}->(); } else { print "unknown command '$do'\n"; $actions{help}->(); } }
If the users of the system know how to use SSH keys, then it would make sense to support the association of one or more SSH public keys with an account, in addition or as a replacement for passwords. Multi-factor authentication could also be offered.
$ ssh service@localhost ... Commands: answer help new-account quit > new-account Login: atest Full name: Test User Added user ``atest'' password: a3JRvC6vVkQzcTYcObzve8FMNcodNhUB > quit Connection to 127.0.0.1 closed. $ ssh atest@localhost atest@127.0.0.1's password: ... Commands: enough help passwd quit > passwd Changing password for atest. ...
Probably the user accounts should be kept in a database, and then the system password files, LDAP, etc. are built from that database. But that is more complicated. Other improvements might be to use curses so that the menu shells can be made more interactive, or to use a editline library for better command line support. More help text and i18n might also be good, as the above has been a barebones sketch of what a pubnix might offer.
A security review by someone else might also be handy, as I'm pretty sure thare are no glaring problems, but have been wrong about this sort of thing before.