about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests/web-apps/netbox.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests/web-apps/netbox.nix')
-rw-r--r--nixpkgs/nixos/tests/web-apps/netbox.nix318
1 files changed, 318 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/web-apps/netbox.nix b/nixpkgs/nixos/tests/web-apps/netbox.nix
new file mode 100644
index 000000000000..233f16a8fe0d
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/netbox.nix
@@ -0,0 +1,318 @@
+let
+  ldapDomain = "example.org";
+  ldapSuffix = "dc=example,dc=org";
+
+  ldapRootUser = "admin";
+  ldapRootPassword = "foobar";
+
+  testUser = "alice";
+  testPassword = "verySecure";
+  testGroup = "netbox-users";
+in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: {
+  name = "netbox";
+
+  meta = with lib.maintainers; {
+    maintainers = [ minijackson n0emis ];
+  };
+
+  nodes.machine = { config, ... }: {
+    virtualisation.memorySize = 2048;
+    services.netbox = {
+      enable = true;
+      package = netbox;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+
+      enableLdap = true;
+      ldapConfigPath = pkgs.writeText "ldap_config.py" ''
+        import ldap
+        from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+        AUTH_LDAP_SERVER_URI = "ldap://localhost/"
+
+        AUTH_LDAP_USER_SEARCH = LDAPSearch(
+            "ou=accounts,ou=posix,${ldapSuffix}",
+            ldap.SCOPE_SUBTREE,
+            "(uid=%(user)s)",
+        )
+
+        AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+            "ou=groups,ou=posix,${ldapSuffix}",
+            ldap.SCOPE_SUBTREE,
+            "(objectClass=posixGroup)",
+        )
+        AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+
+        # Mirror LDAP group assignments.
+        AUTH_LDAP_MIRROR_GROUPS = True
+
+        # For more granular permissions, we can map LDAP groups to Django groups.
+        AUTH_LDAP_FIND_GROUP_PERMS = True
+      '';
+    };
+
+    services.nginx = {
+      enable = true;
+
+      recommendedProxySettings = true;
+
+      virtualHosts.netbox = {
+        default = true;
+        locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
+        locations."/static/".alias = "/var/lib/netbox/static/";
+      };
+    };
+
+    # Adapted from the sssd-ldap NixOS test
+    services.openldap = {
+      enable = true;
+      settings = {
+        children = {
+          "cn=schema".includes = [
+            "${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}mdb" = {
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+              olcDatabase = "{1}mdb";
+              olcDbDirectory = "/var/lib/openldap/db";
+              olcSuffix = ldapSuffix;
+              olcRootDN = "cn=${ldapRootUser},${ldapSuffix}";
+              olcRootPW = ldapRootPassword;
+            };
+          };
+        };
+      };
+      declarativeContents = {
+        ${ldapSuffix} = ''
+          dn: ${ldapSuffix}
+          objectClass: top
+          objectClass: dcObject
+          objectClass: organization
+          o: ${ldapDomain}
+
+          dn: ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: ou=accounts,ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: uid=${testUser},ou=accounts,ou=posix,${ldapSuffix}
+          objectClass: person
+          objectClass: posixAccount
+          userPassword: ${testPassword}
+          homeDirectory: /home/${testUser}
+          uidNumber: 1234
+          gidNumber: 1234
+          cn: ""
+          sn: ""
+
+          dn: ou=groups,ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix}
+          objectClass: posixGroup
+          gidNumber: 2345
+          memberUid: ${testUser}
+        '';
+      };
+    };
+
+    users.users.nginx.extraGroups = [ "netbox" ];
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+
+  testScript = let
+    changePassword = pkgs.writeText "change-password.py" ''
+      from django.contrib.auth.models import User
+      u = User.objects.get(username='netbox')
+      u.set_password('netbox')
+      u.save()
+    '';
+  in ''
+    from typing import Any, Dict
+    import json
+
+    start_all()
+    machine.wait_for_unit("netbox.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
+
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://[::1]:8001 | grep '<title>Home | NetBox</title>'"
+        )
+
+    with subtest("Staticfiles are generated"):
+        machine.succeed("test -e /var/lib/netbox/static/netbox.js")
+
+    with subtest("Superuser can be created"):
+        machine.succeed(
+            "netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com"
+        )
+        # Django doesn't have a "clean" way of inputting the password from the command line
+        machine.succeed("cat '${changePassword}' | netbox-manage shell")
+
+    machine.wait_for_unit("network.target")
+
+    with subtest("Home screen loads from nginx"):
+        machine.succeed(
+            "curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'"
+        )
+
+    with subtest("Staticfiles can be fetched"):
+        machine.succeed("curl -sSfL http://localhost/static/netbox.js")
+        machine.succeed("curl -sSfL http://localhost/static/docs/")
+
+    with subtest("Can interact with API"):
+        json.loads(
+            machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'")
+        )
+
+    def login(username: str, password: str):
+        encoded_data = json.dumps({"username": username, "password": password})
+        uri = "/users/tokens/provision/"
+        result = json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-X POST "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"'http://localhost/api{uri}' "
+                f"--data '{encoded_data}'"
+            )
+        )
+        return result["key"]
+
+    with subtest("Can login"):
+        auth_token = login("netbox", "netbox")
+
+    def get(uri: str):
+        return json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-H 'Accept: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                f"'http://localhost/api{uri}'"
+            )
+        )
+
+    def delete(uri: str):
+        return machine.succeed(
+            "curl -sSfL "
+            f"-X DELETE "
+            "-H 'Accept: application/json' "
+            f"-H 'Authorization: Token {auth_token}' "
+            f"'http://localhost/api{uri}'"
+        )
+
+
+    def data_request(uri: str, method: str, data: Dict[str, Any]):
+        encoded_data = json.dumps(data)
+        return json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                f"-X {method} "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                f"'http://localhost/api{uri}' "
+                f"--data '{encoded_data}'"
+            )
+        )
+
+    def post(uri: str, data: Dict[str, Any]):
+      return data_request(uri, "POST", data)
+
+    def patch(uri: str, data: Dict[str, Any]):
+      return data_request(uri, "PATCH", data)
+
+    with subtest("Can create objects"):
+        result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
+        site_id = result["id"]
+
+        # Example from:
+        # http://netbox.extra.cea.fr/static/docs/integrations/rest-api/#creating-a-new-object
+        post("/ipam/prefixes/", {"prefix": "192.0.2.0/24", "site": site_id})
+
+        result = post(
+            "/dcim/manufacturers/",
+            {"name": "Test manufacturer", "slug": "test-manufacturer"}
+        )
+        manufacturer_id = result["id"]
+
+        # Had an issue with device-types before NetBox 3.4.0
+        result = post(
+            "/dcim/device-types/",
+            {
+                "model": "Test device type",
+                "manufacturer": manufacturer_id,
+                "slug": "test-device-type",
+            },
+        )
+        device_type_id = result["id"]
+
+    with subtest("Can list objects"):
+        result = get("/dcim/sites/")
+
+        assert result["count"] == 1
+        assert result["results"][0]["id"] == site_id
+        assert result["results"][0]["name"] == "Test site"
+        assert result["results"][0]["description"] == ""
+
+        result = get("/dcim/device-types/")
+        assert result["count"] == 1
+        assert result["results"][0]["id"] == device_type_id
+        assert result["results"][0]["model"] == "Test device type"
+
+    with subtest("Can update objects"):
+        new_description = "Test site description"
+        patch(f"/dcim/sites/{site_id}/", {"description": new_description})
+        result = get(f"/dcim/sites/{site_id}/")
+        assert result["description"] == new_description
+
+    with subtest("Can delete objects"):
+        # Delete a device-type since no object depends on it
+        delete(f"/dcim/device-types/{device_type_id}/")
+
+        result = get("/dcim/device-types/")
+        assert result["count"] == 0
+
+    with subtest("Can use the GraphQL API"):
+        encoded_data = json.dumps({
+            "query": "query { prefix_list { prefix, site { id, description } } }",
+        })
+        result = json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                "'http://localhost/graphql/' "
+                f"--data '{encoded_data}'"
+            )
+        )
+
+        assert len(result["data"]["prefix_list"]) == 1
+        assert result["data"]["prefix_list"][0]["prefix"] == "192.0.2.0/24"
+        assert result["data"]["prefix_list"][0]["site"]["id"] == str(site_id)
+        assert result["data"]["prefix_list"][0]["site"]["description"] == new_description
+
+    with subtest("Can login with LDAP"):
+        machine.wait_for_unit("openldap.service")
+        login("alice", "${testPassword}")
+
+    with subtest("Can associate LDAP groups"):
+        result = get("/users/users/?username=${testUser}")
+
+        assert result["count"] == 1
+        assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
+  '';
+})