summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--services/accounts/openldap.nix190
1 files changed, 190 insertions, 0 deletions
diff --git a/services/accounts/openldap.nix b/services/accounts/openldap.nix
new file mode 100644
index 0000000..764d7bf
--- /dev/null
+++ b/services/accounts/openldap.nix
@@ -0,0 +1,190 @@
+# TODO: it would be nice to have an abstraction layer that deals with long
+# lines for us, analogous to the one we have for haproxy. it's not immediately
+# clear to me what the best design for that is; ideally we could use it with
+# any olcAccess section. it's not a need that's likely to arise with any
+# section but those, although in principle it could.
+#
+# TODO: this is not hooked up to anything yet; when you feel it's ready to
+# turn on, add it to the list of imported modules in flake.nix
+#
+# TODO: once we have all
+{ lib, pkgs, ... }:
+
+{
+  imports = [ ];
+
+  services.openldap = {
+    enable = true;
+
+    urlList = [
+      "ldap:///"
+    ];
+
+    mutableConfig = false;
+
+    settings = {
+      attrs = {
+        olcLogLevel = "config stats";
+        olcAuthzPolicy = "to";
+      };
+
+      children = {
+        # TODO: There are a lot of different "person" types, and we should
+        # really pick one. It is my firm political belief that first name,
+        # last name, and display name should all be optional. Also, for our
+        # needs organizationally, since we have both internal and external
+        # users, sometimes we want to have uid and sometimes we want to have
+        # email and sometimes we want both. I don't think any of the existing
+        # schemas do this, but feel free to take a look and see how close we
+        # can get. If we decide it's time to make our own, I'll register an
+        # ISL private enterprise number, but it's bureaucracy I'd prefer to
+        # skip if we can.
+        "cn=schema".includes = [
+          # TODO: We probably don't need all these schemas.
+          "${pkgs.openldap}/etc/schema/core.ldif"
+          "${pkgs.openldap}/etc/schema/cosine.ldif"
+          "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+          "${pkgs.openldap}/etc/schema/nis.ldif"
+        ];
+
+        "olcDatabase={-1}frontend".attrs = {
+          objectClass = [ "olcDatabaseConfig" "olcFrontendConfig" ];
+
+          olcDatabase = "{-1}frontend";
+
+          # Otherwise the default is the root, which is not world-readable.
+          olcDefaultSearchBase = "dc=internetsafetylabs,dc=org";
+
+          # This is the access control list that pertains to configuration
+          # settings. This needs to be set explicitly or else you can't check
+          # what the settings are at runtime, and user-management portals
+          # benefit from being able to.
+          olcAccess = [
+            ''
+              {0}to dn.base=""
+                by * read
+            '' ''
+              {1}to dn.base="cn=subschema"
+                by * read
+            '' ''
+              {2}to *
+                by group.exact="cn=ldap-admins,ou=groups,dc=internetsafetylabs,dc=org" read
+                by * none
+            ''
+          ];
+        };
+
+        "olcDatabase={0}config".attrs = {
+          objectClass = [ "olcDatabaseConfig" ];
+
+          olcDatabase = "{0}config";
+
+          # These things were in the default setup, before we did anything
+          # to explicitly set up the config database. Setting up the olcAccess
+          # values overrode them, so here they are in case they're important.
+          # They haven't been the subject of much thought.
+          olcAddContentAcl = "TRUE";
+          olcLastMod = "TRUE";
+          olcLastBind = "FALSE";
+          olcLastBindPrecision = "0";
+          olcMaxDerefDepth = "15";
+          olcReadOnly = "FALSE";
+          olcSyncUseSubentry = "FALSE";
+          olcMonitoring = "FALSE";
+
+          olcAccess = [
+            ''
+              {0}to dn.subtree="cn=config"
+                by group.exact="cn=ldap-admins,ou=groups,dc=internetsafetylabs,dc=org" read
+                by group.exact="cn=ldap-frontends,ou=groups,dc=internetsafetylabs,dc=org" read
+                by * none
+            '' ''
+              {1}to *
+                by * none
+            ''
+          ];
+        };
+
+        "olcDatabase={1}mdb" = {
+          attrs = {
+            objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+
+            olcDatabase = "{1}mdb";
+            olcDbDirectory = "/var/lib/openldap/data";
+
+            olcSuffix = "dc=internetsafetylabs,dc=org";
+
+            # This is needed because the memberof overlay has to do its
+            # changes as a DN.
+            olcRootDN = "cn=admin,dc=internetsafetylabs,dc=org";
+
+            # This should probably be commented out when there's nothing
+            # horrible going on. It's important for bootstrapping and for
+            # recovering broken situations, but it's a security risk.
+            #
+            # The important property of logins that use the root DN and
+            # password is that logging in that way ignores all ACLs and also
+            # the account doesn't have to actually exist in the database.
+            #
+            # TODO: we might consider adding some sort of auto-expiration
+            # feature to the secret manager?
+            #olcRootPW = builtins.readFile
+            #    "/etc/nixos/secrets/openldap/root-password";
+
+            # TODO: once we have better formatting for this code (see the TODO
+            # about an abstraction layer for long lines), the thinking behind
+            # each individual rule should be documented
+            olcAccess = [
+              ''
+                {0}to attrs=userPassword
+                  by self =xw
+                  by group.exact="cn=ldap-admins,ou=groups,dc=internetsafetylabs,dc=org" =xw
+                  by group.exact="cn=ldap-password-managers,ou=groups,dc=internetsafetylabs,dc=org" =xw
+                  by anonymous auth
+                  by * none
+              '' ''
+                {1}to attrs=authzTo
+                  by group.exact="cn=ldap-frontends,ou=groups,dc=internetsafetylabs,dc=org" auth
+                  by group.exact="cn=ldap-admins,ou=groups,dc=internetsafetylabs,dc=org" write
+                  by * none
+              '' ''
+                {2}to attrs=memberOf
+                  by group.exact="cn=ldap-admins,ou=groups,dc=internetsafetylabs,dc=org" read
+                  by self read
+                  by * none
+              '' ''
+                {3}to dn.subtree="dc=internetsafetylabs,dc=org"
+                  by self write
+                  by group.exact="cn=ldap-admins,ou=groups,dc=internetsafetylabs,dc=org" write
+                  by users read
+                  by * auth
+              '' ''
+                {4}to *
+                  by self write
+                  by users read
+                  by * none
+              ''
+            ];
+          };
+
+          # The `memberof` operator is extremely useful in writing ACLs, so
+          # we enable it.
+          #
+          # We specifically turn on referential integrity for it, meaning
+          # the server will reject edits that would break the bidirectional
+          # nature of the link. These error messages can be confusing, so it's
+          # worth knowing about.
+          children = {
+            "olcOverlay={0}memberof".attrs = {
+              objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
+
+              olcOverlay = "memberof";
+              olcMemberOfRefint = "TRUE";
+            };
+          };
+        };
+      };
+    };
+  };
+}
+