Outils d'utilisateurs

Outils du Site


projets:distantsun

Distant Sun (OpenBSD on a Dedibox)

Reliable service on a reliable connection (well, aDediBox…).

This worked well until ProXad decided to reduce their prices, but not for their old customers (at least, not without having them get a new one). This machine has now be transitionned to WhiteDwarf.

Installation on a Dedibox (v3)

Previous iterations of the Dediboxen were installed (with more or less difficulty) using the OpenBSDedibox method. It is no longer available. The hardware of a Dedibox v3 is a Dell XS11-VX8 server powered by a Via Nano U2250 processor. Despite what this documentation would lead to believe, the IPMI support isn't usable as the BMC only has a local address. This prevents manually installing OpenBSD from the remote serial console.

Ultimately, the solution is to use Yaifo, which allows to create an image running the install script over SSH. This part is greatly eased by fat's documentation. Yaifo needs a running OpenBSD of the same version to build the remote installation image. A build system can be quickly set up in a virtual machine. The sources of the kernel and the system need to be installed in their usual location. This is done by downloading the relevant archives from the local mirror and extracting them, as follows.

$ ftp ftp://mirror.aarnet.edu.au/pub/OpenBSD/4.7/src.tar.gz
$ ftp ftp://mirror.aarnet.edu.au/pub/OpenBSD/4.7/sys.tar.gz
$ sudo tar xzvf src.tar.gz -C /usr/src/
$ sudo tar xzvf sys.tar.gz -C /usr/src/

After getting the Yaifo sources, they have to be configured. This is done in the config file at the root of the tree. Only a few things have to be adjusted from the shipped default.

...
# Dediboxen v3 have two Intel Pro/1000, em0 is the one connected to ProXad's network.
DEVICE=em0

# Dedibox's network parameters
DNS=88.191.254.60
SEARCH=dedibox.fr
IP=88.191.XX.YY
NETMASK=255.255.255.0
GATEWAY=88.191.XX.1
...

As the installation be made remotely accessible via SSH, the keys have to be installed. Password authentication is not enabled, so an authorized_keys files has to be provided as well. All ssh_host_*_key* and the authorized_keys files must be placed at the root of the Yaifo sources, alongside the configuration file.

Once done, the filesystem image can be built.

make cleanall
make obj
sudo make

This creates, amongst others, a file called yaifo.fs. This is the one which needs to be put at the beginning of the Dedibox's hard disk. The easiest way to do so is to start it in rescue mode, copy the image over, then dd it to /dev/sda.

Rebooting the system in non-rescue mode will boot OpenBSD from the image and start the installation process, which only needs a client connecting over SSH to answer the installation questions. A full transcript of the installation is here.

Basic configuration

Bootstrap setup

Non-root administrative user

We activate the operator account, and add it to group wheel to give the user full sudo rights.

# passwd operator
Changing local password for operator.
New password:
Retype new password:
# chsh operator
## Change shell to /bin/sh
# mkdir /operator
# chown operator:wheel /operator/
# chmod 700 /operator/
# user mod -G wheel operator
# visudo
## Uncomment line "%wheel        ALL=(ALL) SETENV: ALL"
# ^D
$ su operator

Basic packages

Some packages that need be installed ASAP.

$ export PKG_PATH=ftp://ftp.fr.openbsd.org/pub/`uname`/`uname -r`/packages/`uname -m`/
$ sudo pkg_add bash vim-7.2.267-no_x11 subversion mutt-1.5.20p2-sasl-sidebar-compressed gmake
...
$ sudo ln -s /usr/local/bin/bash /bin/bash
$ echo /bin/bash | sudo tee -a /etc/shells
$ chsh
## Change shell to /bin/bash
^D
$ su operator
Bash Navigation

The Bash package does not come with an inputrc configuration. The convenient command line navigation shortucts, Ctrl+Left and others, therefore do not work. This can be remediated by creating this file and filling it with a definition of the shortcuts.

/etc/inputrc
# /etc/inputrc, taken verbatim from ArchLinux
# do not bell on tab-completion
#set bell-style none

set meta-flag on
set input-meta on
set convert-meta off
set output-meta on

$if mode=emacs

# for linux console and RH/Debian xterm
"\e[1~": beginning-of-line
"\e[4~": end-of-line
"\e[5~": beginning-of-history
"\e[6~": end-of-history
"\e[7~": beginning-of-line
"\e[3~": delete-char
"\e[2~": quoted-insert
"\e[5C": forward-word
"\e[5D": backward-word
"\e\e[C": forward-word
"\e\e[D": backward-word
"\e[1;5C": forward-word
"\e[1;5D": backward-word

# for rxvt
"\e[8~": end-of-line

# for non RH/Debian xterm, can't hurt for RH/DEbian xterm
"\eOH": beginning-of-line
"\eOF": end-of-line

# for freebsd console
"\e[H": beginning-of-line
"\e[F": end-of-line
$endif

# Include user-specific configuration
$include ~/.inputrc

For this to work in tmux, some more work is needed

/etc/inputrc
...
# for tmux
"\C-[OD" backward-word
"\C-[OC" forward-word
...

Startup daemons

We want a firewall and no INETd. This is done in /etc/rc.conf.local:

pf=YES                  # Packet filter / NAT

IPv6

Stateless Autoconfiguration

Yaifo doesn't setup IPv6 autoconfiguration by default. It can be added in /etc/hostname.em0.

# echo rtsol >> /etc/hostname.em0

To enable proper reception of router advertisements, the following shourld be add to /etc/sysctl.conf'.

net.inet6.ip6.accept_rtadv=1

Tunnel with SixXS

:!: Dediboxen v3 do not have IPv6 on their network yet…

The file /etc/hostname.gif0 contains:

tunnel 88.191.118.228 212.100.184.146
inet6 2001:6f8:202:17b::2 128
dest 2001:6f8:202:17b::1
!route add -inet6 default 2001:6f8:202:17b::1

Firewall

Of course, a little firewall has to be adjusted in /etc/pf.conf:

#       $OpenBSD: pf.conf,v 1.49 2009/09/17 06:39:03 jmc Exp $
#
# See pf.conf(5) for syntax and examples.
# Remember to set net.inet.ip.forwarding=1 and/or net.inet6.ip6.forwarding=1
# in /etc/sysctl.conf if packets are to be forwarded between interfaces.
SIXXS_IF="gif0"
SIXXS_POP=212.100.184.146
SIXXS_POP6=2001:6f8:202:17b::1

TCP_IN="{" domain http https smtp "}"

set skip on lo

# filter rules and anchor for ftp-proxy(8)
#anchor "ftp-proxy/*"
#pass in quick proto tcp to port ftp rdr-to 127.0.0.1 port 8021

# anchor for relayd(8)
#anchor "relayd/*"

pass            # to establish keep-state

block in on egress

# rules for spamd(8)
#table <spamd-white> persist
#table <nospamd> persist file "/etc/mail/nospamd"
#pass in on egress proto tcp from any to any port smtp \
#    rdr-to 127.0.0.1 port spamd
#pass in on egress proto tcp from <nospamd> to any port smtp
#pass in log on egress proto tcp from <spamd-white> to any port smtp
#pass out log on egress proto tcp to any port smtp

# Allow traffic form SixXS PoP
pass in quick on egress proto 41 from $SIXXS_POP to (egress)
pass out quick on egress proto 41 from (egress) to $SIXXS_POP

# ICMP
pass quick inet proto icmp all
pass quick inet6 proto icmp6 all

# Incoming SSH, DNS & others
pass in on egress inet proto tcp from any to (egress) port ssh \
        flags S/SA keep state
pass in on egress inet proto {tcp, udp} from any to (egress) port domain \
        flags S/SA keep state
pass in on egress inet proto tcp from any to (egress) port $TCP_IN \
        flags S/SA keep state

#block in quick from urpf-failed to any # use with care

# By default, do not permit remote connections to X11
block in on ! lo0 proto tcp to port 6000:6010

Brute Force Attacks Mitigation

Seeing that brute-force attacks don't wait long, we install a home-improved DenyHost-like script.

After putting the script in /usr/local/sbin, an entry is added directly in the root's crontab (crontab -e).

*/5     *       *       *       *       /usr/local/sbin/denyhost.sh

A couple of persistent tables and rules have to be added to /etc/pf.conf.

table <whitelist> persist
table <kiddies> persist

# This goes just before the "block in on egress"
pass in quick on egress proto tcp from <whitelist> to (egress) port ssh
block in quick on egress from <kiddies>

We finish by populating the whitelist with a couple of addresses of ours, then reload the nem ruleset.

$ sudo pfctl -vt whitelist -T add ADDRESS
$ sudo pfctl -f /etc/pf.conf

Statistics

We use pfstat (in ports) to get traffic information from pf.

$ cd /usr/ports/net/pfstats
$ sudo make package install clean-depends

The configuration file needs some tweaking to match the system. Namely using em0 and gif0 as the collection interfaces, and placing the generated graphs in the right place. The most relevant changes from the default are as follows.

[...]
collect 30 = interface "gif0" pass bytes in ipv6 diff
collect 31 = interface "gif0" pass bytes out ipv6 diff

image "/srv/www/stats/pfstat/pfstat.jpg" {
 [...]
                graph 30 bps "in inet6" "bits/s" color 0 255 0 filled,
                graph 31 bps "out inet6" "bits/s" color 0 64 255
 [...]
}
[...]
collect 32 = interface "gif0" pass packets in ipv6 diff
collect 33 = interface "gif0" pass packets out ipv6 diff
collect 34 = interface "gif0" block packets in ipv6 diff
collect 35 = interface "gif0" block packets out ipv6 diff

image "/srv/www/stats/pfstat/pfstat-packets.jpg" {
 [...]
        left
  [...]
                graph 32 "pass in inet6"   "packets/s" color 0 255 0 filled,
                graph 33 "pass out inet6"  "packets/s" color 0 64 255
        right
  [...]
                graph 34 "block in inet6"  "packets/s" color 255 64 0,
                graph 35 "block out inet6" "packets/s" color 192 255 0
}
[...]

It needs to run periodically to collect stats and generate graphs. It's also a good idea to cleanup the database every now and then. We add three cron entries in root's crontab.

*       *       *       *       *       /usr/local/bin/pfstat -q
*/5       *       *       *       *       /usr/local/bin/pfstat -p
30      5       2       *       *       /usr/local/pfstat -t 31:365

Mail aliases

All mails to root will be redirected to me, this is done in /etc/mail/aliases, taking into account the future web hosting service.

root: shtrom-distant-sun@ssji.net

(...)
# RFC 2142: SUPPORT MAILBOX NAMES FOR SPECIFIC INTERNET SERVICES
# hostmaster:   root
# usenet:       root
# news:         usenet
webmaster:      root
# ftp:          root

The aliases database then has to be rebuilt

$ cd /etc/mail
$ sudo make

Name server

The chroot for named will be in /srv/named rather than the usual place.

$ sudo mv /var/named /srv/named
$ sudo ln -s /srv/named /var/named

We set up the domain we want to serve as usual, and add a startup entry in /etc/rc.conf.local.

named_flags="-u named -t /srv/named"

The resolver can then be instructed to use the local DNS in /etc/resolv.conf.

nameserver 127.0.0.1

Dynamic DNS

DynDNS is becoming annoyingly restrictive, and does not provide such an added value that it cannot be replaced in a home-made way. Actually, this is added value.

BIND Configuration

Fortunately, it is easy enough to dynamically update BIND DNS entries.

First, a key needs to be created to authenticate the updates.

$ dnssec-keygen -b 512 -a HMAC-MD5 -v 2 -n HOST <DOMAIN>
K%3CDOMAIN%3E.+XXX+XXXXX

A snippet suitable for inclusion in named.conf can then be generated as

$ sudo sed -n 's/Key: \(.*\)/key DOMAIN { algorithm "HMAC-MD5"; secret"\1";};/p' K%3CDOMAIN%3E.+XXX+XXXXX.private | sudo tee -a named.conf
key DOMAIN. { algorithm "HMAC-MD5"; secret"XXXXXX";};

A zone file, and entry in the configuration file (all path are within a chroot), is created specifically, and the bearer of the key authorised to update the desired zone. Note, the nsupdate manual states that “[z]ones that are under dynamic control via nsupdate or a DHCP server should not be edited by hand. Manual edits could conflict with dynamic updates and cause data to be lost.”.

named.conf
// Dynamically updated
zone "DOMAIN" {
        type master;
        file "slave/DOMAIN"; # Because it gets updated
        journal "tmp /DOMAIN.jnl";
        allow-update{
                key DOMAIN;
        };
};
slave/DOMAIN
$ORIGIN DOMAIN.
$TTL 10 ; 10 seconds
 
@       IN      SOA     MNANE RNAME (
                        2013052701      ; serial
                        1h      ; refresh
                        30m     ; retry
                        7d      ; expiration
                        1h )    ; minimum
$TTL 3600
                NS      MNAME

The named user should have write permissions to that file.

$ sudo chown named DOMAIN*

Named can then re-read its configuration.

$ sudo rndc reconfig

The zone is now properly served, and empty.

$ dig @localhost DOMAIN

; <<>> DiG 9.4.2-P2 <<>> @localhost DOMAIN
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22813
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;HOST.             IN      A

;; AUTHORITY SECTION:
HOST.      10      IN      SOA     MNAME. RNAME. 2013052701 3600 1800 604800 3600

;; Query time: 1 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun May 26 11:14:36 2013
;; MSG SIZE  rcvd: 94

RRs are updated (added or removed) with nsupdate.

$ nsupdate -k K%3CDOMAIN%3E.+XXX+XXXXX.private
> update delete HOST.DOMAIN. A
> update delete HOST.DOMAIN. AAAA
> update add HOST.DOMAIN. 10 A 127.0.0.1
> update add HOST.DOMAIN. 10 AAAA ::1
> show
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0
;; flags: ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; UPDATE SECTION:
HOST.DOMAIN.   0       ANY     A
HOST.DOMAIN.   0       ANY     AAAA
HOST.DOMAIN.   10      IN      A       127.0.0.1
HOST.DOMAIN.   10      IN      AAAA    ::1
> send
>

Webservice

Now that BIND is ready to receive updates, we need to provide a DynDNS The FRITZ!Box can use user-defined URLs (where ipaddr is in the form IPv4,IPv6)

http://hostname.tld/cgi-bin/ddns.pl?user=<username>&pass=<pass>&hostname=<domain>&myip=<ipaddr>

and these requests can be handled by a CGI script, which is extended to support IPv6 updates.

Some coding resulted in new and improved version, with IPv6 support, some input validation, proper HTTP statuses and address guessing.

mod_perl

Rather than building a statically-linked Perl to run from the chroot, mod_perl is used to interpret the CGI script. This still requires jumping through some hoops to avoid chroot-related error messages (typically Undefined subroutine &Apache::Registry::handler called.), but it works.

httpd.conf
PerlModule Apache::Registry
<Directory "/var/www/cgi-bin">
    AllowOverride None
    Options ExecCGI
    Order allow,deny
    Allow from all
    <FilesMatch "\.pl$">
      SetHandler "perl-script"
      PerlHandler Apache::Registry::handler
      PerlSendHeader On
    </FilesMatch>
</Directory>

The mod_perl files must be copied into the chroot. FIXME No they don't, this step can be skipped.

$ sudo mkdir /path/to/chroot/usr/local/libdata/perl5/site_perl/`uname -m`-openbsd/
$ sudo cp $(pkg_info -L mod_perl | grep pm) /path/to/chroot/usr/local/libdata/perl5/site_perl/`uname -m`-openbsd/

Socket communication

The CGI does not call nsupdate directly. Rather, for good separation of concerns, it just writes a set of instructions which another process, and use, handles. These instructions can be written straight into a socket, and a simple loop calling nsupdate can take care of the update. The loop is started from /etc/rc.local.

/var/named/bin/ddns.sh
#!/bin/sh
# Process commands from dns.pl's socket by passing them to nsupdate to
# dynamically update a DNS zone.
#
# To be used from /etc/rc.local
#
# Based on [0], and updated to support flexible query string and IPv6 updates
# [0] http://www.debian-administration.org/article/Creating_Bind_DNS-Entries_with_regular_dyndns-clients_in_routers
 
NAMEDPATH=/srv/named
WWWPATH=/srv/www
 
UPDATEKEY=/etc/KDOMAIN+XXX+XXXXX.private
SOCKET=/tmp/ddns.sock
NAMEDSOCKET=${NAMEDPATH}${SOCKET}
WWWSOCKET=${WWWPATH}${SOCKET}
 
NSUPDATE=/usr/sbin/nsupdate
LOG=/var/log/nsupdate.log
 
# rc.local(8) ready
if [ -x ${NSUPDATE} ]; then
        echo -n ' ddns'
        rm -rf ${WWWSOCKET} ${NAMEDSOCKET}
        mkfifo -m 600 ${WWWSOCKET}
        ln -f ${WWWSOCKET} ${NAMEDSOCKET}
 
        chown www ${WWWSOCKET}
        chgrp named ${NAMEDSOCKET}
        chmod g+rw ${NAMEDSOCKET}
 
        sudo -u named sh -c "while : ; do
                ${NSUPDATE} -k ${NAMEDPATH}${UPDATEKEY} ${NAMEDPATH}${SOCKET}
                # Avoid looping too fast if something goes wrong
                /bin/sleep 1
        done" >> ${LOG} 2>&1 &
fi

Clients

What remains is to get machines to updates these records. This is essentially a matter of using wget or curl, leveraging the address-guessing mechanism. This can go in many places (including crontabs, but it makes more sense to add it in the interface-setup callbacks).

$ DNSUSER=user
$ DNSPASS=correcthorsebatterystaple
$ DNSURL="https://www.example.net/cgi-bin/ddns.pl?user=${DNSUSER}&pass=${DNSPASS}&hostname="
$ /usr/bin/wget -4 "${DNSURL}$(hostname).example.net" -O - > /dev/null # Assuming GNU hostname, otherwise the -s flag might be needed
$ /usr/bin/wget -6 "${DNSURL}$(hostname).example.net" -O - > /dev/null
ArchLinux

netctl.profile(5) states that “any executable script in /etc/netctl/interfaces/ with the name of the interface for the profile [is] sourced”.

/etc/netctl/interfaces/eth0
DNSUSER=user
DNSPASS=correcthorsebatterystaple
DNSURL="https://www.example.net/cgi-bin/ddns.pl?user=${DNSUSER}&pass=${DNSPASS}&hostname="
ExecUpPost="( \
            /usr/bin/wget -4 '${DNSURL}$(hostname).example.net' -O - > /dev/null; \
            /usr/bin/wget -6 '${DNSURL}$(hostname).example.net' -O - > /dev/null \
            ) || true"
Debian
/etc/network/interfaces
iface eth0 inet dhcp
   ...
   post-up /usr/bin/wget -4 "https://www.example.net/cgi-bin/ddns.pl?user=user&pass=correcthorsebatterystaple&hostname=$(hostname).example.net" -O - -o /dev/null
   post-up /usr/bin/wget -6 "https://www.example.net/cgi-bin/ddns.pl?user=user&pass=correcthorsebatterystaple&hostname=$(hostname).example.net" -O - -o /dev/null

Alternatively, this can be enabled for all interfaces in '/etc/network/if-up.d/', which is perhaps the most generic solution.

/etc/network/if-up.d/ddns
#!/bin/bash
DNSUSER=user
DNSPASS=correcthorsebatterystaple
DNSURL="https://www.example.net/cgi-bin/ddns.pl?user=${DNSUSER}&pass=${DNSPASS}&hostname="
 
case $PHASE in
	post-up)
        case $IFACE in
		lo*)
		;;
                wlan*)
                logger -t $IFACE-$PHASE `/usr/bin/wget -4 "${DNSURL}$(hostname).wlan.example.net" -O - -o /dev/null` &
                logger -t $IFACE-$PHASE `/usr/bin/wget -6 "${DNSURL}$(hostname).wlan.example.net" -O - -o /dev/null` &
                ;;
                *)
                logger -t $IFACE-$PHASE `/usr/bin/wget -4 "${DNSURL}$(hostname).example.net" -O - -o /dev/null` &
                logger -t $IFACE-$PHASE `/usr/bin/wget -6 "${DNSURL}$(hostname).example.net" -O - -o /dev/null` &
                ;;
        esac
        ;;
esac
Gentoo
/etc/conf.d/net
DNSUSER=user
DNSPASS=correcthorsebatterystaple
DNSURL="https://www.example.net/cgi-bin/ddns.pl?user=${DNSUSER}&pass=${DNSPASS}&hostname="
 
postup () {
        sleep 10
        if [[ "${IFACE}" == "eth0" ]] ; then
                /usr/bin/wget -4 "${DNSURL}$(hostname).example.net" -O - > /dev/null
                /usr/bin/wget -6 "${DNSURL}$(hostname).example.net" -O - > /dev/null
        elif [[ "${IFACE}" == "wlan0" ]] ; then
                /usr/bin/wget -4 "${DNSURL}$(hostname)-wlan.example.net" -O - > /dev/null
                /usr/bin/wget -6 "${DNSURL}$(hostname)-wlan.example.net" -O - > /dev/null
        fi
}
dhclient(8)

A dhclient-script(8) hook can we written FIXME untested

/etc/dhcp/dhclient-enter-hooks.d/ddns
#!/bin/bash
DNSUSER=user
DNSPASS=correcthorsebatterystaple
DNSURL="https://www.example.net/cgi-bin/ddns.pl?user=${DNSUSER}&pass=${DNSPASS}&hostname="
 
case $reason in
	BOUND|REBIND)
	logger -t $interface-$0 `/usr/bin/wget -4 "${DNSURL}$(hostname).example.net" -O - -o /dev/null` &
	logger -t $interface-$0 `/usr/bin/wget -6 "${DNSURL}$(hostname).example.net" -O - -o /dev/null` &
	;;
esac
NetworkManager

Though relying on dhclient(8), NetworkManager uses a different hook system.

On Debian (Jessie) systems, NetworkManager is hooked back into 'ifupdown'(8), which is preferred as more generic.

/etc/NetworkManager/dispatcher.d/ddns
#!/bin/bash
DNSUSER=user
DNSPASS=correcthorsebatterystaple
DNSURL="https://www.example.net/cgi-bin/ddns.pl?user=${DNSUSER}&pass=${DNSPASS}&hostname="
 
case $2 in
	up)
        case $1 in
                eth*)
                logger -t $1-$2 `/usr/bin/wget -4 "${DNSURL}$(hostname).example.net" -O - -o /dev/null` &
                logger -t $1-$2 `/usr/bin/wget -6 "${DNSURL}$(hostname).example.net" -O - -o /dev/null` &
                ;;
                wlan*)
                logger -t $1-$2 `/usr/bin/wget -4 "${DNSURL}$(hostname)-wlan.example.net" -O - -o /dev/null` &
                logger -t $1-$2 `/usr/bin/wget -6 "${DNSURL}$(hostname)-wlan.example.net" -O - -o /dev/null` &
                ;;
        esac
        ;;
esac

Web server

Every thing will be in /srv/www.

$ sudo mv /var/www /srv/www
$ sudo ln -s /srv/www /var/www

We create a /tmp directory which will be used for storing session information as the webserver is run in a chroot.

$ sudo mkdir /srv/www/tmp
$ sudo chown www:www /srv/www/tmp

The startup parameters have to be updated in /etc/rc.conf.local:

httpd_flags="-d /srv/www"

The config file (/srv/www/conf/httpd.conf) also has to be tweaked by replacing all instances of /var/www by /srv/www:

ServerAdmin webmaster@narf.ssji.net

FIXME ErrorDocuments

SSL Configuration

The server serves several VirtualHosts. It needs an SSL certificate which covers all of them, as well as its IP address. The standard /etc/ssl/openssl.conf is extended to include x509 v3 subjectAltName in CSRs. We also adjust some default parameters (see here for basic OpenSSL key management). The new option file is named /etc/ssl/openssl-ds.conf

[req]
default_keyfile       = /etc/ssl/private/distant-sun.narf.ssji.net.key
...
req_extensions         = v3_req
...
[ req_distinguished_name ]
countryName_default            = FR
localityName_default           = Paris
0.organizationName_default     = Narf
commonName_default             = distant-sun.narf.ssji.net
emailAddress_default           = admin@narf.ssji.net
...
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# Some CAs do not yet support subjectAltName in CSRs.
# Instead the additional names are form entries on web
# pages where one requests the certificate...
subjectAltName          = @alt_names

[alt_names]
DNS.1 = <IP>
DNS.2 = www.narf.ssji.net
DNS.3 = scm.narf.ssji.net
DNS.4 = narf.ssji.net
...

The certificate request is created as follows.

$ sudo openssl req -new -config /etc/ssl/openssl-ds.cnf -key /etc/ssl/private/distant-sun.narf.ssji.net.key -out /etc/ssl/distant-sun.narf.ssji.net.csr

It is then submitted to CAcert. The certificate they provide will be placed in /etc/ssl/distant-sun.narf.ssji.net.crt

SSL is then enabled by uncommenting the relevant sections in /srv/www/conf/httpd.conf. The SSLCertificAteFile and SSLCertificateKeyFile have to be adjusted to point to the key and certificate.

The httpd_flags in /etc/rc.conf.local has to be updated to include -D SSL.

Modules

Some modules like mod_rewrite or mod_proxy are highly useful. We activate them (by uncommenting the relevant line) with a default, safe, configuration.

LoadModule rewrite_module       /usr/lib/apache/modules/mod_rewrite.so
[...]
LoadModule proxy_module /usr/lib/apache/modules/libproxy.so
[...]
<IfModule mod_proxy.c>
ProxyRequests On

<Directory proxy:*>
    Order deny,allow
    Deny from all
    Allow from 127.0.0.1
</Directory>

#
# Enable/disable the handling of HTTP/1.1 "Via:" headers.
# ("Full" adds the server version; "Block" removes all outgoing Via: headers)
# Set to one of: Off | On | Full | Block
#
ProxyVia Block

</IfModule>

Server side includes

We activate server side includes

#
# To use server-parsed HTML files
#                                                                               
AddType text/html .shtml
AddHandler server-parsed .shtml

PHP

We install and configure the PHP modules

$ sudo pkg_add php5-core-5.1.4p2-hardened
$ /usr/local/sbin/phpxs -s

And add the relevant type line in /srv/www/conf/httpd.conf:

AddType application/x-httpd-php .php

No hardened version of the GD library without X11 exist. To solve the problem, we build all the PHP extensions from the ports. It may be necessary to had some specific packages (of which, X11, only necessary at compile time).

$ cd /usr/ports/www/php5/extensions
$ FLAVOR="no_x11 hardened no_dba no_dbase no_filepro no_gmp no_imap no_mysql no_mysqli no_ncurses no_odbc no_pgsql no_shmop no_snmp no_soap no_sybase_ct" sudo make install clean

PHP's mail() function relies on a sendmail binary being available. This is a problem in a chroot. This can be solved by using the mini_sendmail-chroot package.

$ sudo pkg_add -v femail-chroot
...
$ sudo cp -p /bin/sh /srv/www/bin # Needed for php's popen()

Then, PHP has to be configured to use it.

/etc/php-5.2.ini
[mail function]                                                                 
sendmail_path = "/bin/femail -t -i"

Virtual hosts

We want to manage several websites, we set up mass vhosting and the entries for narf.ssji.net in /srv/www/conf/httpd.conf:

(...)
LoadModule vhost_alias_module   /usr/lib/apache/modules/mod_vhost_alias.so
(...)
# get the server name from the Host: header
UseCanonicalName Off

# this log format can be split per-virtual-host based on the first field
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon

# include the server name in the filenames used to satisfy requests
VirtualDocumentRoot /htdocs/%-2.0.%-1/%-3/%-4
VirtualScriptAlias  /htdocs/%-2.0.%-1/%-3/%-4/cgi-bin

(...)
<Directory "/srv/www/htdocs">
    Options Indexes FollowSymLinks Includes Multiviews
    IndexOptions FancyIndexing FoldersFirst
    AllowOverride None
    Order deny,allow
    Deny from all
</Directory>

<Directory "/srv/www/htdocs/ssji.net/narf">
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

We also create the directory structure for the domain:

$ sudo mkdir -p /srv/www/htdocs/ssji.net/narf/_
$ sudo mkdir -p /srv/www/htdocs/ssji.net/narf/distant_sun
$ sudo mkdir -p /srv/www/htdocs/ssji.net/narf/www

Some .htaccess files are needed to ensure appropriate redirections in the two first directories:

Redirect permanent / http://www.narf.ssji.net/

mehani.name

Hosting of the websites for mehani.name is simply added by putting the appropriate files in /srv/www/htdocs/mehani.name:

$ sudo mkdir -p /srv/www/htdocs/mehani.name/_
$ sudo mkdir -p /srv/www/htdocs/mehani.name/www
$ sudo ln -s /srv/www/htdocs/mehani.name/www /srv/www/htdocs/mehani.name/www/w_

and the needed .htaccess in the _ directory (direct requests to http://mehani.name):

Redirect permanent / http://www.mehani.name/

and adding some lines to /srv/www/conf/httpd.conf:

<Directory "/srv/www/htdocs/mehani.name">
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

LDAP

We want to store all user-related information (GECOS, vCard, contact list, roster, etc.) into a handy and replicable LDAP base.

Naturally, the dn will be

dn: dc=narf,dc=ssji,dc=net

Server configuration

After adding the client and server package, it's time to configure the server in /etc/openldap/slapd.conf, the data will be stored in /srv/ldap in LDBM (OpenBSD default) format:

include         /etc/openldap/schema/core.schema

# Include the needed data schemes
include         /etc/openldap/schema/cosine.schema
include         /etc/openldap/schema/inetorgperson.schema
include         /etc/openldap/schema/nis.schema

# Use md5 to hash the passwords
# Password are generated using /usr/local/sbin/slappasswd -h {Md5}
password-hash {Md5}

# Define SSL and TLS properties (optional)
TLSCertificateFile /etc/ssl/openldap/ssl/ldap.narf.ssji.net.crt
TLSCertificateKeyFile /etc/openldap/ssl/ldap.narf.ssji.net.key
TLSCACertificateFile /etc/ssl/private/ca.narf.ssji.net.key

(...)
access to attrs="userPassword"
  by dn="uid=root,ou=people,dc=narf,dc=ssji,dc=net" write
  by self write
  by anonymous auth
  by * none
  
access to attrs=userPassword,gecos,description,loginShell
  by dn="uid=root,ou=people,dc=narf,dc=ssji,dc=net" write
  by self write
  by * none

access to *
  by dn="uid=root,ou=people,dc=narf,dc=ssji,dc=net" write
  by users read
  by anonymous auth
#  by * search

(...)
database        ldbm
suffix          "dc=narf,dc=ssji,dc=net"
rootdn          "cn=Manager,dc=narf,dc=ssji,dc=net"

SSL certificates

$ sudo mkdir -p /etc/openldap/ssl
$ sudo openssl genrsa -out /etc/openldap/ssl/ldap.narf.ssji.net.key 1024
$ sudo chmod -R g-rwx,u-rwx /etc/openldap/ssl
$ openssl req -new -key /etc/openldap/ssl/ldap.narf.ssji.net.key -out /etc/openldap/ssl/ldap.narf.ssji.net.csr
(...)
Country Name (2 letter code) []:FR
State or Province Name (full name) []:France
Locality Name (eg, city) []:Paris
Organization Name (eg, company) []:narf.ssji.net
Organizational Unit Name (eg, section) []:n/a
Common Name (eg, fully qualified host name) []:ldap.narf.ssji.net
Email Address []:root@distant-sun.narf.ssji.net

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$ sudo openssl x509 -req -days 999999 -in /etc/openldap/ssl/ldap.narf.ssji.net.csr -signkey /etc/ssl/private/ca.narf.ssji.net.key -out /etc/openldap/ssl/ldap.narf.ssji.net.crt
Signature ok
subject=/C=FR/ST=France/L=Paris/O=narf.ssji.net/OU=n/a/CN=ldap.narf.ssji.net/emailAddress=root@distant-sun.narf.ssji.net
Getting Private key

Startup

We finally add what's needed to start de daemon at bootup in /etc/rc.local:

echo -n ' slapd'
/usr/local/libexec/slapd -h 'ldaps://'

FIXME chroot & priviledges drop

Client configuration

We set the clients' parameters in /etc/openldap/ldap.conf:

BASE    dc=narf, dc=ssji, dc=net
URI    ldaps://ldap.narf.ssji.net

References

SCM

SVN

svnserve is started by inetd. Service lines for both IPv4 and IPv6 are added to /etc/inetd.conf.

svn stream tcp nowait root /usr/local/bin/svnserve svnserve -i -r  /srv/svn
svn stream tcp6 nowait root /usr/local/bin/svnserve svnserve -i -r  /srv/svn

Unfortunately, the base system doesn't have a service entry for SVN. It has to be added to /etc/services for inetd to work.

svn             3690/tcp                        # Subversion

A hole also has to be dug in the firewall. This is easily done by manipulating our TCP_IN variable in /etc/pf.conf to add svn.

Then, the configuration files have to be reloaded.

$ sudo pkill -HUP inetd
$ sudo pfctl -f /etc/pf.conf

Trac

Installation

Trac will be used to have a nice access to versionned files. The configuration files will stand in /srv/trac. The daemon will run as user _trac

$ sudo pkg_add -trac
$ mkdir /srv/trac # if it doesn't exist yet
$ sudo useradd -d /srv/trac -g =uid -r 100..999 -s /sbin/nologin _trac
$ sudo -u _trac trac-admin /srv/trac initenv --inherit=/srv/trac/trac.ini # only the first time
[...]
$ sudo chgrp -R _trac /srv/trac/
$ sudo chmod -R g+rw /srv/trac/

Bootup

It should then be started at bootup, in /etc/rc.local, serving several projects, and identifying user from httpd's base:

if [ -x /usr/local/bin/tracd ]; then
  echo -n ' tracd' 
  export TRAC_ENV_INDEX_TEMPLATE=/srv/trac/templates/index.html
  sudo -u _trac /usr/local/bin/tracd -d -p 8000 \
        --basic-auth="*",/srv/www/.htpasswd, \
        --base-path=/svn \
        -e /srv/trac/projects >/dev/null 2>&1
fi

Nota: this requires setting the following with visudo.

/etc/sudoers
Defaults env_keep +="TRAC_ENV_INDEX_TEMPLATE"

Proxying via Apache

A transparent proxy is added in Apache's /src/www/conf/httpd.conf in order to “mount” Trac into the exposed HTTP tree.

<IfModule proxy.c>
ProxyPass /trac http://localhost:8000
ProxyPassReverse /trac http://localhost:8000
<Directory proxy:http://localhost:8000>
        Order allow,deny
        Allow from all
</Directory>
</IfModule>

or, if using a virtual host,

<VirtualHost *:80 *:443>
        ServerName scm.narf.ssji.net
        DocumentRoot /srv/www/sites/narf.ssji.net/www
        ProxyPassReverse / http://127.0.0.1:8000/

        RewriteEngine On
        RewriteCond %{HTTPS}            !on
        RewriteRule ^/([^/]+)/login     https://scm.narf.ssji.net/$1/login  [R,L]
        RewriteRule ^/(.*)     http://127.0.0.1:8000/$1 [P]
</VirtualHost>

Backups

Using rdiff-backup to sync locally or with a distant server.

Remote login

rdiff-backup can operate over ssh. The server connects to the backup server as an unprivileged user using ssh and the host RSA key generated at first startup (/etc/ssh/ssh_host_rsa_key).

The authorized_keys file for the user on the backup server restricts operation to rdiff-backup in server mode, and is populated using public key /etc/ssh/ssh_host_rsa_key.pub:

command="rdiff-backup --server --restrict /srv/rdiff-backup/USER/",no-port-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAA[...]== root@distant-sun.narf.ssji.net

It may be good to deploy the backup server's public key in the local known hosts list /etc/ssh/known_hosts to avoid questions when the script runs unattended.

Final Details

File /etc/changelist tells OpenBSD which file to keep a close eye on and warn about modifications. The configuration files modified earlier which were not part of the base system have to be listed there. If they contain sensitive information, a + prepended to their name will have the system only store and compare hashes.

References

projets/distantsun.txt · Dernière modification: 2014/02/17 11:44 par oliviermehani