SSH Certificate Notes 29 Nov 2023

There are lots of tutorials out there, but I want to compile my notes so if I walk away from playing with them again for a few years, I can pick it up again!

SSH Keys for different things are all the same (modulo chosen algo type)

User keys, host keys, CA keys -- they are all the same. Don't let ssh-keygen docs, with all of its options and whatnot, convince you otherwise.

Use AuthorizedPrincipalsFile to manage access

Don't go and create a local user account for everyone who may be accessing the host, just have a shared local user, probably even root, where you differentiate who did what using the identity on the cert they authed with.

AuthorizedPrincipalsFile is a file that lists the allowed principals for a given local user. Use groups as principals, not individual users, and list the groups which may use the local user in the file for the given local user. The groups an actual user is in are then added to that user's cert.

AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u

The %u is the normal user placeholder, so /etc/ssh/auth_principals/root would control the principals that may log in as root. The file takes one principal per line. Copilot really wants me to add that you can use wildcards in the file, but that idea is making me queasy. Principals as groups sounds better to me.

The overall sshd_config looks something like:

Port {{ port }}
Protocol 2
AcceptEnv LANG LC_*
LoginGraceTime 120

UsePAM no 
PasswordAuthentication no
IgnoreRhosts yes
PubkeyAuthentication yes

HostKey {{ path }}/ssh_host_ed25519_key
TrustedUserCAKeys {{ path }}/ca.pub
AuthorizedPrincipalsFile {{ path }}/auth_principals/%u

Testing with sshd

You can run sshd as a normal user, as long as you only try to log in as that same user.

Invocation looks like, /opt/homebrew/sbin/sshd -d -D -f /tmp/my_ssh/sshd_config. This will start it without forking behavior, process a single login, and give us debug output.

The sshd_config may need StrictModes no depending on the directories in play and the permissions on them.

Various invocations

ssh with weird agent socket

Given I am doing this to test agent + cert muckery, invoking ssh to talk to the sshd mentioned above looks like:

ssh -v \
    -o UserKnownHostsFile=/dev/null \
    -o StrictHostKeyChecking=no \
    -F /dev/null \
    -l $USER \
    -o IdentityAgent=/path/to/agent.sock \
    -p 2222 \
    localhost

Same, but for a local cert instead of agent (assuming user_key is the private key, and has user_key.pub and user_key-cert.pub alongside it):

ssh -v \
    -o UserKnownHostsFile=/dev/null \
    -o StrictHostKeyChecking=no \
    -F /dev/null \
    -l $USER \
    -i path/to/user_key \
    -p 2222 \
    localhost

Given a CA key, generate a user cert locally

ssh-keygen -s  path/to/ca -I wobble -n {{ principals }} .path/to/user_key

Add a comma seperated (no spaces) list of the principals (groups) for {{ principals }} in that invocation (needs to align with the auth principals on sshd). The -I is basically irrevalent for testing purposes, is just a cert identifier.


Using More FreeBSD 17 Nov 2023

I do some volunteer sysadmining for a local non-profit, mostly network management and related things. Between there, and my home setup, I have gradually moved from defaulting to Ubuntu back towards FreeBSD over the last year. Nothing wrong with Ubuntu, but I kept finding FreeBSD to simply be a little bit easier and a lot better documented. For a dev box, I'll stick with linux—all the tooling optimizes there first, in particular the container ecosystem is too useful to walk away from. For sysadmin'y stuff though, FreeBSD is where it's at. At a minimum, it's rc system is much easier to work with than systemd :-)

My general toolkit for running anything is a pair of refurbished HP Prodesks sharing a CARP virtual interface using devd events if I need to do anything on failover. It's shockingly easy and just works.

(side note: I tested 14.0-RELEASE on a pair of them and discovered that if a server is hosting a VIP and needs to to use that virtual IP when it is the backup, say to find DNS, you need to disable net.inet.ip.source_address_validation in sysctl, but then it works fine. I probably shouldn't do this, but I'll sort that out later.)

(side note 2: With those old HP Prodesks, you need to go into the BIOS and disable UEFI (switch to use "legacy boot" for the FreeBSD install to work). You don't need to for Ubuntu.)


Home DNS with Unbound and NSD 12 Nov 2023

I recently redid the DNS on my home network, moving from dnsmasq to Unbound and NSD. Unbound acts as the DNS server the network uses, and Unbound hosts losts local zones for my search domains. For reasons, this is really across two sites: our home, and a barn we own a few miles away. Because I am a dork, I have things at both sites :-)

My network controller has a built in DNS server which assigns a local domain, a la brians-laptop.local to anything which gets a DHCP lease. I wanted to be able to respect these, but also assign a diferent domain to statically assigned things on the network, such as printers and our NAS. For these I set up a .home and .barn respectively, for things in the home and in the barn. Those zones are hosted on NSD, with a config like:

# /usr/local/etc/nsd/nsd.conf
server:
    ip-address: 127.0.0.1
    port: 53530
zone:
    name: home
    zonefile: "home.zone"
zone:
    name: barn
    zonefile: "barn.zone"

Note that NSD is only listening on 127.0.0.1 and on port 53530. It should only ever be queried from the unbound instance on the same host (which is using port 53).

The zone files referenced are just stubs, not really correct, but they don't need to be :-)

; /usr/local/etc/nsd/home.zone
$ORIGIN home. ; 'default' domain as FQDN for this zone
$TTL 600 ; default time-to-live for this zone

home.   IN  SOA     ns.home. noc.dns.icann.org. (
        16          ;Serial
        7200        ;Refresh
        3600        ;Retry
        1209600     ;Expire
        3600        ;Negative response caching TTL
)

; The nameserver that are authoritative for this zone.
                        NS      ns.home.
nas.home.       A       192.168.2.114
printer.home.   A       192.168.2.140
m0001.home.     A       192.168.2.101
m0002.home.     A       192.168.2.102

and the barn:

$ORIGIN barn. ; 'default' domain as FQDN for this zone
$TTL 600 ; default time-to-live for this zone

barn.   IN  SOA     ns.barn. noc.dns.icann.org. (
        17          ;Serial
        7200        ;Refresh
        3600        ;Retry
        1209600     ;Expire
        3600        ;Negative response caching TTL
)

; The nameserver that are authoritative for this zone.
                NS      ns.barn.
m0003.barn.     A       192.168.81.101
m0004.barn.     A       192.168.81.102
dvr.barn.       A       192.168.81.110

With NSD up and running, I set up Unbound to make use of those local zones. It runs on the same instance as NSD, and is configured ot use it for those domains:

# /usr/local/etc/unbound/unbound.conf
server:
    # This is a CARP interface, which apparently 
    # requires explicitely listing
    interface: 192.168.2.100
    interface: 0.0.0.0
    do-not-query-localhost: no
    access-control: 192.168.0.0/16 allow

    local-zone: "home" nodefault
    domain-insecure: "home"

    local-zone: "barn" nodefault
    domain-insecure: "barn"

stub-zone:
    name: "home."
    stub-addr: 127.0.0.1@53530

stub-zone:
    name: "barn."
    stub-addr: 127.0.0.1@53530

forward-zone:
    name: "local."
    forward-addr: 192.168.1.1

Unbound is set up to listen on all interfaces, but because I am using a CARP interface as well, it seems to require seperately listing that one to avoid confusions. The two local zones are configured to be insecure (no DNSSEC) and to forward to the NSD instance as a stub-zone for each domain, respectively.

The dynamically assigned .local domain is forwarded to the router to pick up the names of things which get DHCP leases via the forward-zone block.

Finally, to make it all work, I hand out three search domains on DHCP, .local, .home, and .barn. This is DHCP code 119 with a value local,home,barn, for future reference.

Deployment wise, this is running on two servers in each location, with each server running both NSD and Unbound. The CARP interface is used to provide a single IP address for the DNS servers, and the DHCP server is configured to hand out that IP address as the DNS server for the network.

Fun fact along the way: I learned emacs has a mode for zone files which automatically increments serial when you save. Handy!