Compare commits

...

58 Commits

Author SHA1 Message Date
Jeremy Stretch
300aff71bb Merge pull request #286 from digitalocean/develop
Release v1.2.1
2016-07-13 12:08:48 -04:00
Jeremy Stretch
d9bf199e75 Version bump: v1.2.1 2016-07-13 12:01:34 -04:00
Jeremy Stretch
6f1ed9fc16 Clarified the process of voting on feature requests 2016-07-13 11:47:20 -04:00
Jeremy Stretch
96b496ffa8 Updated documentation to include banner settings 2016-07-13 11:24:34 -04:00
Jeremy Stretch
f1b6f0cfee Fixes #285: Added PREFER_IPV4 configuration setting 2016-07-13 11:16:09 -04:00
Jeremy Stretch
e19ce043d6 Fixes #275: Exclude self when checking for overlapping aggregates 2016-07-13 10:37:25 -04:00
Jeremy Stretch
03542b400d Renamed IPAMConfig to DCIMConfig 2016-07-13 09:25:13 -04:00
Jeremy Stretch
73d24532c9 Merge pull request #281 from lukerussell/Link-to-docs-in-readme
Added a link to docs in readme.md
2016-07-13 09:04:00 -04:00
Jeremy Stretch
b60f964835 Fixes #272: Added a step to copy the gunicorn config 2016-07-12 16:57:00 -04:00
lukerussell
8e7e02a622 Added a link to docs in readme.md
Adding a direct link for easy access. I couldn't find a link anywhere except digging in through the docs/ directory.
2016-07-13 06:04:29 +10:00
Jeremy Stretch
2c23ca33a2 Fixes #274: Correct reference to old field 2016-07-12 15:48:56 -04:00
Jeremy Stretch
e1da3b8f10 Related to #243: Implemented natsort on all Device and DeviceType objects (except interfaces) 2016-07-12 14:53:59 -04:00
Jeremy Stretch
d80ffd2308 Merge pull request #248 from Zanthras/develop
possible fix for #243 generic sorting for device bays
2016-07-12 14:13:25 -04:00
Jeremy Stretch
18846cf40a Fixes #271: Corrected select_related() in secrets API 2016-07-12 12:27:26 -04:00
Jeremy Stretch
e81a2094df Post-release version bump 2016-07-12 11:40:40 -04:00
Jeremy Stretch
0c3970233e Merge pull request #269 from digitalocean/develop
Release v1.2.0
2016-07-12 11:37:56 -04:00
Jeremy Stretch
17011843d7 Version bump: v1.2.0 2016-07-12 11:28:28 -04:00
Jeremy Stretch
4c45e38aea Fixes #162: Return Unicode from display_name() 2016-07-12 10:46:32 -04:00
Jeremy Stretch
e1a6188580 Merge pull request #261 from digitalocean/primary-ip4-ip6
Initial work on #93: Primary IPv4/IPv6 support
2016-07-12 10:38:00 -04:00
Jeremy Stretch
4e4bb01a55 Initial work on #93: Primary IPv4/IPv6 support 2016-07-11 16:24:46 -04:00
Jeremy Stretch
e92f60afda Fixes #260: Corrected typo in get_queryset() 2016-07-11 16:21:05 -04:00
Jeremy Stretch
a365cfcf9b Corrected removal of div 2016-07-11 15:04:39 -04:00
Jeremy Stretch
f617828712 A little cleanup from #222 2016-07-11 13:29:58 -04:00
Jeremy Stretch
d85561c6fd Merge pull request #222 from Gelob/motd
Banner support
2016-07-11 13:12:49 -04:00
Jeremy Stretch
4cba418d89 Merge pull request #256 from srossen/patch-1
Update netbox.md
2016-07-11 11:43:27 -04:00
Jeremy Stretch
6112e5542d Merge pull request #251 from bellwood/mobile-fix-navbar-stacking
fixes #217 - navbar not collapsing soon enough
2016-07-11 11:42:05 -04:00
Jeremy Stretch
1b8786ab98 Merge pull request #255 from digitalocean/docker-install-docs
Fixes #246 - Add build directive to docker-compose.yml
2016-07-11 10:00:46 -04:00
Steve Rossen
876361c0b2 Update netbox.md
Added libpq-dev as a dependency so the pip install of requirements.txt will complete.
2016-07-11 08:55:21 -05:00
Zach Moody
db25894363 Fixes #246 - Add build directive to docker-compose.yml 2016-07-10 19:00:40 -05:00
bellwood
f1881fad71 Update base.css 2016-07-09 14:09:35 -04:00
Jeremy Stretch
40173b4f8e Added "What NetBox isn't" section do intro doc 2016-07-09 11:51:08 -04:00
Joel
173a4cde8b Update the requirements file to include the natsort library requirement. 2016-07-08 23:27:00 -07:00
Joel
d9867423de Use the natsort library to provide a generic sorting option for better sorting of generic names for device bays. 2016-07-08 22:47:08 -07:00
Jeremy Stretch
bf35b4121f Merge pull request #240 from linuxsimba/bug_215
Potential quick fix for Rack view broken when viewport less than 992px wide
2016-07-08 23:00:21 -04:00
Jeremy Stretch
85461f9be0 Merge pull request #245 from bellwood/mobile-fix-for-footer
fixes #219 - footer display issue on mobile
2016-07-08 22:59:44 -04:00
Jeremy Stretch
fafd2ab517 Merge pull request #244 from bellwood/mobile-fix-login-form-width
fixes #218 - login form width on mobile
2016-07-08 22:59:00 -04:00
Jeremy Stretch
498eb50f56 Fixes PEP8 E401 CI error 2016-07-08 22:56:54 -04:00
bellwood
1cdf70da0b Update _base.html 2016-07-08 20:45:27 -04:00
bellwood
cbd6370889 Update login.html 2016-07-08 20:43:58 -04:00
Jeremy Stretch
8dd6112a4b Corrected detection of private_key 2016-07-08 17:49:20 -04:00
Jeremy Stretch
4c354277ec Enabled LDAP authentication 2016-07-08 17:09:35 -04:00
Jeremy Stretch
b8c5366c3e Split configuration doc into two sections 2016-07-08 16:25:34 -04:00
Jeremy Stretch
129415e15f Remove obsolete docs.html template 2016-07-08 16:09:05 -04:00
Jeremy Stretch
68515b9d46 Merge pull request #203 from dinoocch/ldap-docs
Add LDAP Authentication Documentation
2016-07-08 16:08:07 -04:00
Jeremy Stretch
c948682370 Removed TOC elements 2016-07-08 16:05:14 -04:00
Jeremy Stretch
5758ce2be4 Reorganized installation docs 2016-07-08 16:00:53 -04:00
Jeremy Stretch
27c8cb046c Structured docs layout 2016-07-08 12:55:57 -04:00
Jeremy Stretch
b6e87d1526 Merge branch 'new-docs' into develop 2016-07-08 12:08:32 -04:00
Jeremy Stretch
90dadfc5d9 Moving docs to readthedocs.org 2016-07-08 12:07:04 -04:00
Jeremy Stretch
edc3ab597f Merge pull request #232 from digitalocean/api-integration
Add initial API Integration document
2016-07-08 12:05:28 -04:00
stanley karunditu
4372043ddb Potential quick fix for Issue #215. The proper solution is to
redo the grid layout for the page so that its fully responsive.
It is only partial responsive. Did tests using Firefox developer tools.
2016-07-08 07:06:15 -07:00
Jeremy Stretch
db72a64ef7 Changed DeviceForm.device_type label 2016-07-07 22:30:51 -04:00
Matt Layher
eb9315c11c Add initial API Integration document 2016-07-07 16:18:34 -04:00
Jeremy Stretch
b9e0739f72 Fixes #228: Correct conditional inclusion of device bays 2016-07-07 13:39:54 -04:00
Matt Layher
29358a18b8 Merge remote-tracking branch 'origin/master' into develop 2016-07-07 13:01:24 -04:00
Jeremy Stretch
9c48340b9a Dev version bump 2016-07-07 12:54:25 -04:00
Gelob
1338bf6012 Banner/MOTD Support 2016-07-06 23:37:04 -05:00
dinoocch
9750da4761 Add LDAP Authentication Documentation
Addresses #65

This commit adds documentation for installing and configuring ldap
authentication for netbox.

It may be beneficial to add settings to the configuration.py instead of
editing settings.py if this is an important feature.
2016-07-05 22:10:14 -05:00
54 changed files with 1070 additions and 792 deletions

View File

@@ -27,9 +27,10 @@ IRC.
## Feature Requests
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you'd like to see
has already been requested (and possibly rejected). If it is, be sure to comment with a "+1" and any additional
justification you have for the feature.
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're requesting
has already been requested (and possibly rejected). If it has, click "add a reaction" in the top right corner of the
issue and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel
free to add a comment with any additional justification for the feature.
* While discussion of new features is welcome, it's important to limit the scope of NetBox's feature set to avoid
feature creep. For example, the following features would be firmly out of scope for NetBox:

View File

@@ -4,6 +4,8 @@ NetBox is an IP address management (IPAM) and data center infrastructure managem
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/) Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/digitalocean/netbox).
The complete documentation for Netbox can be found at [Read the Docs](http://netbox.readthedocs.io/en/latest/).
Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net**!
### Build Status
@@ -15,62 +17,14 @@ Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net*
## Screenshots
![Screenshot of main page](docs/screenshot1.png "Main page")
![Screenshot of main page](docs/media/screenshot1.png "Main page")
![Screenshot of rack elevation](docs/screenshot2.png "Rack elevation")
![Screenshot of rack elevation](docs/media/screenshot2.png "Rack elevation")
![Screenshot of prefix hierarchy](docs/screenshot3.png "Prefix hierarchy")
![Screenshot of prefix hierarchy](docs/media/screenshot3.png "Prefix hierarchy")
# Installation
Please see docs/getting-started.md for instructions on installing NetBox.
To upgrade NetBox, please download the [latest release](https://github.com/digitalocean/netbox/releases) and run `upgrade.sh`.
# Components
NetBox understands all of the physical and logical building blocks that comprise network infrastructure, and the manners in which they are all related.
## DCIM
DCIM comprises all the physical installations and connections which comprise a network. NetBox tracks where devices are installed, as well as their individual power, console, and network connections.
**Site:** A physical location (typically a building) where network devices are installed. Devices in different sites cannot be directly connected to one another.
**Rack:** An equipment rack into which devices are installed. Each rack belongs to a site.
**Device:** Any type of rack-mounted device. For example, routers, switches, servers, console servers, PDUs, etc. 0U (non-rack-mounted) devices are supported.
## IPAM
IPAM deals with the IP addressing and VLANs in use on a network. NetBox makes a distinction between IP prefixes (networks) and individual IP addresses.
Because NetBox is a combined DCIM/IPAM system, IP addresses can be assigned to device interfaces in the application just as they are in the real world.
**Aggregate:** A top-level aggregate of IP address space; for example, 10.0.0.0/8 or 2001:db8::/32. Each aggregate belongs to a regional Internet registry (RIR) like ARIN or RIPE, or to an authoritative standard such as RFC 1918.
**VRF:** A virtual routing table. VRF support is currently still under development.
**Prefix:** An IPv4 or IPv6 network. A prefix can be assigned to a VRF; if not, it is considered to belong to the global table. Prefixes are grouped by aggregates automatically and can optionally be assigned to sites.
**IP Address:** An individual IPv4 or IPv6 address (with CIDR mask). IP address can be assigned to device interfaces.
**VLAN:** VLANs are assigned to sites, and can optionally have one or more IP prefixes assigned to them. VLAN IDs are unique only within the scope of a site.
## Circuits
Long-distance data connections are typically referred to as _circuits_. NetBox provides a method for managing circuits and their providers. Individual circuits can be terminated to device interfaces.
**Provider:** An entity to which a network connects to. This can be a transit provider, peer, or some other organization.
**Circuit:** A data circuit which connects to a provider. The local end of a circuit can be assigned to a device interface.
## Secrets
NetBox provides encrypted storage of sensitive data it calls _secrets_. Each user may be issued an encryption key with which stored secrets can be retrieved.
Note that NetBox does not merely hash secrets, a function which is only useful for validation. It employs fully reversible AES-256 encryption so that secret data can be retrieved and consumed by other services.
**Secrets** Any piece of confidential data which must be retrievable. For example: passwords, SNMP communities, RADIUS shared secrets, etc.
**User Key:** An individual user's encrypted copy of the master key, which can be used to retrieve secret data.

View File

@@ -9,6 +9,7 @@ services:
POSTGRES_PASSWORD: J5brHrAXFLQSif0K
POSTGRES_DB: netbox
netbox:
build: .
image: digitalocean/netbox
links:
- postgres

19
docs/api-integration.md Normal file
View File

@@ -0,0 +1,19 @@
# API Integration
NetBox features a read-only REST API which can be used to integrate it with
other applications.
In the future, both read and write actions will be available via the API.
## Clients
The easiest way to start integrating your applications with NetBox is to make
use of an API client. If you build or discover an API client that is not part
of this list, please send a pull request!
- **Go**: [github.com/digitalocean/go-netbox](https://github.com/digitalocean/go-netbox)
## Documentation
If you wish to build a new API client or simply explore the NetBox API,
Swagger documentation can be found at the URL `/api/docs/` on a NetBox server.

View File

@@ -0,0 +1,45 @@
NetBox's local configuration is held in `netbox/netbox/configuration.py`. An example configuration is provided at `netbox/netbox/configuration.example.py`. You may copy or rename the example configuration and make changes as appropriate. NetBox will not run without a configuration file.
## ALLOWED_HOSTS
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', '192.0.2.123']
```
---
## DATABASE
NetBox requires access to a PostgreSQL database service to store data. This service can run locally or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
* NAME - Database name
* USER - PostgreSQL username
* PASSWORD - PostgreSQL password
* HOST - Name or IP address of the database server (use `localhost` if running locally)
* PORT - TCP port of the PostgreSQL service; leave blank for default port (5432)
Example:
```
DATABASE = {
'NAME': 'netbox', # Database name
'USER': 'netbox', # PostgreSQL username
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
'HOST': 'localhost', # Database server
'PORT': '', # Database port (leave blank for default)
}
```
---
## SECRET_KEY
This is a secret cryptographic key is used to improve the security of cookies and password resets. The key defined here should not be shared outside of the configuration file. `SECRET_KEY` can be changed at any time, however be aware that doing so will invalidate all existing sessions.
Please note that this key is **not** used for hashing user passwords or for the encrypted storage of secret data in NetBox.
`SECRET_KEY` should be at least 50 characters in length and contain a random mix of letters, digits, and symbols. The script located at `netbox/generate_secret_key.py` may be used to generate a suitable key.

View File

@@ -1,62 +1,6 @@
<h1>Configuration</h1>
The following are optional settings which may be declared in `netbox/netbox/configuration.py`.
NetBox's local configuration is held in `netbox/netbox/configuration.py`. An example configuration is provided at `netbox/netbox/configuration.example.py`. You may copy or rename the example configuration and make changes as appropriate. NetBox will not run without a configuration file.
[TOC]
# Mandatory Settings
---
#### ALLOWED_HOSTS
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', '192.0.2.123']
```
---
#### DATABASE
NetBox requires access to a PostgreSQL database service to store data. This service can run locally or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
* NAME - Database name
* USER - PostgreSQL username
* PASSWORD - PostgreSQL password
* HOST - Name or IP address of the database server (use `localhost` if running locally)
* PORT - TCP port of the PostgreSQL service; leave blank for default port (5432)
Example:
```
DATABASE = {
'NAME': 'netbox', # Database name
'USER': 'netbox', # PostgreSQL username
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
'HOST': 'localhost', # Database server
'PORT': '', # Database port (leave blank for default)
}
```
---
#### SECRET_KEY
This is a secret cryptographic key is used to improve the security of cookies and password resets. The key defined here should not be shared outside of the configuration file. `SECRET_KEY` can be changed at any time, however be aware that doing so will invalidate all existing sessions.
Please note that this key is **not** used for hashing user passwords or for the encrypted storage of secret data in NetBox.
`SECRET_KEY` should be at least 50 characters in length and contain a random mix of letters, digits, and symbols. The script located at `netbox/generate_secret_key.py` may be used to generate a suitable key.
# Optional Settings
---
#### ADMINS
## ADMINS
NetBox will email details about critical errors to the administrators listed here. This should be a list of (name, email) tuples. For example:
@@ -69,15 +13,28 @@ ADMINS = [
---
#### DEBUG
## BANNER_TOP
Default: False
## BANNER_BOTTOM
This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
Setting these variables will display content in a banner at the top and/or bottom of the page, respectively. To replicate the content of the top banner in the bottom banner, set:
```
BANNER_TOP = 'Your banner text'
BANNER_BOTTOM = BANNER_TOP
```
---
#### EMAIL
## DEBUG
Default: False
This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
---
## EMAIL
In order to send email, NetBox needs an email server configured. The following items can be defined within the `EMAIL` setting:
@@ -90,7 +47,7 @@ In order to send email, NetBox needs an email server configured. The following i
---
#### LOGIN_REQUIRED
## LOGIN_REQUIRED
Default: False,
@@ -98,7 +55,7 @@ Setting this to True will permit only authenticated users to access any part of
---
#### MAINTENANCE_MODE
## MAINTENANCE_MODE
Default: False
@@ -106,15 +63,15 @@ Setting this to True will display a "maintenance mode" banner at the top of ever
---
#### NETBOX_USERNAME
## NETBOX_USERNAME
#### NETBOX_PASSWORD
## NETBOX_PASSWORD
If provided, NetBox will use these credentials to authenticate against devices when collecting data.
---
#### PAGINATE_COUNT
## PAGINATE_COUNT
Default: 50
@@ -122,7 +79,15 @@ Determine how many objects to display per page within each list of objects.
---
#### TIME_ZONE
## PREFER_IPV4
Default: False
When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to prefer IPv4 instead.
---
## TIME_ZONE
Default: UTC
@@ -130,7 +95,7 @@ The time zone NetBox will use when dealing with dates and times. It is recommend
---
#### Date and Time Formatting
## Date and Time Formatting
You may define custom formatting for date and times. For detailed instructions on writing format strings, please see [the Django documentation](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date).

View File

@@ -1,9 +1,5 @@
<h1>Circuits</h1>
The circuits component of NetBox deals with the management of long-haul Internet and private transit links and providers.
[TOC]
# Providers
A provider is any entity which provides some form of connectivity. This obviously includes carriers which offer Internet and private transit service. However, it might also include Internet exchange (IX) points and even organizations with whom you peer directly.

View File

@@ -1,9 +1,5 @@
<h1>DCIM</h1>
Data center infrastructure management (DCIM) entails all physical assets: sites, racks, devices, cabling, etc.
[TOC]
# Sites
How you define sites will depend on the nature of your organization, but typically a site will equate a building or campus. For example, a chain of banks might create a site to represent each of its branches, a site for its corporate headquarters, and two additional sites for its presence in two colocation facilities.

View File

@@ -1,9 +1,5 @@
<h1>Extras</h1>
This section entails features of NetBox which are not crucial to its primary functions, but that provide additional value.
[TOC]
# Export Templates
NetBox allows users to define custom templates that can be used when exporting objects. To create an export template, navigate to Extras > Export Templates under the admin interface.

View File

@@ -1,9 +1,5 @@
<h1>IPAM</h1>
IP address management (IPAM) entails the allocation of IP networks, addresses, and related numeric resources.
[TOC]
# VRFs
A VRF object in NetBox represents a virtual routing and forwarding (VRF) domain within a network. Each VRF is essentially a separate routing table: the same IP prefix or address can exist in multiple VRFs. VRFs are commonly used to isolate customers or organizations from one another within a network.

View File

@@ -1,9 +1,5 @@
<h1>Secrets</h1>
"Secrets" are small amounts of data that must be kept confidential; for example, passwords and SNMP community strings. NetBox provides encrypted storage of secret data.
[TOC]
# Secrets
A secret represents a single credential or other string which must be stored securely. Each secret is assigned to a device within NetBox. The plaintext value of a secret is encrypted to a ciphertext immediately prior to storage within the database using a 256-bit AES master key. A SHA256 hash of the plaintext is also stored along with each ciphertext to validate the decrypted plaintext.

View File

@@ -1,502 +0,0 @@
<h1>Getting Started</h1>
This guide documents the process of installing NetBox on an Ubuntu 14.04 server with [nginx](https://www.nginx.com/) and [gunicorn](http://gunicorn.org/).
[TOC]
# PostgreSQL
## Installation
The following packages are needed to install PostgreSQL:
* postgresql
* libpq-dev
* python-psycopg2
```
# sudo apt-get install -y postgresql libpq-dev python-psycopg2
```
## Configuration
At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. This is done with the following commands.
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
```
# sudo -u postgres psql
psql (9.3.13)
Type "help" for help.
postgres=# CREATE DATABASE netbox;
CREATE DATABASE
postgres=# CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox;
GRANT
postgres=# \q
```
You can verify that authentication works using the following command:
```
# psql -U netbox -h localhost -W
```
---
# NetBox
## Installation
NetBox requires following dependencies:
* python2.7
* python-dev
* python-pip
* libxml2-dev
* libxslt1-dev
* libffi-dev
* graphviz
```
# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz
```
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
### Option A: Download a Release
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`.
```
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
# ln -s netbox-1.0.4/ netbox
# cd /opt/netbox/
```
### Option B: Clone the Git Repository
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
```
# mkdir -p /opt/netbox/
# cd /opt/netbox/
```
If `git` is not already installed, install it:
```
# sudo apt-get install -y git
```
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
```
# git clone -b master https://github.com/digitalocean/netbox.git .
Cloning into '.'...
remote: Counting objects: 1994, done.
remote: Compressing objects: 100% (150/150), done.
remote: Total 1994 (delta 80), reused 0 (delta 0), pack-reused 1842
Receiving objects: 100% (1994/1994), 472.36 KiB | 0 bytes/s, done.
Resolving deltas: 100% (1495/1495), done.
Checking connectivity... done.
```
### Install Python Packages
Install the necessary Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the required dependencies.)
```
# sudo pip install -r requirements.txt
```
## Configuration
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
```
# cd netbox/netbox/
# cp configuration.example.py configuration.py
```
Open `configuration.py` with your preferred editor and set the following variables:
* ALLOWED_HOSTS
* DATABASE
* SECRET_KEY
### ALLOWED_HOSTS
This is a list of the valid hostnames by which this server can be reached. You must specify at least one name or IP address.
Example:
```
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
```
### DATABASE
This parameter holds the database configuration details. You must define the username and password used when you configured PostgreSQL. If the service is running on a remote host, replace `localhost` with its address.
Example:
```
DATABASE = {
'NAME': 'netbox', # Database name
'USER': 'netbox', # PostgreSQL username
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
'HOST': 'localhost', # Database server
'PORT': '', # Database port (leave blank for default)
}
```
### SECRET_KEY
Generate a random secret key of at least 50 alphanumeric characters. This key must be unique to this installation and must not be shared outside the local system.
You may use the script located at `netbox/generate_secret_key.py` to generate a suitable key.
## Run Migrations
Before NetBox can run, we need to install the database schema. This is done by running `./manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):
```
# cd /opt/netbox/netbox/
# ./manage.py migrate
Operations to perform:
Apply all migrations: dcim, sessions, admin, ipam, utilities, auth, circuits, contenttypes, extras, secrets, users
Running migrations:
Rendering model states... DONE
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
...
```
If this step results in a PostgreSQL authentication error, ensure that the username and password created in the database match what has been specified in `configuration.py`
## Create a Super User
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
```
# ./manage.py createsuperuser
Username: admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.
```
## Collect Static Files
```
# ./manage.py collectstatic
You have requested to collect static files at the destination
location as specified in your settings:
/opt/netbox/netbox/static
This will overwrite existing files!
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: yes
```
## Test the Application
At this point, NetBox should be able to run. We can verify this by starting a development instance:
```
# ./manage.py runserver 0.0.0.0:8000 --insecure
Performing system checks...
System check identified no issues (0 silenced).
June 17, 2016 - 16:17:36
Django version 1.9.7, using settings 'netbox.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
```
Now if we navigate to the name or IP of the server (as defined in `ALLOWED_HOSTS`) we should be greeted with the NetBox home page. Note that this built-in web service is for development and testing purposes only. It is not suited for production use.
If the test service does not run, or you cannot reach the NetBox home page, something has gone wrong. Do not proceed with the rest of this guide until the installation has been corrected.
# Web Server and gunicorn
## Installation
We'll set up a simple HTTP front end using [gunicorn](http://gunicorn.org/) for the purposes of this guide. For web servers, we provide example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/2.4). (You are of course free to use whichever combination of HTTP and WSGI services you'd like.) We'll also use [supervisord](http://supervisord.org/) for service persistence.
```
# sudo apt-get install -y gunicorn supervisor
```
## nginx Configuration
The following will serve as a minimal nginx configuration. Be sure to modify your server name and installation path appropriately.
```
# sudo apt-get install -y nginx
```
Once nginx is installed, proceed with the following configuration:
```
server {
listen 80;
server_name netbox.example.com;
access_log off;
location /static/ {
alias /opt/netbox/netbox/static/;
}
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
}
```
Save this configuration to `/etc/nginx/sites-available/netbox`. Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sites-enabled` directory to the configuration file you just created.
```
# cd /etc/nginx/sites-enabled/
# rm default
# ln -s /etc/nginx/sites-available/netbox
```
Restart the nginx service to use the new configuration.
```
# service nginx restart
* Restarting nginx nginx
```
## Apache Configuration
```
# sudo apt-get install -y apache2
```
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
```
<VirtualHost *:80>
ProxyPreserveHost On
ServerName netbox.example.com
Alias /static /opt/netbox/netbox/static
<Directory /opt/netbox/netbox/static>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Require all granted
</Directory>
<Location /static>
ProxyPass !
</Location>
ProxyPass / http://127.0.0.1:8001/
ProxyPassReverse / http://127.0.0.1:8001/
</VirtualHost>
```
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
```
# a2enmod proxy
# a2enmod proxy_http
# a2ensite netbox
# service apache2 restart
```
## gunicorn Configuration
Save the following configuration file in the root netbox installation path (in this example, `/opt/netbox/`) as `gunicorn_config.py`. Be sure to verify the location of the gunicorn executable (e.g. `which gunicorn`) and to update the `pythonpath` variable if needed.
```
command = '/usr/bin/gunicorn'
pythonpath = '/opt/netbox/netbox'
bind = '127.0.0.1:8001'
workers = 3
user = 'www-data'
```
## supervisord Configuration
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
```
[program:netbox]
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
directory = /opt/netbox/netbox/
user = www-data
```
Finally, restart the supervisor service to detect and run the gunicorn service:
```
# service supervisor restart
```
At this point, you should be able to connect to the nginx HTTP service at the server name or IP address you provided. If you are unable to connect, check that the nginx service is running and properly configured. If you receive a 502 (bad gateway) error, this indicates that gunicorn is misconfigured or not running.
Please keep in mind that the configurations provided here are bare minimums required to get NetBox up and running. You will almost certainly want to make some changes to better suit your production environment.
## Let's Encrypt SSL + nginx
To add SSL support to the installation we'll start by installing the arbitrary precision calculator language.
```
# sudo apt-get install -y bc
```
Next we'll clone Let's Encrypt into /opt/:
```
# sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
```
To ensure Let's Encrypt can publicly access the directory it needs for certificate validation you'll need to edit `/etc/nginx/sites-available/netbox` and add:
```
location /.well-known/ {
alias /opt/netbox/netbox/.well-known/;
allow all;
}
```
Then restart nginix:
```
# sudo services nginx restart
```
To create the certificate use the following commands ensuring to change `netbox.example.com` to the domain name of the server:
```
# cd /opt/letsencrypt
# ./letsencrypt-auto certonly -a webroot --webroot-path=/opt/netbox/netbox/ -d netbox.example.com
```
If you wish to add support for the `www` prefix you'd use:
```
# cd /opt/letsencrypt
# ./letsencrypt-auto certonly -a webroot --webroot-path=/opt/netbox/netbox/ -d netbox.example.com -d www.netbox.example.com
```
Make sure you have DNS records setup for the hostnames you use and that they resolve back the netbox server.
You will be prompted for your email address to receive notifications about your SSL and then asked to accept the subscriber agreement.
If successful you'll now have four files in `/etc/letsencrypt/live/netbox.example.com` (remember, your hostname is different)
```
cert.pem
chain.pem
fullchain.pem
privkey.pem
```
Now edit your nginx configuration `/etc/nginx/sites-available/netbox` and at the top edit to the following:
```
#listen 80;
#listen [::]80;
listen 443;
listen [::]443;
ssl on;
ssl_certificate /etc/letsencrypt/live/netbox.example.com/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/netbox.example.com/privkey.pem;
```
If you are not using IPv6 then you do not need `listen [::]443;` The two commented lines are for non-SSL for both IPv4 and IPv6.
Lastly, restart nginx:
```
# sudo services nginx restart
```
You should now have netbox running on a SSL protected connection.
# Upgrading
## Installation of Upgrade
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
### Option A: Download a Release
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`. For this guide we are using 1.0.4 as the old version and 1.0.7 as the new version.
Download & extract latest version:
```
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
# ln -sf netbox-1.0.7/ netbox
```
Copy the 'configuration.py' you created when first installing to the new version:
```
# cp /opt/netbox-1.0.4/configuration.py /opt/netbox/configuration.py
```
### Option B: Clone the Git Repository (latest master release)
For this guide, we'll use `/opt/netbox`.
Check that your git branch is up to date & is set to master:
```
# cd /opt/netbox
# git status
```
If not on branch master, set it and verify status:
```
# git checkout master
# git status
```
Pull down the set branch from git status above:
```
# git pull
```
## Upgrade Script & Netbox Restart
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).
```
# ./upgrade.sh
```
This script:
* Installs or upgrades any new required Python packages
* Applies any database migrations that were included in the release
* Collects all static files to be served by the HTTP service
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
```
# sudo supervisorctl restart netbox
```

View File

@@ -1,3 +1,53 @@
# NetBox Documentation
# What is NetBox?
NetBox is an IP address management (IPAM) and data center infrastructure management (DCIM) application.
NetBox is an open source web application designed to help manage and document computer networks. Initially conceived by the network engineering team at [DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically to address the needs of network and infrastructure engineers. It encompasses the following aspects of network management:
* **IP address management (IPAM)** - IP networks and addresses, VRFs, and VLANs
* **Equipment racks** - Organized by group and site
* **Devices** - Types of devices and where they are installed
* **Connections** - Network, console, and power connections among devices
* **Data circuits** - Long-haul communications circuits and providers
* **Secrets** - Encrypted storage of sensitive credentials
# What NetBox Isn't
While NetBox strives to cover many areas of network management, the scope of its feature set is necessarily limited. This ensures that development focuses on core functionality and that scope creep is reasonably contained. To that end, it might help to provide some examples of functionality that NetBox **does not** provide:
* Network monitoring
* DNS server
* RADIUS server
* Configuration management
* Facilities management
That said, NetBox _can_ be used to great effect in populating external tools with the data they need to perform these functions.
# Design Philosophy
NetBox was designed with the following tenets foremost in mind.
## Replicate the Real World
Careful consideration has been given to the data model to ensure that it can accurately reflect a real-world network. For instance, IP addresses are assigned not to devices, but to specific interfaces attached to a device, and an interface may have multiple IP addresses assigned to it.
## Serve as a "Source of Truth"
NetBox intends to represent the _desired_ state of a network versus its _operational_ state. As such, automated import of live network state is strongly discouraged. All data created in NetBox should first be vetted by a human to ensure its integrity. NetBox can then be used to populate monitoring and provisioning systems with a high degree of confidence.
## Keep it Simple
When given a choice between a relatively simple [80% solution](https://en.wikipedia.org/wiki/Pareto_principle) and a much more complex complete solution, the former will typically be favored. This ensures a lean codebase with a low learning curve.
# Application Stack
NetBox is built on the [Django](https://djangoproject.com/) Python framework and utilizes a [PostgreSQL](https://www.postgresql.org/) database. It runs as a WSGI service behind your choice of HTTP server.
| Function | Component |
|--------------|-------------------|
| HTTP Service | nginx or Apache |
| WSGI Service | gunicorn or uWSGI |
| Application | Django/Python |
| Database | PostgreSQL |
# Getting Started
See the [getting started](getting-started.md) guide for help with getting NetBox up and running quickly.

View File

@@ -1,13 +1,11 @@
<h1>Getting Started with NetBox and Docker</h1>
This guide assumes that the latest versions of [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) are already installed in your host.
This guide demonstrates how to build and run NetBox as a Docker container. It assumes that the latest versions of [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) are already installed in your host.
# Quickstart
To get NetBox up and running:
```
git clone https://github.com/digitalocean/netbox.git
git clone -b master https://github.com/digitalocean/netbox.git
cd netbox
docker-compose up -d
```
@@ -15,13 +13,13 @@ docker-compose up -d
The application will be available on http://localhost/ after a few minutes.
Default credentials:
* user: admin
* password: admin
* Username: **admin**
* Password: **admin**
# Configuration
You can configure the app at runtime using variables (see docker-compose.yml).
Possible environment variables:
You can configure the app at runtime using variables (see `docker-compose.yml`). Possible environment variables include:
* SUPERUSER_NAME
* SUPERUSER_EMAIL
@@ -51,4 +49,3 @@ Possible environment variables:
* SHORT_TIME_FORMAT
* DATETIME_FORMAT
* SHORT_DATETIME_FORMAT

101
docs/installation/ldap.md Normal file
View File

@@ -0,0 +1,101 @@
This guide explains how to implement LDAP authentication using an external server. User authentication will fall back to
built-in Django users in the event of a failure.
# Requirements
## Install openldap-devel
On Ubuntu:
```
sudo apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev
```
On CentOS:
```
sudo yum install -y python-devel openldap-devel
```
## Install django-auth-ldap
```
sudo pip install django-auth-ldap
```
# Configuration
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`.
## General Server Configuration
```python
import ldap
# Server URI
AUTH_LDAP_SERVER_URI = "ldaps://ad.example.com"
# 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 = "CN=NETBOXSA, OU=Service Accounts,DC=example,DC=com"
AUTH_LDAP_BIND_PASSWORD = "demo"
# 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 = True
```
## User Authentication
```python
from django_auth_ldap.config import LDAPSearch
# 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("ou=Users,dc=example,dc=com",
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 = "uid=%(user)s,ou=users,dc=example,dc=com"
# You can map user attributes to Django attributes as so.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn"
}
```
# User Groups for Permissions
```python
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
# heirarchy.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("dc=example,dc=com", ldap.SCOPE_SUBTREE,
"(objectClass=group)")
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Define a group required to login.
AUTH_LDAP_REQUIRE_GROUP = "CN=NETBOX_USERS,DC=example,DC=com"
# Define special user types using groups. Exercise great caution when assigning superuser status.
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"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"
}
# 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
```

181
docs/installation/netbox.md Normal file
View File

@@ -0,0 +1,181 @@
# Installation
NetBox requires following system dependencies:
* python2.7
* python-dev
* python-pip
* libxml2-dev
* libxslt1-dev
* libffi-dev
* graphviz
* libpq-dev
```
# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev
```
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
## Option A: Download a Release
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive and extract it to your desired path. In this example, we'll use `/opt/netbox`.
```
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
# ln -s netbox-X.Y.Z/ netbox
# cd /opt/netbox/
```
## Option B: Clone the Git Repository
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
```
# mkdir -p /opt/netbox/
# cd /opt/netbox/
```
If `git` is not already installed, install it:
```
# sudo apt-get install -y git
```
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
```
# git clone -b master https://github.com/digitalocean/netbox.git .
Cloning into '.'...
remote: Counting objects: 1994, done.
remote: Compressing objects: 100% (150/150), done.
remote: Total 1994 (delta 80), reused 0 (delta 0), pack-reused 1842
Receiving objects: 100% (1994/1994), 472.36 KiB | 0 bytes/s, done.
Resolving deltas: 100% (1495/1495), done.
Checking connectivity... done.
```
## Install Python Packages
Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
```
# sudo pip install -r requirements.txt
```
# Configuration
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
```
# cd netbox/netbox/
# cp configuration.example.py configuration.py
```
Open `configuration.py` with your preferred editor and set the following variables:
* ALLOWED_HOSTS
* DATABASE
* SECRET_KEY
## ALLOWED_HOSTS
This is a list of the valid hostnames by which this server can be reached. You must specify at least one name or IP address.
Example:
```
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
```
## DATABASE
This parameter holds the database configuration details. You must define the username and password used when you configured PostgreSQL. If the service is running on a remote host, replace `localhost` with its address.
Example:
```
DATABASE = {
'NAME': 'netbox', # Database name
'USER': 'netbox', # PostgreSQL username
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
'HOST': 'localhost', # Database server
'PORT': '', # Database port (leave blank for default)
}
```
## SECRET_KEY
Generate a random secret key of at least 50 alphanumeric characters. This key must be unique to this installation and must not be shared outside the local system.
You may use the script located at `netbox/generate_secret_key.py` to generate a suitable key.
# Run Database Migrations
Before NetBox can run, we need to install the database schema. This is done by running `./manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):
```
# cd /opt/netbox/netbox/
# ./manage.py migrate
Operations to perform:
Apply all migrations: dcim, sessions, admin, ipam, utilities, auth, circuits, contenttypes, extras, secrets, users
Running migrations:
Rendering model states... DONE
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
...
```
If this step results in a PostgreSQL authentication error, ensure that the username and password created in the database match what has been specified in `configuration.py`
# Create a Super User
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
```
# ./manage.py createsuperuser
Username: admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.
```
# Collect Static Files
```
# ./manage.py collectstatic
You have requested to collect static files at the destination
location as specified in your settings:
/opt/netbox/netbox/static
This will overwrite existing files!
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: yes
```
# Test the Application
At this point, NetBox should be able to run. We can verify this by starting a development instance:
```
# ./manage.py runserver 0.0.0.0:8000 --insecure
Performing system checks...
System check identified no issues (0 silenced).
June 17, 2016 - 16:17:36
Django version 1.9.7, using settings 'netbox.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
```
Now if we navigate to the name or IP of the server (as defined in `ALLOWED_HOSTS`) we should be greeted with the NetBox home page. Note that this built-in web service is for development and testing purposes only. It is not suited for production use.
!!! warning
If the test service does not run, or you cannot reach the NetBox home page, something has gone wrong. Do not proceed with the rest of this guide until the installation has been corrected.

View File

@@ -0,0 +1,42 @@
NetBox requires a PostgreSQL database to store data. MySQL is not supported, as NetBox leverage's PostgreSQL's built-in [network address types](https://www.postgresql.org/docs/9.1/static/datatype-net-types.html).
# Installation
The following packages are needed to install PostgreSQL with Python support:
* postgresql
* libpq-dev
* python-psycopg2
```
# sudo apt-get install -y postgresql libpq-dev python-psycopg2
```
# Configuration
At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. This is done with the following commands.
!!! danger
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
```
# sudo -u postgres psql
psql (9.3.13)
Type "help" for help.
postgres=# CREATE DATABASE netbox;
CREATE DATABASE
postgres=# CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox;
GRANT
postgres=# \q
```
You can verify that authentication works issuing the following command and providing the configured password:
```
# psql -U netbox -h localhost -W
```
If successful, you will enter a `postgres` prompt. Type `\q` to exit.

View File

@@ -0,0 +1,61 @@
# Install the Latest Code
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
## Option A: Download a Release
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`.
Download and extract the latest version:
```
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
# ln -sf netbox-X.Y.Z/ netbox
```
Copy the 'configuration.py' you created when first installing to the new version:
```
# cp /opt/netbox-X.Y.Z/configuration.py /opt/netbox/configuration.py
```
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
```
# cp /opt/netbox-X.Y.Z/gunicorn_config.py /opt/netbox/gunicorn_config.py
```
## Option B: Clone the Git Repository (latest master release)
This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most recent iteration of the master branch:
```
# cd /opt/netbox
# git checkout master
# git pull origin master
# git status
```
# Run the Upgrade Script
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).
```
# ./upgrade.sh
```
This script:
* Installs or upgrades any new required Python packages
* Applies any database migrations that were included in the release
* Collects all static files to be served by the HTTP service
# Restart the WSGI Service
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
```
# sudo supervisorctl restart netbox
```

View File

@@ -0,0 +1,132 @@
# Web Server Installation
We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for the purposes of this guide. For web servers, we provide example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/2.4). (You are of course free to use whichever combination of HTTP and WSGI services you'd like.) We'll also use [supervisord](http://supervisord.org/) to enable service persistence.
```
# sudo apt-get install -y gunicorn supervisor
```
## Option A: nginx
The following will serve as a minimal nginx configuration. Be sure to modify your server name and installation path appropriately.
```
# sudo apt-get install -y nginx
```
Once nginx is installed, proceed with the following configuration:
```
server {
listen 80;
server_name netbox.example.com;
access_log off;
location /static/ {
alias /opt/netbox/netbox/static/;
}
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
}
```
Save this configuration to `/etc/nginx/sites-available/netbox`. Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sites-enabled` directory to the configuration file you just created.
```
# cd /etc/nginx/sites-enabled/
# rm default
# ln -s /etc/nginx/sites-available/netbox
```
Restart the nginx service to use the new configuration.
```
# service nginx restart
* Restarting nginx nginx
```
To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04).
## Option B: Apache
```
# sudo apt-get install -y apache2
```
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
```
<VirtualHost *:80>
ProxyPreserveHost On
ServerName netbox.example.com
Alias /static /opt/netbox/netbox/static
<Directory /opt/netbox/netbox/static>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Require all granted
</Directory>
<Location /static>
ProxyPass !
</Location>
ProxyPass / http://127.0.0.1:8001/
ProxyPassReverse / http://127.0.0.1:8001/
</VirtualHost>
```
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
```
# a2enmod proxy
# a2enmod proxy_http
# a2ensite netbox
# service apache2 restart
```
To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-14-04).
# gunicorn Installation
Save the following configuration file in the root netbox installation path (in this example, `/opt/netbox/`) as `gunicorn_config.py`. Be sure to verify the location of the gunicorn executable (e.g. `which gunicorn`) and to update the `pythonpath` variable if needed.
```
command = '/usr/bin/gunicorn'
pythonpath = '/opt/netbox/netbox'
bind = '127.0.0.1:8001'
workers = 3
user = 'www-data'
```
# supervisord Installation
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
```
[program:netbox]
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
directory = /opt/netbox/netbox/
user = www-data
```
Finally, restart the supervisor service to detect and run the gunicorn service:
```
# service supervisor restart
```
At this point, you should be able to connect to the nginx HTTP service at the server name or IP address you provided. If you are unable to connect, check that the nginx service is running and properly configured. If you receive a 502 (bad gateway) error, this indicates that gunicorn is misconfigured or not running.
!!! info
Please keep in mind that the configurations provided here are bare minimums required to get NetBox up and running. You will almost certainly want to make some changes to better suit your production environment.

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

24
mkdocs.yml Normal file
View File

@@ -0,0 +1,24 @@
site_name: NetBox
pages:
- 'Introduction': 'index.md'
- 'Installation':
- 'PostgreSQL': 'installation/postgresql.md'
- 'NetBox': 'installation/netbox.md'
- 'Web Server': 'installation/web-server.md'
- 'LDAP (Optional)': 'installation/ldap.md'
- 'Upgrading': 'installation/upgrading.md'
- 'Alternate Install: Docker': 'installation/docker.md'
- 'Configuration':
- 'Mandatory Settings': 'configuration/mandatory-settings.md'
- 'Optional Settings': 'configuration/optional-settings.md'
- 'Data Model':
- 'Circuits': 'data-model/circuits.md'
- 'DCIM': 'data-model/dcim.md'
- 'IPAM': 'data-model/ipam.md'
- 'Secrets': 'data-model/secrets.md'
- 'Extras': 'data-model/extras.md'
- 'API Integration': 'api-integration.md'
markdown_extensions:
- admonition:

View File

@@ -1 +1 @@
default_app_config = 'dcim.apps.IPAMConfig'
default_app_config = 'dcim.apps.DCIMConfig'

View File

@@ -89,7 +89,7 @@ class DeviceTypeAdmin(admin.ModelAdmin):
power_port_count=Count('power_port_templates', distinct=True),
power_outlet_count=Count('power_outlet_templates', distinct=True),
interface_count=Count('interface_templates', distinct=True),
devicebay_count=Count('devicebay_templates', distinct=True),
devicebay_count=Count('device_bay_templates', distinct=True),
)
def console_ports(self, instance):
@@ -180,4 +180,4 @@ class DeviceAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(DeviceAdmin, self).get_queryset(request)
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip', 'rack')
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack')

View File

@@ -221,12 +221,14 @@ class DeviceSerializer(serializers.ModelSerializer):
platform = PlatformNestedSerializer()
rack = RackNestedSerializer()
primary_ip = DeviceIPAddressNestedSerializer()
primary_ip4 = DeviceIPAddressNestedSerializer()
primary_ip6 = DeviceIPAddressNestedSerializer()
parent_device = serializers.SerializerMethodField()
class Meta:
model = Device
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'platform', 'serial', 'rack', 'position',
'face', 'parent_device', 'status', 'primary_ip', 'comments']
'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
def get_parent_device(self, obj):
try:

View File

@@ -194,7 +194,7 @@ class DeviceListView(generics.ListAPIView):
List devices (filterable)
"""
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'platform', 'rack__site')\
.prefetch_related('primary_ip__nat_outside')
.prefetch_related('primary_ip4__nat_outside', 'primary_ip6__nat_outside')
serializer_class = serializers.DeviceSerializer
filter_class = filters.DeviceFilter
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]

View File

@@ -1,6 +1,6 @@
from django.apps import AppConfig
class IPAMConfig(AppConfig):
class DCIMConfig(AppConfig):
name = "dcim"
verbose_name = "DCIM"

View File

@@ -1919,7 +1919,8 @@
"position": 1,
"face": 0,
"status": true,
"primary_ip": 1,
"primary_ip4": 1,
"primary_ip6": null,
"comments": ""
}
},
@@ -1938,7 +1939,8 @@
"position": 17,
"face": 0,
"status": true,
"primary_ip": 5,
"primary_ip4": 5,
"primary_ip6": null,
"comments": ""
}
},
@@ -1957,7 +1959,8 @@
"position": 33,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -1976,7 +1979,8 @@
"position": 34,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -1995,7 +1999,8 @@
"position": 34,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2014,7 +2019,8 @@
"position": 33,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2033,7 +2039,8 @@
"position": 1,
"face": 0,
"status": true,
"primary_ip": 3,
"primary_ip4": 3,
"primary_ip6": null,
"comments": ""
}
},
@@ -2052,7 +2059,8 @@
"position": 17,
"face": 0,
"status": true,
"primary_ip": 19,
"primary_ip4": 19,
"primary_ip6": null,
"comments": ""
}
},
@@ -2071,7 +2079,8 @@
"position": 42,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2090,7 +2099,8 @@
"position": null,
"face": null,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2109,7 +2119,8 @@
"position": null,
"face": null,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},

View File

@@ -340,7 +340,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
disabled_indicator='device'))
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(),
widget=forms.Select(attrs={'filter-for': 'device_type'}))
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), label='Model', widget=APISelect(
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), label='Device type', widget=APISelect(
api_url='/api/dcim/device-types/?manufacturer_id={{manufacturer}}',
display_field='model'
))
@@ -349,7 +349,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
class Meta:
model = Device
fields = ['name', 'device_role', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
'platform', 'primary_ip', 'comments']
'platform', 'primary_ip4', 'primary_ip6', 'comments']
help_texts = {
'device_role': "The function this device serves",
'serial': "Chassis serial number",
@@ -369,20 +369,23 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
self.initial['site'] = self.instance.rack.site
self.initial['manufacturer'] = self.instance.device_type.manufacturer
# Compile list of IPs assigned to this device
primary_ip_choices = []
interface_ips = IPAddress.objects.filter(interface__device=self.instance)
primary_ip_choices += [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
nat_ips = IPAddress.objects.filter(nat_inside__interface__device=self.instance)\
.select_related('nat_inside__interface')
primary_ip_choices += [(ip.id, '{} ({} NAT)'.format(ip.address, ip.nat_inside.interface)) for ip in nat_ips]
self.fields['primary_ip'].choices = [(None, '---------')] + primary_ip_choices
# Compile list of choices for primary IPv4 and IPv6 addresses
for family in [4, 6]:
ip_choices = []
interface_ips = IPAddress.objects.filter(family=family, interface__device=self.instance)
ip_choices += [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
nat_ips = IPAddress.objects.filter(family=family, nat_inside__interface__device=self.instance)\
.select_related('nat_inside__interface')
ip_choices += [(ip.id, '{} ({} NAT)'.format(ip.address, ip.nat_inside.interface)) for ip in nat_ips]
self.fields['primary_ip{}'.format(family)].choices = [(None, '---------')] + ip_choices
else:
# An object that doesn't exist yet can't have any IPs assigned to it
self.fields['primary_ip'].choices = []
self.fields['primary_ip'].widget.attrs['readonly'] = True
self.fields['primary_ip4'].choices = []
self.fields['primary_ip4'].widget.attrs['readonly'] = True
self.fields['primary_ip6'].choices = []
self.fields['primary_ip6'].widget.attrs['readonly'] = True
# Limit rack choices
if self.is_bound:

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 18:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ipam', '0001_initial'),
('dcim', '0005_auto_20160706_1722'),
]
operations = [
migrations.AddField(
model_name='device',
name='primary_ip4',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name=b'Primary IPv4'),
),
migrations.AddField(
model_name='device',
name='primary_ip6',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name=b'Primary IPv6'),
),
]

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 18:40
from __future__ import unicode_literals
from django.db import migrations
def copy_primary_ip(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for d in Device.objects.select_related('primary_ip'):
if not d.primary_ip:
continue
if d.primary_ip.family == 4:
d.primary_ip4 = d.primary_ip
elif d.primary_ip.family == 6:
d.primary_ip6 = d.primary_ip
d.save()
def restore_primary_ip(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for d in Device.objects.select_related('primary_ip4', 'primary_ip6'):
if d.primary_ip:
continue
# Prefer IPv6 over IPv4
if d.primary_ip6:
d.primary_ip = d.primary_ip6
elif d.primary_ip4:
d.primary_ip = d.primary_ip4
d.save()
class Migration(migrations.Migration):
dependencies = [
('dcim', '0006_add_device_primary_ip4_ip6'),
]
operations = [
migrations.RunPython(copy_primary_ip, restore_primary_ip),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 19:01
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dcim', '0007_device_copy_primary_ip'),
]
operations = [
migrations.RemoveField(
model_name='device',
name='primary_ip',
),
]

View File

@@ -1,5 +1,6 @@
from collections import OrderedDict
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import MinValueValidator
@@ -263,7 +264,7 @@ class Rack(CreatedUpdatedModel):
@property
def display_name(self):
if self.facility_id:
return "{} ({})".format(self.name, self.facility_id)
return u"{} ({})".format(self.name, self.facility_id)
return self.name
def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False):
@@ -605,8 +606,10 @@ class Device(CreatedUpdatedModel):
help_text='Number of the lowest U position occupied by the device')
face = models.PositiveSmallIntegerField(blank=True, null=True, choices=RACK_FACE_CHOICES, verbose_name='Rack face')
status = models.BooleanField(choices=STATUS_CHOICES, default=STATUS_ACTIVE, verbose_name='Status')
primary_ip = models.OneToOneField('ipam.IPAddress', related_name='primary_for', on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='Primary IP')
primary_ip4 = models.OneToOneField('ipam.IPAddress', related_name='primary_ip4_for', on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='Primary IPv4')
primary_ip6 = models.OneToOneField('ipam.IPAddress', related_name='primary_ip6_for', on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='Primary IPv6')
comments = models.TextField(blank=True)
class Meta:
@@ -696,9 +699,9 @@ class Device(CreatedUpdatedModel):
if self.name:
return self.name
elif self.position:
return "{} ({} U{})".format(self.device_type, self.rack.name, self.position)
return u"{} ({} U{})".format(self.device_type, self.rack.name, self.position)
else:
return "{} ({})".format(self.device_type, self.rack.name)
return u"{} ({})".format(self.device_type, self.rack.name)
@property
def identifier(self):
@@ -709,6 +712,17 @@ class Device(CreatedUpdatedModel):
return self.name
return '{{{}}}'.format(self.pk)
@property
def primary_ip(self):
if settings.PREFER_IPV4 and self.primary_ip4:
return self.primary_ip4
elif self.primary_ip6:
return self.primary_ip6
elif self.primary_ip4:
return self.primary_ip4
else:
return None
def get_children(self):
"""
Return the set of child Devices installed in DeviceBays within this Device.

View File

@@ -318,6 +318,8 @@ class DeviceTest(APITestCase):
'parent_device',
'status',
'primary_ip',
'primary_ip4',
'primary_ip6',
'comments',
]
@@ -375,6 +377,10 @@ class DeviceTest(APITestCase):
'primary_ip_address',
'primary_ip_family',
'primary_ip_id',
'primary_ip4_address',
'primary_ip4_family',
'primary_ip4_id',
'primary_ip6',
'rack_display_name',
'rack_facility_id',
'rack_id',

View File

@@ -1,4 +1,6 @@
import re
from natsort import natsorted
from operator import attrgetter
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
@@ -259,13 +261,22 @@ def devicetype(request, pk):
devicetype = get_object_or_404(DeviceType, pk=pk)
# Component tables
consoleport_table = tables.ConsolePortTemplateTable(ConsolePortTemplate.objects.filter(device_type=devicetype))
consoleserverport_table = tables.ConsoleServerPortTemplateTable(ConsoleServerPortTemplate.objects
.filter(device_type=devicetype))
powerport_table = tables.PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
poweroutlet_table = tables.PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
consoleport_table = tables.ConsolePortTemplateTable(
natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
powerport_table = tables.PowerPortTemplateTable(
natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
poweroutlet_table = tables.PowerOutletTemplateTable(
natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
devicebay_table = tables.DeviceBayTemplateTable(DeviceBayTemplate.objects.filter(device_type=devicetype))
devicebay_table = tables.DeviceBayTemplateTable(
natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
if request.user.has_perm('dcim.change_devicetype'):
consoleport_table.base_columns['pk'].visible = True
consoleserverport_table.base_columns['pk'].visible = True
@@ -501,7 +512,8 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
#
class DeviceListView(ObjectListView):
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip')
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip4',
'primary_ip6')
filter = filters.DeviceFilter
filter_form = forms.DeviceFilterForm
table = tables.DeviceTable
@@ -512,15 +524,26 @@ class DeviceListView(ObjectListView):
def device(request, pk):
device = get_object_or_404(Device, pk=pk)
console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
console_ports = natsorted(
ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
)
cs_ports = natsorted(
ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
)
power_ports = natsorted(
PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
)
power_outlets = natsorted(
PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
)
interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
.select_related('connected_as_a', 'connected_as_b', 'circuit')
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
.select_related('connected_as_a', 'connected_as_b', 'circuit')
device_bays = DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer')
device_bays = natsorted(
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
key=attrgetter('name')
)
# Gather any secrets which belong to this device
secrets = device.secrets.all()
@@ -1634,7 +1657,10 @@ def ipaddress_assign(request, pk):
ipaddress.interface))
if form.cleaned_data['set_as_primary']:
device.primary_ip = ipaddress
if ipaddress.family == 4:
device.primary_ip4 = ipaddress
elif ipaddress.family == 6:
device.primary_ip6 = ipaddress
device.save()
if '_addanother' in request.POST:

View File

@@ -329,7 +329,7 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
class IPAddressFromCSVForm(forms.ModelForm):
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
error_messages={'invalid_choice': 'Site not found.'})
error_messages={'invalid_choice': 'VRF not found.'})
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
error_messages={'invalid_choice': 'Device not found.'})
interface_name = forms.CharField(required=False)
@@ -368,7 +368,10 @@ class IPAddressFromCSVForm(forms.ModelForm):
name=self.cleaned_data['interface_name'])
# Set as primary for device
if self.cleaned_data['is_primary']:
self.instance.primary_for = self.cleaned_data['device']
if self.instance.family == 4:
self.instance.primary_ip4_for = self.cleaned_data['device']
elif self.instance.family == 6:
self.instance.primary_ip6_for = self.cleaned_data['device']
return super(IPAddressFromCSVForm, self).save(commit=commit)

View File

@@ -123,6 +123,8 @@ class Aggregate(CreatedUpdatedModel):
# Ensure that the aggregate being added does not cover an existing aggregate
covered_aggregates = Aggregate.objects.filter(prefix__net_contained=str(self.prefix))
if self.pk:
covered_aggregates = covered_aggregates.exclude(pk=self.pk)
if covered_aggregates:
raise ValidationError("{} is overlaps with an existing aggregate ({})"
.format(self.prefix, covered_aggregates[0]))
@@ -314,12 +316,20 @@ class IPAddress(CreatedUpdatedModel):
super(IPAddress, self).save(*args, **kwargs)
def to_csv(self):
# Determine if this IP is primary for a Device
is_primary = False
if self.family == 4 and getattr(self, 'primary_ip4_for', False):
is_primary = True
elif self.family == 6 and getattr(self, 'primary_ip6_for', False):
is_primary = True
return ','.join([
str(self.address),
self.vrf.rd if self.vrf else '',
self.device.identifier if self.device else '',
self.interface.name if self.interface else '',
'True' if getattr(self, 'primary_for', False) else '',
'True' if is_primary else '',
self.description,
])
@@ -367,7 +377,7 @@ class VLAN(CreatedUpdatedModel):
@property
def display_name(self):
return "{} ({})".format(self.vid, self.name)
return u"{} ({})".format(self.vid, self.name)
def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status]

View File

@@ -364,7 +364,7 @@ def prefix_ipaddresses(request, pk):
# Find all IPAddresses belonging to this Prefix
ipaddresses = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix))\
.select_related('vrf', 'interface__device', 'primary_for')
.select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
ip_table = tables.IPAddressTable(ipaddresses)
ip_table.model = IPAddress
@@ -383,7 +383,7 @@ def prefix_ipaddresses(request, pk):
#
class IPAddressListView(ObjectListView):
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_for')
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
filter = filters.IPAddressFilter
filter_form = forms.IPAddressFilterForm
table = tables.IPAddressTable
@@ -443,9 +443,14 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
obj.save()
# Update primary IP for device if needed
try:
device = obj.primary_for
device.primary_ip = obj
device.save()
if obj.family == 4 and obj.primary_ip4_for:
device = obj.primary_ip4_for
device.primary_ip4 = obj
device.save()
elif obj.family == 6 and obj.primary_ip6_for:
device = obj.primary_ip6_for
device.primary_ip6 = obj
device.save()
except Device.DoesNotExist:
pass

View File

@@ -73,3 +73,12 @@ 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'
# Optionally display a persistent banner at the top and/or bottom of every page. To display the same content in both
# banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
BANNER_TOP = ''
BANNER_BOTTOM = ''
# 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 = False

View File

@@ -1,3 +1,4 @@
import logging
import os
import socket
@@ -11,7 +12,7 @@ except ImportError:
"the documentation.")
VERSION = '1.1.0'
VERSION = '1.2.1'
# Import local configuration
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
@@ -37,8 +38,40 @@ TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# Attempt to import LDAP configuration if it has been defined
LDAP_IGNORE_CERT_ERRORS = False
try:
from ldap_config import *
LDAP_CONFIGURED = True
except ImportError:
LDAP_CONFIGURED = False
# LDAP configuration (optional)
if LDAP_CONFIGURED:
try:
import ldap
import django_auth_ldap
# Prepend LDAPBackend to the default ModelBackend
AUTHENTICATION_BACKENDS = [
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
]
# Optionally disable strict certificate checking
if LDAP_IGNORE_CERT_ERRORS:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
# Enable logging for django_auth_ldap
logger = logging.getLogger('django_auth_ldap')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
except ImportError:
raise ImproperlyConfigured("LDAP authentication has been configured, but django-auth-ldap is not installed. "
"You can remove netbox/ldap.py to disable LDAP.")
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Database

View File

@@ -2,7 +2,7 @@ from django.conf.urls import include, url
from django.contrib import admin
from django.views.defaults import page_not_found
from views import home, docs, trigger_500
from views import home, trigger_500
from users.views import login, logout
@@ -30,10 +30,6 @@ urlpatterns = [
url(r'^api/docs/', include('rest_framework_swagger.urls')),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# Dcoumentation
url(r'^docs/$', docs, kwargs={'path': 'index'}, name='docs_root'),
url(r'^docs/(?P<path>[\w-]+)/$', docs, name='docs'),
# Error testing
url(r'^404/$', page_not_found),
url(r'^500/$', trigger_500),

View File

@@ -45,25 +45,6 @@ def home(request):
})
def docs(request, path):
"""
Display a page of Markdown-formatted documentation.
"""
filename = '{}/docs/{}.md'.format(settings.BASE_DIR.rsplit('/', 1)[0], path)
try:
with open(filename, 'r') as docfile:
markup = docfile.read()
except:
raise Http404
content = mark_safe(markdown(markup, extensions=['mdx_gfm', 'toc']))
return render(request, 'docs.html', {
'content': content,
'path': path,
})
def trigger_500(request):
"""Hot-wired method of triggering a server error to test reporting."""

View File

@@ -28,6 +28,42 @@ body {
footer p {
margin: 20px 0;
}
@media (max-width: 1120px) {
.navbar-header {
float: none;
}
.navbar-left,.navbar-right {
float: none !important;
}
.navbar-toggle {
display: block;
}
.navbar-collapse {
border-top: 1px solid transparent;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
}
.navbar-fixed-top {
top: 0;
border-width: 0 0 1px;
}
.navbar-collapse.collapse {
display: none!important;
}
.navbar-nav {
float: none!important;
margin-top: 7.5px;
}
.navbar-nav>li {
float: none;
}
.navbar-nav>li>a {
padding-top: 10px;
padding-bottom: 10px;
}
.collapse.in {
display:block !important;
}
}
/* Forms */
label {
@@ -259,6 +295,9 @@ ul.rack_near_face li.empty:hover a {
.dark_gray:hover { background-color: #2c3e50; }
/* Misc */
.banner-bottom {
margin-bottom: 50px;
}
.panel table {
margin-bottom: 0;
}

View File

@@ -42,7 +42,7 @@ class SecretListView(generics.GenericAPIView):
"""
List secrets (filterable). If a private key is POSTed, attempt to decrypt each Secret.
"""
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
queryset = Secret.objects.select_related('device__primary_ip4', 'device__primary_ip6', 'role')\
.prefetch_related('role__users', 'role__groups')
serializer_class = serializers.SecretSerializer
filter_class = SecretFilter
@@ -52,7 +52,7 @@ class SecretListView(generics.GenericAPIView):
queryset = self.filter_queryset(self.get_queryset())
# Attempt to decrypt each Secret if a private key was provided.
if private_key is not None:
if private_key:
try:
uk = UserKey.objects.get(user=request.user)
except UserKey.DoesNotExist:
@@ -87,7 +87,7 @@ class SecretDetailView(generics.GenericAPIView):
"""
Retrieve a single Secret. If a private key is POSTed, attempt to decrypt the Secret.
"""
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
queryset = Secret.objects.select_related('device__primary_ip4', 'device__primary_ip6', 'role')\
.prefetch_related('role__users', 'role__groups')
serializer_class = serializers.SecretSerializer
renderer_classes = [FormlessBrowsableAPIRenderer, JSONRenderer, FreeRADIUSClientsRenderer]
@@ -96,7 +96,7 @@ class SecretDetailView(generics.GenericAPIView):
secret = get_object_or_404(Secret, pk=pk)
# Attempt to decrypt the Secret if a private key was provided.
if private_key is not None:
if private_key:
try:
uk = UserKey.objects.get(user=request.user)
except UserKey.DoesNotExist:

View File

@@ -224,6 +224,11 @@
</div>
</nav>
<div class="container wrapper">
{% if settings.BANNER_TOP %}
<div class="alert alert-info text-center" role="alert">
{{ settings.BANNER_TOP|safe }}
</div>
{% endif %}
{% if settings.MAINTENANCE_MODE %}
<div class="alert alert-warning text-center" role="alert">
<h4><i class="fa fa-exclamation-triangle"></i> Maintenance Mode</h4>
@@ -239,20 +244,25 @@
</div>
{% endfor %}
{% block content %}{% endblock %}
<div class="push"></div>
<div class="push"></div>
{% if settings.BANNER_BOTTOM %}
<div class="alert alert-info text-center banner-bottom" role="alert">
{{ settings.BANNER_BOTTOM|safe }}
</div>
{% endif %}
</div>
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-md-4">
<div class="col-xs-4">
<p class="text-muted">{{ settings.HOSTNAME }} (v{{ settings.VERSION }})</p>
</div>
<div class="col-md-4 text-center">
<div class="col-xs-4 text-center">
<p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
</div>
<div class="col-md-4 text-right">
<div class="col-xs-4 text-right">
<p class="text-muted">
<i class="fa fa-fw fa-book text-primary"></i> <a href="{% url 'docs_root' %}">Docs</a> &middot;
<i class="fa fa-fw fa-book text-primary"></i> <a href="http://netbox.readthedocs.io/" target="_blank">Docs</a> &middot;
<i class="fa fa-fw fa-cloud text-primary"></i> <a href="/api/docs/">API</a> &middot;
<i class="fa fa-fw fa-code text-primary"></i> <a href="https://github.com/digitalocean/netbox">Code</a>
</p>

View File

@@ -101,14 +101,29 @@
</td>
</tr>
<tr>
<td>Primary IP</td>
<td>Primary IPv4</td>
<td>
{% if device.primary_ip %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip.pk %}">{{ device.primary_ip.address.ip }}</a>
{% if device.primary_ip.nat_inside %}
<span>(NAT for {{ device.primary_ip.nat_inside.address.ip }})</span>
{% elif device.primary_ip.nat_outside %}
<span>(NAT: {{ device.primary_ip.nat_outside.address.ip }})</span>
{% if device.primary_ip4 %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip4.pk %}">{{ device.primary_ip4.address.ip }}</a>
{% if device.primary_ip4.nat_inside %}
<span>(NAT for {{ device.primary_ip4.nat_inside.address.ip }})</span>
{% elif device.primary_ip4.nat_outside %}
<span>(NAT: {{ device.primary_ip4.nat_outside.address.ip }})</span>
{% endif %}
{% else %}
<span class="text-muted">Not defined</span>
{% endif %}
</td>
</tr>
<tr>
<td>Primary IPv6</td>
<td>
{% if device.primary_ip6 %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip6.pk %}">{{ device.primary_ip6.address.ip }}</a>
{% if device.primary_ip6.nat_inside %}
<span>(NAT for {{ device.primary_ip6.nat_inside.address.ip }})</span>
{% elif device.primary_ip6.nat_outside %}
<span>(NAT: {{ device.primary_ip6.nat_outside.address.ip }})</span>
{% endif %}
{% else %}
<span class="text-muted">Not defined</span>

View File

@@ -31,7 +31,10 @@
<div class="panel-body">
{% render_field form.platform %}
{% render_field form.status %}
{% if obj %}{% render_field form.primary_ip %}{% endif %}
{% if obj %}
{% render_field form.primary_ip4 %}
{% render_field form.primary_ip6 %}
{% endif %}
</div>
</div>
<div class="panel panel-default">

View File

@@ -81,7 +81,7 @@
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
</div>
<div class="col-md-6">
{% if devicetype.is_network_device %}
{% if devicetype.is_parent_device %}
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' add_url='dcim:devicetype_add_devicebay' delete_url='dcim:devicetype_delete_devicebay' %}
{% endif %}
{% if devicetype.is_network_device %}

View File

@@ -4,7 +4,7 @@
</td>
<td>{{ ip.interface }}</td>
<td>
{% if device.primary_ip == ip %}
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
<span class="label label-success">Primary</span>
{% endif %}
</td>

View File

@@ -154,17 +154,19 @@
</div>
</div>
</div>
<div class="col-md-3">
<div class="rack_header">
<div class="row col-md-6">
<div class="col-md-6 col-sm-6 col-xs-12">
<div class="rack_header">
<h4>Front</h4>
</div>
{% include 'dcim/_rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 %}
</div>
<div class="col-md-3">
</div>
{% include 'dcim/_rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 %}
</div>
<div class="col-md-6 col-sm-6 col-xs-12">
<div class="rack_header">
<h4>Rear</h4>
</div>
{% include 'dcim/_rack_elevation.html' with primary_face=rear_elevation secondary_face=front_elevation face_id=1 %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,29 +0,0 @@
{% extends '_base.html' %}
{% load render_table from django_tables2 %}
{% block title %}Documentation{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Documentation</strong>
</div>
<div class="list-group">
<a href="{% url 'docs_root' %}" class="list-group-item{% if path == 'index' %} active{% endif %}">Home</a>
<a href="{% url 'docs' path='getting-started' %}" class="list-group-item{% if path == 'getting-started' %} active{% endif %}">Getting Started</a>
<a href="{% url 'docs' path='configuration' %}" class="list-group-item{% if path == 'configuration' %} active{% endif %}">Configuration</a>
<a href="{% url 'docs' path='dcim' %}" class="list-group-item{% if path == 'dcim' %} active{% endif %}">DCIM</a>
<a href="{% url 'docs' path='ipam' %}" class="list-group-item{% if path == 'ipam' %} active{% endif %}">IPAM</a>
<a href="{% url 'docs' path='circuits' %}" class="list-group-item{% if path == 'circuits' %} active{% endif %}">Circuits</a>
<a href="{% url 'docs' path='secrets' %}" class="list-group-item{% if path == 'secrets' %} active{% endif %}">Secrets</a>
<a href="{% url 'docs' path='extras' %}" class="list-group-item{% if path == 'extras' %} active{% endif %}">Extras</a>
</div>
</div>
</div>
<div class="col-md-9">
{{ content }}
</div>
</div>
{% endblock %}

View File

@@ -3,7 +3,7 @@
{% block content %}
<div class="row" style="margin-top: 150px;">
<div class="col-md-4 col-md-offset-4">
<div class="col-sm-4 col-sm-offset-4">
{% if form.non_field_errors %}
<div class="panel panel-danger">
<div class="panel-heading"><strong>Errors</strong></div>

View File

@@ -15,3 +15,4 @@ py-gfm==0.1.3
pycrypto==2.6.1
sqlparse==0.1.19
xmltodict==0.10.2
natsort>=5.0.0