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