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