python312Packages.rmscene: 0.6.0 -> 0.6.1 (#364388)
[NixPkgs.git] / nixos / tests / web-apps / netbox.nix
blobbc3fec264543c4b375fa6f2d379e3faf9ea474c1
1 let
2   ldapDomain = "example.org";
3   ldapSuffix = "dc=example,dc=org";
5   ldapRootUser = "admin";
6   ldapRootPassword = "foobar";
8   testUser = "alice";
9   testPassword = "verySecure";
10   testGroup = "netbox-users";
12 import ../make-test-python.nix (
13   {
14     lib,
15     pkgs,
16     netbox,
17     ...
18   }:
19   {
20     name = "netbox";
22     meta = with lib.maintainers; {
23       maintainers = [
24         minijackson
25       ];
26     };
28     nodes.machine =
29       { config, ... }:
30       {
31         virtualisation.memorySize = 2048;
32         services.netbox = {
33           enable = true;
34           package = netbox;
35           secretKeyFile = pkgs.writeText "secret" ''
36             abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
37           '';
39           enableLdap = true;
40           ldapConfigPath = pkgs.writeText "ldap_config.py" ''
41             import ldap
42             from django_auth_ldap.config import LDAPSearch, PosixGroupType
44             AUTH_LDAP_SERVER_URI = "ldap://localhost/"
46             AUTH_LDAP_USER_SEARCH = LDAPSearch(
47                 "ou=accounts,ou=posix,${ldapSuffix}",
48                 ldap.SCOPE_SUBTREE,
49                 "(uid=%(user)s)",
50             )
52             AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
53                 "ou=groups,ou=posix,${ldapSuffix}",
54                 ldap.SCOPE_SUBTREE,
55                 "(objectClass=posixGroup)",
56             )
57             AUTH_LDAP_GROUP_TYPE = PosixGroupType()
59             # Mirror LDAP group assignments.
60             AUTH_LDAP_MIRROR_GROUPS = True
62             # For more granular permissions, we can map LDAP groups to Django groups.
63             AUTH_LDAP_FIND_GROUP_PERMS = True
64           '';
65         };
67         services.nginx = {
68           enable = true;
70           recommendedProxySettings = true;
72           virtualHosts.netbox = {
73             default = true;
74             locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
75             locations."/static/".alias = "/var/lib/netbox/static/";
76           };
77         };
79         # Adapted from the sssd-ldap NixOS test
80         services.openldap = {
81           enable = true;
82           settings = {
83             children = {
84               "cn=schema".includes = [
85                 "${pkgs.openldap}/etc/schema/core.ldif"
86                 "${pkgs.openldap}/etc/schema/cosine.ldif"
87                 "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
88                 "${pkgs.openldap}/etc/schema/nis.ldif"
89               ];
90               "olcDatabase={1}mdb" = {
91                 attrs = {
92                   objectClass = [
93                     "olcDatabaseConfig"
94                     "olcMdbConfig"
95                   ];
96                   olcDatabase = "{1}mdb";
97                   olcDbDirectory = "/var/lib/openldap/db";
98                   olcSuffix = ldapSuffix;
99                   olcRootDN = "cn=${ldapRootUser},${ldapSuffix}";
100                   olcRootPW = ldapRootPassword;
101                 };
102               };
103             };
104           };
105           declarativeContents = {
106             ${ldapSuffix} = ''
107               dn: ${ldapSuffix}
108               objectClass: top
109               objectClass: dcObject
110               objectClass: organization
111               o: ${ldapDomain}
113               dn: ou=posix,${ldapSuffix}
114               objectClass: top
115               objectClass: organizationalUnit
117               dn: ou=accounts,ou=posix,${ldapSuffix}
118               objectClass: top
119               objectClass: organizationalUnit
121               dn: uid=${testUser},ou=accounts,ou=posix,${ldapSuffix}
122               objectClass: person
123               objectClass: posixAccount
124               userPassword: ${testPassword}
125               homeDirectory: /home/${testUser}
126               uidNumber: 1234
127               gidNumber: 1234
128               cn: ""
129               sn: ""
131               dn: ou=groups,ou=posix,${ldapSuffix}
132               objectClass: top
133               objectClass: organizationalUnit
135               dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix}
136               objectClass: posixGroup
137               gidNumber: 2345
138               memberUid: ${testUser}
139             '';
140           };
141         };
143         users.users.nginx.extraGroups = [ "netbox" ];
145         networking.firewall.allowedTCPPorts = [ 80 ];
146       };
148     testScript =
149       let
150         changePassword = pkgs.writeText "change-password.py" ''
151           from users.models import User
152           u = User.objects.get(username='netbox')
153           u.set_password('netbox')
154           u.save()
155         '';
156       in
157       ''
158         from typing import Any, Dict
159         import json
161         start_all()
162         machine.wait_for_unit("netbox.target")
163         machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
165         with subtest("Home screen loads"):
166             machine.succeed(
167                 "curl -sSfL http://[::1]:8001 | grep '<title>Home | NetBox</title>'"
168             )
170         with subtest("Staticfiles are generated"):
171             machine.succeed("test -e /var/lib/netbox/static/netbox.js")
173         with subtest("Superuser can be created"):
174             machine.succeed(
175                 "netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com"
176             )
177             # Django doesn't have a "clean" way of inputting the password from the command line
178             machine.succeed("cat '${changePassword}' | netbox-manage shell")
180         machine.wait_for_unit("network.target")
182         with subtest("Home screen loads from nginx"):
183             machine.succeed(
184                 "curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'"
185             )
187         with subtest("Staticfiles can be fetched"):
188             machine.succeed("curl -sSfL http://localhost/static/netbox.js")
189             machine.succeed("curl -sSfL http://localhost/static/docs/")
191         def login(username: str, password: str):
192             encoded_data = json.dumps({"username": username, "password": password})
193             uri = "/users/tokens/provision/"
194             result = json.loads(
195                 machine.succeed(
196                     "curl -sSfL "
197                     "-X POST "
198                     "-H 'Accept: application/json' "
199                     "-H 'Content-Type: application/json' "
200                     f"'http://localhost/api{uri}' "
201                     f"--data '{encoded_data}'"
202                 )
203             )
204             return result["key"]
206         with subtest("Can login"):
207             auth_token = login("netbox", "netbox")
209         def get(uri: str):
210             return json.loads(
211                 machine.succeed(
212                     "curl -sSfL "
213                     "-H 'Accept: application/json' "
214                     f"-H 'Authorization: Token {auth_token}' "
215                     f"'http://localhost/api{uri}'"
216                 )
217             )
219         def delete(uri: str):
220             return machine.succeed(
221                 "curl -sSfL "
222                 f"-X DELETE "
223                 "-H 'Accept: application/json' "
224                 f"-H 'Authorization: Token {auth_token}' "
225                 f"'http://localhost/api{uri}'"
226             )
229         def data_request(uri: str, method: str, data: Dict[str, Any]):
230             encoded_data = json.dumps(data)
231             return json.loads(
232                 machine.succeed(
233                     "curl -sSfL "
234                     f"-X {method} "
235                     "-H 'Accept: application/json' "
236                     "-H 'Content-Type: application/json' "
237                     f"-H 'Authorization: Token {auth_token}' "
238                     f"'http://localhost/api{uri}' "
239                     f"--data '{encoded_data}'"
240                 )
241             )
243         def post(uri: str, data: Dict[str, Any]):
244           return data_request(uri, "POST", data)
246         def patch(uri: str, data: Dict[str, Any]):
247           return data_request(uri, "PATCH", data)
249         with subtest("Can create objects"):
250             result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
251             site_id = result["id"]
253             # Example from:
254             # http://netbox.extra.cea.fr/static/docs/integrations/rest-api/#creating-a-new-object
255             post("/ipam/prefixes/", {"prefix": "192.0.2.0/24", "site": site_id})
257             result = post(
258                 "/dcim/manufacturers/",
259                 {"name": "Test manufacturer", "slug": "test-manufacturer"}
260             )
261             manufacturer_id = result["id"]
263             # Had an issue with device-types before NetBox 3.4.0
264             result = post(
265                 "/dcim/device-types/",
266                 {
267                     "model": "Test device type",
268                     "manufacturer": manufacturer_id,
269                     "slug": "test-device-type",
270                 },
271             )
272             device_type_id = result["id"]
274         with subtest("Can list objects"):
275             result = get("/dcim/sites/")
277             assert result["count"] == 1
278             assert result["results"][0]["id"] == site_id
279             assert result["results"][0]["name"] == "Test site"
280             assert result["results"][0]["description"] == ""
282             result = get("/dcim/device-types/")
283             assert result["count"] == 1
284             assert result["results"][0]["id"] == device_type_id
285             assert result["results"][0]["model"] == "Test device type"
287         with subtest("Can update objects"):
288             new_description = "Test site description"
289             patch(f"/dcim/sites/{site_id}/", {"description": new_description})
290             result = get(f"/dcim/sites/{site_id}/")
291             assert result["description"] == new_description
293         with subtest("Can delete objects"):
294             # Delete a device-type since no object depends on it
295             delete(f"/dcim/device-types/{device_type_id}/")
297             result = get("/dcim/device-types/")
298             assert result["count"] == 0
300         with subtest("Can use the GraphQL API"):
301             encoded_data = json.dumps({
302                 "query": "query { prefix_list { prefix, site { id, description } } }",
303             })
304             result = json.loads(
305                 machine.succeed(
306                     "curl -sSfL "
307                     "-H 'Accept: application/json' "
308                     "-H 'Content-Type: application/json' "
309                     f"-H 'Authorization: Token {auth_token}' "
310                     "'http://localhost/graphql/' "
311                     f"--data '{encoded_data}'"
312                 )
313             )
315             assert len(result["data"]["prefix_list"]) == 1
316             assert result["data"]["prefix_list"][0]["prefix"] == "192.0.2.0/24"
317             assert result["data"]["prefix_list"][0]["site"]["id"] == str(site_id)
318             assert result["data"]["prefix_list"][0]["site"]["description"] == new_description
320         with subtest("Can login with LDAP"):
321             machine.wait_for_unit("openldap.service")
322             login("alice", "${testPassword}")
324         with subtest("Can associate LDAP groups"):
325             result = get("/users/users/?username=${testUser}")
327             assert result["count"] == 1
328             assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
329       '';
330   }