liminix

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

configuration.adoc (8835B)


      1 == Configuration
      2 
      3 There are many things you can specify in a configuration, but most
      4 commonly you need to change:
      5 
      6 * which services (processes) to run
      7 * what packages to install
      8 * permitted users and groups
      9 * Linux kernel configuration options
     10 * Busybox applets
     11 * filesystem layout
     12 
     13 === Modules
     14 
     15 *Modules* are a means of abstraction which allow "bundling" of
     16 configuration options related to a common purpose or theme. For example,
     17 the `+dnsmasq+` module defines a template for a dnsmasq service, ensures
     18 that the dnsmasq package is installed, and provides a dnsmasq user and
     19 group for the service to run as. The `+ppp+` module defines a service
     20 template and also enables various PPP-related kernel configuration.
     21 
     22 Not all modules are included in the configuration by default, because
     23 that would mean that the kernel (and the Busybox binary providing common
     24 CLI tools) was compiled with many unnecessary bells and whistles and
     25 therefore be bigger than needed. (This is not purely an academic concern
     26 if your device has little flash storage). Therefore, specifying a
     27 service is usually a two-step process. For example, to add an NTP
     28 service you first add `+modules/ntp+` to your `+imports+` list, then you
     29 create a service by calling `+config.system.service.ntp.build { .... }+`
     30 with the appropriate service-dependent configuration parameters.
     31 
     32 [source,nix]
     33 ----
     34 let svc = config.system.service;
     35 in {
     36   # ...
     37   imports = [
     38     ./modules/ntp
     39     # ....
     40   ];
     41   config.services.ntp = svc.ntp.build {
     42     pools = { "pool.ntp.org" = ["iburst"]; };
     43     makestep = { threshold = 1.0; limit = 3; };
     44   };
     45 ----
     46 
     47 Merely including the module won't define the service on its own: it only
     48 creates the template in `+config.system.service.foo+` and you have to
     49 create an actual service using the template. This is an intentional
     50 choice to allow the creation of multiple differently-configured services
     51 based on the same template - perhaps e.g. when you have multiple
     52 networks (VPNs etc) in different trust domains, or you want to run two
     53 SSH daemons on different ports. (For the background to this, please
     54 refer to the `+architecture decision record <adr/module-system>+`)
     55 
     56 [TIP]
     57 ====
     58 Liminix modules should be quite familiar (but also different) if you
     59 already know how to use NixOS modules. We use the NixOS module
     60 infrastructure code, meaning that you should recognise the syntax, the
     61 type system, the rules for combining configuration values from different
     62 sources. We don't use the NixOS modules themselves, because the
     63 underlying system is not similar enough for them to work.
     64 ====
     65 
     66 [[configuration-services]]
     67 === Services
     68 
     69 In Liminix a service is any kind of long-running task or process on the
     70 system, that is managed (started, stopped, and monitored) by a service
     71 supervisor. A typical SOHO router might have services to
     72 
     73 * answer DHCP and DNS requests from the LAN
     74 * provide a wireless access point
     75 * connect using PPPoE or L2TP to an upstream network
     76 * start/stop the firewall
     77 * enable/disable IP packet forwarding
     78 * mount filesystems
     79 
     80 (Some of these might not be considered services using other definitions
     81 of the term: for example, this L2TP process would be a "client" in the
     82 client/server classification; and enabling packet forwarding doesn't
     83 require any long-lived process - just a setting to be toggled. However,
     84 there is value in being able to use the same abstractions for all the
     85 things to manage them and specify their dependency relationships - so in
     86 Liminix "everything is a service")
     87 
     88 The service supervision system enables service health monitoring,
     89 restart of unhealthy services, and failover to "backup" services when a
     90 primary service fails or its dependencies are unavailable. The intention
     91 is that you have a framework in which you can specify policy
     92 requirements like "ethernet wan dhcp-client should be restarted if it
     93 crashes, but if it can't start because the hardware link is down, then
     94 4G ppp service should be started instead".
     95 
     96 Any attribute in [.title-ref]#config.services# will become part of the
     97 default set of services that s6-rc will try to bring up. Services are
     98 usually started at boot time, but *controlled services* are those that
     99 are required only in particular contexts. For example, a service to
    100 mount a USB backup drive should run only when the drive is attached to
    101 the system. Liminix currently implements three kinds of controlled
    102 service:
    103 
    104 * "uevent-rule" service controllers use sysfs/uevent to identify when
    105 particular hardware devices are present, and start/stop a controlled
    106 service appropriately.
    107 * the "round-robin" service controller is used for service failover: it
    108 allows you to specify a list of services and runs each of them in turn
    109 until it exits, then runs the next.
    110 * the "health-check" service wraps another service, and runs a "health
    111 check" command at regular intervals. When the health check fails,
    112 indicating that the wrapped service is not working, it is terminated and
    113 allowed to restart.
    114 
    115 === Runtime secrets (external vault)
    116 
    117 Secrets (such as wifi passphrases, PPP username/password, SSH keys, etc)
    118 that you provide as literal values in `+configuration.nix+` are
    119 processed into into config files and scripts at build time, and
    120 eventually end up in various files in the (world-readable)
    121 `+/nix/store+` before being baked into a flashable image. To change a
    122 secret - whether due to a compromise, or just as part of to a routine
    123 key rotation - you need to rebuild the configuration and potentially
    124 reflash the affected devices.
    125 
    126 To avoid this, you may instead use a "secrets service", which is a
    127 mechanism for your device to fetch secrets from a source external to the
    128 Nix store, and create at runtime the configuration files and scripts
    129 that start the services which require them.
    130 
    131 Not every possible parameter to every possible service is configurable
    132 using a secrets service. Parameters which can be configured this way are
    133 those with the type `+liminix.lib.types.replacable+`. At the time this
    134 document was written, these include:
    135 
    136 * ppp (pppoe and l2tp): `+username+`, `+password+`
    137 * ssh: `+authorizedKeys+`
    138 * hostapd: all parameters (most likely to be useful for
    139 `+wpa_passphrase+`)
    140 
    141 To use a runtime secret for any of these parameters:
    142 
    143 * create a secrets service to specify the source of truth for secrets
    144 * use the `+outputRef+` function in the service parameter to specify the
    145 secrets service and path
    146 
    147 For example, given you had an HTTPS server hosting a JSON file with the
    148 structure
    149 
    150 [source,json]
    151 ----
    152 "ssh": {
    153   "authorizedKeys": {
    154 "root": [ "ssh-rsa ....",  "ssh-rsa ....", ... ]
    155 "guest": [ "ssh-rsa ....",  "ssh-rsa ....", ... ]
    156   }
    157 }
    158 ----
    159 
    160 you could use a `+configuration.nix+` fragment something like this to
    161 make those keys visible to ssh:
    162 
    163 [source,nix]
    164 ----
    165 services.secrets = svc.secrets.outboard.build {
    166   name = "secret-service";
    167   url = "http://10.0.0.1/secrets.json";
    168   username = "secrets";
    169   password = "liminix";
    170   interval = 30; # minutes
    171   dependencies = [ config.services.lan ];
    172 };
    173 services.sshd = svc.ssh.build {
    174   authorizedKeys = outputRef config.services.secrets "ssh/authorizedKeys";
    175 };
    176 ----
    177 
    178 There are presently two implementations of a secrets service:
    179 
    180 ===== Outboard secrets (HTTPS)
    181 
    182 This service expects a URL to a JSON file containing all the secrets.
    183 
    184 You may specify a username and password along with the URL, which are
    185 used if the file is password-protected (HTTP Basic authentication). Note
    186 that this is not a protection against a malicious local user: the
    187 username and password are normal build-time parameters so will be
    188 readable in the Nix store. This is a mitigation against the URL being
    189 accidentally discovered due to e.g. a log file or error message on the
    190 server leaking.
    191 
    192 ===== Tang secrets (encrypted local file)
    193 
    194 Aternatively, secrets may be stored locally on the device, in a file
    195 that has been encrypted using https://github.com/latchset/tang[Tang].
    196 
    197 ____
    198 Tang is a server for binding data to network presence.
    199 
    200 This sounds fancy, but the concept is simple. You have some data, but
    201 you only want it to be available when the system containing the data is
    202 on a certain, usually secure, network.
    203 ____
    204 
    205 [source,nix]
    206 ----
    207 services.secrets = svc.secrets.tang.build {
    208   name = "secret-service";
    209   path = "/run/mnt/usbstick/secrets.json.jwe";
    210   interval = 30; # minutes
    211   dependencies = [ config.services.mount-usbstick ];
    212 };
    213 ----
    214 
    215 The encryption uses the same scheme/algorithm as
    216 https://github.com/latchset/clevis[Clevis] : you may use the
    217 https://github.com/latchset/clevis?tab=readme-ov-file#pin-tang[Clevis
    218 instructions] to encrypt the file on another host and then copy it to
    219 your Liminix device, or you can use `+tangc encrypt+` to encrypt
    220 directly on the device. (That latter approach may pose a chicken/egg
    221 problem if the device needs secrets to boot up and run the services you
    222 are relying on in order to login).
    223