at 23.11-beta 10 kB view raw
1let 2 ldapDomain = "example.org"; 3 ldapSuffix = "dc=example,dc=org"; 4 5 ldapRootUser = "admin"; 6 ldapRootPassword = "foobar"; 7 8 testUser = "alice"; 9 testPassword = "verySecure"; 10 testGroup = "netbox-users"; 11in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: { 12 name = "netbox"; 13 14 meta = with lib.maintainers; { 15 maintainers = [ minijackson n0emis ]; 16 }; 17 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 ''; 26 27 enableLdap = true; 28 ldapConfigPath = pkgs.writeText "ldap_config.py" '' 29 import ldap 30 from django_auth_ldap.config import LDAPSearch, PosixGroupType 31 32 AUTH_LDAP_SERVER_URI = "ldap://localhost/" 33 34 AUTH_LDAP_USER_SEARCH = LDAPSearch( 35 "ou=accounts,ou=posix,${ldapSuffix}", 36 ldap.SCOPE_SUBTREE, 37 "(uid=%(user)s)", 38 ) 39 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() 46 47 # Mirror LDAP group assignments. 48 AUTH_LDAP_MIRROR_GROUPS = True 49 50 # For more granular permissions, we can map LDAP groups to Django groups. 51 AUTH_LDAP_FIND_GROUP_PERMS = True 52 ''; 53 }; 54 55 services.nginx = { 56 enable = true; 57 58 recommendedProxySettings = true; 59 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 }; 66 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} 97 98 dn: ou=posix,${ldapSuffix} 99 objectClass: top 100 objectClass: organizationalUnit 101 102 dn: ou=accounts,ou=posix,${ldapSuffix} 103 objectClass: top 104 objectClass: organizationalUnit 105 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: "" 115 116 dn: ou=groups,ou=posix,${ldapSuffix} 117 objectClass: top 118 objectClass: organizationalUnit 119 120 dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix} 121 objectClass: posixGroup 122 gidNumber: 2345 123 memberUid: ${testUser} 124 ''; 125 }; 126 }; 127 128 users.users.nginx.extraGroups = [ "netbox" ]; 129 130 networking.firewall.allowedTCPPorts = [ 80 ]; 131 }; 132 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 143 144 start_all() 145 machine.wait_for_unit("netbox.target") 146 machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening") 147 148 with subtest("Home screen loads"): 149 machine.succeed( 150 "curl -sSfL http://[::1]:8001 | grep '<title>Home | NetBox</title>'" 151 ) 152 153 with subtest("Staticfiles are generated"): 154 machine.succeed("test -e /var/lib/netbox/static/netbox.js") 155 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") 162 163 machine.wait_for_unit("network.target") 164 165 with subtest("Home screen loads from nginx"): 166 machine.succeed( 167 "curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'" 168 ) 169 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/") 173 174 with subtest("Can interact with API"): 175 json.loads( 176 machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'") 177 ) 178 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"] 193 194 with subtest("Can login"): 195 auth_token = login("netbox", "netbox") 196 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 ) 206 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 ) 215 216 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 ) 230 231 def post(uri: str, data: Dict[str, Any]): 232 return data_request(uri, "POST", data) 233 234 def patch(uri: str, data: Dict[str, Any]): 235 return data_request(uri, "PATCH", data) 236 237 with subtest("Can create objects"): 238 result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"}) 239 site_id = result["id"] 240 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}) 244 245 result = post( 246 "/dcim/manufacturers/", 247 {"name": "Test manufacturer", "slug": "test-manufacturer"} 248 ) 249 manufacturer_id = result["id"] 250 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"] 261 262 with subtest("Can list objects"): 263 result = get("/dcim/sites/") 264 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"] == "" 269 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" 274 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 280 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}/") 284 285 result = get("/dcim/device-types/") 286 assert result["count"] == 0 287 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 ) 302 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 307 308 with subtest("Can login with LDAP"): 309 machine.wait_for_unit("openldap.service") 310 login("alice", "${testPassword}") 311 312 with subtest("Can associate LDAP groups"): 313 result = get("/users/users/?username=${testUser}") 314 315 assert result["count"] == 1 316 assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"]) 317 ''; 318})