From 9b9ecfa159daa1e218b75405f2f56149127e56ba Mon Sep 17 00:00:00 2001
From: Thomas Schneider <thomas@fsmpi.rwth-aachen.de>
Date: Sun, 22 Sep 2019 01:41:18 +0200
Subject: [PATCH] Add netbox to uwsgi-python role

---
 uwsgi-python/handlers/main.yml                |   7 +
 uwsgi-python/tasks/app.yml                    |   4 +
 uwsgi-python/tasks/apps/netbox.yml            |  67 +++++++
 uwsgi-python/templates/apps/netbox-ldap.py.j2 |  61 ++++++
 .../templates/apps/netbox-rqworker.service.j2 |  15 ++
 uwsgi-python/templates/apps/netbox.j2         | 178 ++++++++++++++++++
 uwsgi-python/templates/uwsgi.ini.j2           |   2 +-
 uwsgi-python/vars/default.yml                 |   1 +
 uwsgi-python/vars/netbox.yml                  |  29 +++
 webserver/tasks/main.yml                      |   3 +
 10 files changed, 366 insertions(+), 1 deletion(-)
 create mode 100644 uwsgi-python/tasks/apps/netbox.yml
 create mode 100644 uwsgi-python/templates/apps/netbox-ldap.py.j2
 create mode 100644 uwsgi-python/templates/apps/netbox-rqworker.service.j2
 create mode 100644 uwsgi-python/templates/apps/netbox.j2
 create mode 100644 uwsgi-python/vars/netbox.yml

diff --git a/uwsgi-python/handlers/main.yml b/uwsgi-python/handlers/main.yml
index 0da2ba0..3625c7b 100644
--- a/uwsgi-python/handlers/main.yml
+++ b/uwsgi-python/handlers/main.yml
@@ -103,3 +103,10 @@
 
 - name: restart uwsgi instance infoscreen
   service: name="uwsgi@infoscreen" state=restarted
+
+- name: restart uwsgi instance netbox
+  service: name="uwsgi@netbox" state=restarted
+
+# - name: restart uwsgi instance netbox rqworker
+#   service: name="netbox-rqworker" state=restarted
+#   listen: "restart uwsgi instance netbox"
diff --git a/uwsgi-python/tasks/app.yml b/uwsgi-python/tasks/app.yml
index 7f26ce6..cc8f2a6 100644
--- a/uwsgi-python/tasks/app.yml
+++ b/uwsgi-python/tasks/app.yml
@@ -56,6 +56,10 @@
     - include: postgres.yml
       when: app_db_type == "postgres"
   when: app_db_type is defined
+  tags:
+    - uwsgi-app
+    - "{{ app.app }}"
+    - "{{ app.instance }}"
 
 - name: ensure we have a group
   group:
diff --git a/uwsgi-python/tasks/apps/netbox.yml b/uwsgi-python/tasks/apps/netbox.yml
new file mode 100644
index 0000000..032e500
--- /dev/null
+++ b/uwsgi-python/tasks/apps/netbox.yml
@@ -0,0 +1,67 @@
+---
+
+- name: Install LDAP auth configuration
+  template:
+    src: apps/netbox-ldap.py.j2
+    dest: "{{app_path}}/netbox/netbox/ldap_config.py"
+    owner: root
+    group: "{{app_group}}"
+    mode: '0640'
+  notify:
+    - "restart uwsgi instance {{ app.instance }}"
+
+# https://github.com/ansible/ansible/issues/42983
+- name: ensure there exists a .ansible folder
+  file:
+    path: "{{app_path}}/.ansible"
+    state: directory
+    owner: "{{app_user}}"
+    group: "{{app_group}}"
+
+- name: ensure plugins are installed  # noqa 403
+  pip:
+    name:
+      - napalm
+      - django-auth-ldap
+    virtualenv: "{{ app_venv }}"
+    virtualenv_python: "python{{ app_python_version }}"
+    state: latest
+  notify:
+    - "restart uwsgi instance {{ app.instance }}"
+
+- name: ensure data model migrations are applied  # noqa 301
+  command: "{{app_venv}}/bin/python manage.py migrate"
+  args:
+    chdir: "{{app_chdir}}"
+  become: true
+  become_user: "{{app_user}}"
+  notify:
+    - "restart uwsgi instance {{app.instance}}"
+
+- name: Collect static files  # noqa 301
+  command: "{{app_venv}}/bin/python manage.py collectstatic --no-input"
+  args:
+    chdir: "{{app_chdir}}"
+
+- name: ensure the folder from above is not present anymore
+  file:
+    path: "{{app_path}}/.ansible"
+    state: absent
+
+- name: ensure the rqworker unit file exists
+  template:
+    src: apps/netbox-rqworker.service.j2
+    dest: "/etc/systemd/system/{{ app_name }}-rqworker.service"
+    owner: root
+    group: root
+    mode: '0644'
+  notify:
+    - reload systemd service files
+    - "restart uwsgi instance {{app.instance}}"
+
+# Not needed (and working) at the moment
+# - name: ensure the rqworker service is enabled
+#   service:
+#     name: "{{app_name}}-rqworker"
+#     enabled: true
+#     state: started
diff --git a/uwsgi-python/templates/apps/netbox-ldap.py.j2 b/uwsgi-python/templates/apps/netbox-ldap.py.j2
new file mode 100644
index 0000000..ec891e3
--- /dev/null
+++ b/uwsgi-python/templates/apps/netbox-ldap.py.j2
@@ -0,0 +1,61 @@
+import ldap
+from django_auth_ldap.config import LDAPSearch, NestedGroupOfNamesType
+
+# Server URI
+AUTH_LDAP_SERVER_URI = "{{netbox_ldap.uri}}"
+
+# The following may be needed if you are binding to Active Directory.
+AUTH_LDAP_CONNECTION_OPTIONS = {
+    ldap.OPT_REFERRALS: 0
+}
+
+# Set the DN and password for the NetBox service account.
+AUTH_LDAP_BIND_DN = "{{netbox_ldap.binddn}}"
+AUTH_LDAP_BIND_PASSWORD = "{{netbox_ldap.bindpw}}"
+
+# Include this setting if you want to ignore certificate errors. This might be needed to accept a self-signed cert.
+# Note that this is a NetBox-specific setting which sets:
+#     ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
+LDAP_IGNORE_CERT_ERRORS = False
+
+# This search matches users with the sAMAccountName equal to the provided username. This is required if the user's
+# username is not in their DN (Active Directory).
+AUTH_LDAP_USER_SEARCH = LDAPSearch("{{netbox_ldap.user_base}}",
+                                    ldap.SCOPE_SUBTREE,
+                                    "(sAMAccountName=%(user)s)")
+
+# If a user's DN is producible from their username, we don't need to search.
+AUTH_LDAP_USER_DN_TEMPLATE = "cn=%(user)s,{{netbox_ldap.user_base}}"
+
+# You can map user attributes to Django attributes as so.
+AUTH_LDAP_USER_ATTR_MAP = {
+    "first_name": "givenName",
+    "last_name": "sn",
+    "email": "mail"
+}
+
+# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
+# hierarchy.
+AUTH_LDAP_GROUP_SEARCH = LDAPSearch("{{netbox_ldap.group_base}}", ldap.SCOPE_SUBTREE,
+                                    "(objectClass=group)")
+AUTH_LDAP_GROUP_TYPE = NestedGroupOfNamesType()
+
+# Define a group required to login.
+AUTH_LDAP_REQUIRE_GROUP = "{{netbox_ldap.login_group}}"
+
+# Mirror LDAP group assignments.
+AUTH_LDAP_MIRROR_GROUPS = True
+
+# Define special user types using groups. Exercise great caution when assigning superuser status.
+AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+    "is_active": "{{netbox_ldap.group_flags.is_active}}",
+    "is_staff": "{{netbox_ldap.group_flags.is_staff}}",
+    "is_superuser": "{{netbox_ldap.group_flags.is_superuser}}"
+}
+
+# For more granular permissions, we can map LDAP groups to Django groups.
+AUTH_LDAP_FIND_GROUP_PERMS = True
+
+# Cache groups for one hour to reduce LDAP traffic
+AUTH_LDAP_CACHE_GROUPS = True
+AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
diff --git a/uwsgi-python/templates/apps/netbox-rqworker.service.j2 b/uwsgi-python/templates/apps/netbox-rqworker.service.j2
new file mode 100644
index 0000000..9cf0067
--- /dev/null
+++ b/uwsgi-python/templates/apps/netbox-rqworker.service.j2
@@ -0,0 +1,15 @@
+[Unit]
+Description={{ app_name }}-rqworker
+After=network.target
+
+[Service]
+User={{ app_user }}
+Group={{ app_group }}
+WorkingDirectory={{ app_chdir }}
+Environment="VIRTUAL_ENV={{ app_venv }}"
+Environment="PATH={{ app_venv }}/bin:/usr/local/bin:/usr/bin:/bin"
+ExecStart={{ app_venv }}/bin/python manage.py rqworker
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
diff --git a/uwsgi-python/templates/apps/netbox.j2 b/uwsgi-python/templates/apps/netbox.j2
new file mode 100644
index 0000000..68b23b5
--- /dev/null
+++ b/uwsgi-python/templates/apps/netbox.j2
@@ -0,0 +1,178 @@
+#########################
+#                       #
+#   Required settings   #
+#                       #
+#########################
+
+# This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write
+# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
+#
+# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
+ALLOWED_HOSTS = ['{{ netbox_host }}']
+
+# PostgreSQL database configuration.
+DATABASE = {
+    'NAME': 'netbox',         # Database name
+    'USER': '',               # PostgreSQL username
+    'PASSWORD': '',           # PostgreSQL password
+    'HOST': '',               # Database server
+    'PORT': '',               # Database port (leave blank for default)
+}
+
+# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
+# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
+# symbols. NetBox will not run without this defined. For more information, see
+# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECRET_KEY
+from .secret_config import secret_key as SECRET_KEY
+
+# Redis database settings. The Redis database is used for caching and background processing such as webhooks
+REDIS = {
+    'HOST': 'localhost',
+    'PORT': 6379,
+    'PASSWORD': '',
+    'DATABASE': 0,
+    'CACHE_DATABASE': 1,
+    'DEFAULT_TIMEOUT': 300,
+    'SSL': False,
+}
+
+
+#########################
+#                       #
+#   Optional settings   #
+#                       #
+#########################
+
+# Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of
+# application errors (assuming correct email settings are provided).
+ADMINS = [
+    {% for a in netbox_admins %}
+    [ "{{ a[0] }}", "{{ a[1] }}" ],
+    {% endfor %}
+]
+
+# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
+# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
+BANNER_TOP = ''
+BANNER_BOTTOM = ''
+
+# Text to include on the login page above the login form. HTML is allowed.
+BANNER_LOGIN = ''
+
+# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
+# BASE_PATH = 'netbox/'
+BASE_PATH = ''
+
+# Cache timeout in seconds. Set to 0 to dissable caching. Defaults to 900 (15 minutes)
+CACHE_TIMEOUT = 900
+
+# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
+CHANGELOG_RETENTION = 90
+
+# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
+# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
+# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
+CORS_ORIGIN_ALLOW_ALL = False
+CORS_ORIGIN_WHITELIST = [
+    # 'https://hostname.example.com',
+]
+CORS_ORIGIN_REGEX_WHITELIST = [
+    # r'^(https?://)?(\w+\.)?example\.com$',
+]
+
+# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
+# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
+# on a production system.
+DEBUG = False
+
+# Email settings
+EMAIL = {
+    'SERVER': '{{ netbox_mail.server }}',
+    'PORT': 25,
+    'USERNAME': '',
+    'PASSWORD': '',
+    'TIMEOUT': 10,  # seconds
+    'FROM_EMAIL': '{{ netbox_mail.from }}',
+}
+
+# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table
+# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
+ENFORCE_GLOBAL_UNIQUE = False
+
+# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
+# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
+EXEMPT_VIEW_PERMISSIONS = [
+    # 'dcim.site',
+    # 'dcim.region',
+    # 'ipam.prefix',
+]
+
+# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
+#   https://docs.djangoproject.com/en/1.11/topics/logging/
+LOGGING = {}
+
+# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
+# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
+LOGIN_REQUIRED = True
+
+# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
+# re-authenticate. (Default: 1209600 [14 days])
+LOGIN_TIMEOUT = None
+
+# Setting this to True will display a "maintenance mode" banner at the top of every page.
+MAINTENANCE_MODE = False
+
+# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
+# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
+# all objects by specifying "?limit=0".
+MAX_PAGE_SIZE = 1000
+
+# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
+# the default value of this setting is derived from the installed location.
+# MEDIA_ROOT = '/opt/netbox/netbox/media'
+
+# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
+METRICS_ENABLED = False
+
+# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
+NAPALM_USERNAME = ''
+NAPALM_PASSWORD = ''
+
+# NAPALM timeout (in seconds). (Default: 30)
+NAPALM_TIMEOUT = 30
+
+# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
+# be provided as a dictionary.
+NAPALM_ARGS = {}
+
+# Determine how many objects to display per page within a list. (Default: 50)
+PAGINATE_COUNT = 50
+
+# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
+# prefer IPv4 instead.
+PREFER_IPV4 = True
+
+# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
+# this setting is derived from the installed location.
+# REPORTS_ROOT = '/opt/netbox/netbox/reports'
+
+# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
+# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
+# database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
+SESSION_FILE_PATH = None
+
+# Time zone (default: UTC)
+TIME_ZONE = '{{ netbox_tz }}'
+
+# The webhooks backend is disabled by default. Set this to True to enable it. Note that this requires a Redis
+# database be configured and accessible by NetBox.
+WEBHOOKS_ENABLED = False
+
+# Date/time formatting. See the following link for supported formats:
+# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
+DATE_FORMAT = 'N j, Y'
+SHORT_DATE_FORMAT = 'Y-m-d'
+TIME_FORMAT = 'g:i a'
+SHORT_TIME_FORMAT = 'H:i:s'
+DATETIME_FORMAT = 'N j, Y g:i a'
+SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
diff --git a/uwsgi-python/templates/uwsgi.ini.j2 b/uwsgi-python/templates/uwsgi.ini.j2
index 3b8d9bc..7ac8a12 100644
--- a/uwsgi-python/templates/uwsgi.ini.j2
+++ b/uwsgi-python/templates/uwsgi.ini.j2
@@ -32,7 +32,7 @@ env = {{ env }}
 mule = 
 {% endfor %}
 #umask = 227
-chdir = {{app_path}}
+chdir = {{app_chdir}}
 uid = {{app_user}}
 gid = {{app_group}}
 log-date =
diff --git a/uwsgi-python/vars/default.yml b/uwsgi-python/vars/default.yml
index 2e5ded0..90db232 100644
--- a/uwsgi-python/vars/default.yml
+++ b/uwsgi-python/vars/default.yml
@@ -5,6 +5,7 @@ app_user: "{{ app.app }}"
 app_group: "{{ app.app }}"
 app_home: "/var/www/{{ app.app }}"
 app_path: "/var/www/{{ app.app }}"
+app_chdir: "{{ app_path }}"
 app_python_version: 3
 app_venv: "/var/www/{{ app.app }}/venv/"
 app_program: "{{ app.app }}.py"
diff --git a/uwsgi-python/vars/netbox.yml b/uwsgi-python/vars/netbox.yml
new file mode 100644
index 0000000..bd6ba44
--- /dev/null
+++ b/uwsgi-python/vars/netbox.yml
@@ -0,0 +1,29 @@
+---
+app_db_name: netbox
+app_db_type: postgres
+app_git_url: 'https://github.com/netbox-community/netbox.git'
+app_git_version: 'v2.6.7'
+app_config_file: netbox/netbox/configuration.py
+app_chdir: "{{ app_path }}/netbox"
+app_module: netbox.wsgi:application
+app_secret_config: true
+app_deploy_key: ""
+
+netbox_admins:
+  - ['Admin', 'admin@example.com']
+netbox_mail:
+  server: localhost
+  from: netbox@example.com
+netbox_host: netbox.example.com
+netbox_tz: UTC
+netbox_ldap:
+  uri: ldaps://dc.example.com
+  binddn: "CN=NETBOXSA, OU=Service Accounts,DC=example,DC=com"
+  bindpw: hunter2
+  user_base: "OU=Users,DC=example,DC=com"
+  group_base: "OU=Groups,DC=example,DC=com"
+  login_group: "CN=Netbox Users,OU=Groups,DC=example,DC=com"
+  group_flags:
+    is_active: "cn=active,ou=groups,dc=example,dc=com"
+    is_staff: "cn=staff,ou=groups,dc=example,dc=com"
+    is_superuser: "cn=superuser,ou=groups,dc=example,dc=com"
diff --git a/webserver/tasks/main.yml b/webserver/tasks/main.yml
index 1cd54ec..187b4ab 100644
--- a/webserver/tasks/main.yml
+++ b/webserver/tasks/main.yml
@@ -4,6 +4,9 @@
 - name: include debian version specific configuration
   include_vars:
     file: "{{debian_version|default('fallback')}}.yml"
+  tags:
+    - nginx
+    - webservices
 
 - name: ensure nginx is installed
   apt:
-- 
GitLab