1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

added docs, migrated input validated from JS to execute.py

This commit is contained in:
checktheroads
2019-05-28 00:53:45 -07:00
parent 65709a3779
commit a417afdd8c
33 changed files with 885 additions and 740 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,16 +0,0 @@
Authentication parameters are stored in the `devices.toml` file, at `hyperglass/hyperglass/configuration/devices.toml`. The array of tables simply stores the username and password for a device. SSH Key authentication is not yet supported.
Example:
```toml
[credential.'default']
username = "hyperglass"
password = "secret_password"
[credential.'other_credential']
username = "other_username"
password = "other_secret_password"
```
!!! warning "Security Warning"
These values are stored in plain text. Make sure the accounts are restricted and that the configuration file is stored in a secure location.

View File

@@ -1,25 +0,0 @@
Blacklisted querys are defined in `hyperglass/hyperglass/configuration/blacklist.toml`.
The blacklist is a simple TOML array (list) of host IPs or prefixes that you do not want end users to be able to query. For example, if you want to prevent users from looking up 198.18.0.0/15 or any contained host or prefix, you can add it to the blacklist:
```toml
blacklist = [
198.18.0.0/15
]
```
If you have multiple hosts/subnets you wish to blacklist, you can do so by adding a comma `,` after each entry (except the last):
```toml
blacklist = [
'198.18.0.0/15',
'10.0.0.0/8',
'192.168.0.0/16',
'2001:db8::/32'
'172.16.0.0/12'
]
```
When users attempt to query a matching host/prefix, they will receive the following error message by default:
<img src="/blacklist_error.png"></img>

View File

@@ -10,9 +10,10 @@
} }
</style> </style>
From `hyperglass/hyperglass/configuration/config.toml`: From `hyperglass/hyperglass/configuration/configuration.toml` `[branding]` table.
### site_title # Site Parameters
#### site_title
| Type | Default Value | | Type | Default Value |
| ------ | -------------- | | ------ | -------------- |
@@ -20,7 +21,7 @@ From `hyperglass/hyperglass/configuration/config.toml`:
HTML `<title>` element that is shown in a browser's title bar. HTML `<title>` element that is shown in a browser's title bar.
### title_mode #### title_mode
| Type | Default Value | | Type | Default Value |
| ------ | ------------- | | ------ | ------------- |
@@ -28,27 +29,17 @@ HTML `<title>` element that is shown in a browser's title bar.
Controls the title section on the main page. Controls the title section on the main page.
#### Parameters - `"none"` Hides Title and Subtitle text, displays logo defined in [logo_path](#logo_path).
- `"both"` Displays both Title and Subtitle text defined in [title](#title) and [subtitle](#subtitle) parameters.
- `"hide_subtitle"` Displays only the Title text defined in the [title](#title) parameter.
##### `"none"` #### title
Hides Title and Subtitle text, displays logo defined in [logo_path](#logo_path).
##### `"both"`
Displays both Title and Subtitle text defined in [title](#title) and [subtitle](#subtitle) parameters.
##### `"hide_subtitle"`
Displays only the Title text defined in the [title](#title) parameter.
### title
| Type | Default Value | | Type | Default Value |
| ------ | -------------- | | ------ | -------------- |
| String | `"hyperglass"` | | String | `"hyperglass"` |
### subtitle #### subtitle
| Type | Default Value | | Type | Default Value |
| ------ | -------------------- | | ------ | -------------------- |
@@ -56,7 +47,7 @@ Displays only the Title text defined in the [title](#title) parameter.
See [primary_asn](#primary_asn) parameter. See [primary_asn](#primary_asn) parameter.
### enable_footer #### enable_footer
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
@@ -64,7 +55,7 @@ See [primary_asn](#primary_asn) parameter.
Enables or disables entire footer element, which contains text defined in `hyperglass/hyperglass/render/templates/footer.md`. Enables or disables entire footer element, which contains text defined in `hyperglass/hyperglass/render/templates/footer.md`.
### enable_credit #### enable_credit
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
@@ -72,87 +63,7 @@ Enables or disables entire footer element, which contains text defined in `hyper
Enables or disables hoverable icon on the left side of the footer, which links to the hyperglass repo. Enables or disables hoverable icon on the left side of the footer, which links to the hyperglass repo.
### color_btn_submit #### show_peeringdb
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
Sets color of the submit button.
### color_tag_loctitle
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
Sets color of the title portion of the location tag which appears at the top of the results box on the left side.
### color_tag_cmdtitle
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
Sets color of the title portion of the command tag which appears at the top of the results box on the right side.
### color_tag_cmd
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#ff5e5b"` | <span class="bd-color" style="background-color: #ff5e5b;"></span> |
Sets color of the command name portion of the command tag which appears at the top of the results box on the right side.
### color_tag_loc
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
Sets color of the location name portion of the location tag which appears at the top of the results box on the left side.
### color_hero
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#fbfffe"` | <span class="bd-color" style="background-color: #fbfffe;"></span> |
Sets the background color of the main page. The main page is a Bulma [fullheight hero class](https://bulma.io/documentation/layout/hero/) layout. This parameter will set the color of the entire hero `<section>` class, including navbar, head, body, and footer subclasses.
### color_progressbar
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
Sets color of the progress bar that displays while the back-end application processes the request.
### logo_path
| Type | Default Value |
| ------ | ------------------------------------- |
| String | `"static/images/hyperglass-dark.png"` |
Sets the path to the logo file, which will be displayed if [title_mode](#title_mode) is set to `"logo_only"`. This file can be any browser-compatible format, such as JPEG, PNG, or SVG.
### logo_width
| Type | Default Value |
| ------ | ------------- |
| String | `"384"` |
Sets the width of the logo defined in the [logo_path](#logo_path) parameter. This is helpful if your logo is a dimension that doesn't quite work with the default width.
### placeholder_prefix
| Type | Default Value |
| ------ | ------------------------------------- |
| String | `"Prefix, IP, Community, or AS_PATH"` |
Sets the placeholder text that appears in the main search box.
### show_peeringdb
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
@@ -160,7 +71,93 @@ Sets the placeholder text that appears in the main search box.
Enables or disables the PeeringDB link in the upper right corner. If `True`, the [primary_asn](#primary_asn) will be automatically used to create the URL to your ASN's PeeringDB entry. Enables or disables the PeeringDB link in the upper right corner. If `True`, the [primary_asn](#primary_asn) will be automatically used to create the URL to your ASN's PeeringDB entry.
### text_results # Colors
#### color_btn_submit
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
Sets color of the submit button.
#### color_tag_loctitle
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
Sets color of the title portion of the location tag which appears at the top of the results box on the left side.
#### color_tag_cmdtitle
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
Sets color of the title portion of the command tag which appears at the top of the results box on the right side.
#### color_tag_cmd
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#ff5e5b"` | <span class="bd-color" style="background-color: #ff5e5b;"></span> |
Sets color of the command name portion of the command tag which appears at the top of the results box on the right side.
#### color_tag_loc
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
Sets color of the location name portion of the location tag which appears at the top of the results box on the left side.
#### color_bg
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#fbfffe"` | <span class="bd-color" style="background-color: #fbfffe;"></span> |
Sets the background color of the main page.
#### color_progressbar
| Type | Default Value | Preview |
| ------ | ------------- | ----------------------------------------------------------------- |
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
Sets color of the progress bar that displays while the back-end application processes the request.
# Logo
#### logo_path
| Type | Default Value |
| ------ | ------------------------------------- |
| String | `"static/images/hyperglass-dark.png"` |
Sets the path to the logo file, which will be displayed if [title_mode](#title_mode) is set to `"logo_only"`. This file can be any browser-compatible format, such as JPEG, PNG, or SVG.
#### logo_width
| Type | Default Value |
| ------ | ------------- |
| String | `"384"` |
Sets the width of the logo defined in the [logo_path](#logo_path) parameter. This is helpful if your logo is a dimension that doesn't quite work with the default width.
# UI Text
#### placeholder_prefix
| Type | Default Value |
| ------ | ------------------------------------- |
| String | `"Prefix, IP, Community, or AS_PATH"` |
Sets the placeholder text that appears in the main search box.
#### text_results
| Type | Default Value | | Type | Default Value |
| ------ | ------------- | | ------ | ------------- |
@@ -168,7 +165,7 @@ Enables or disables the PeeringDB link in the upper right corner. If `True`, the
Sets the header text of the results box. Sets the header text of the results box.
### text_location #### text_location
| Type | Default Value | | Type | Default Value |
| ------ | ------------- | | ------ | ------------- |
@@ -176,7 +173,7 @@ Sets the header text of the results box.
Sets the placeholder text of the location selector. Sets the placeholder text of the location selector.
### text_cache #### text_cache
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------------------------------------- | | ------ | ------------------------------------------------------- |
@@ -184,7 +181,7 @@ Sets the placeholder text of the location selector.
Sets the text at the bottom of the results box that states the cache timeout. `{cache_timeout}` will be formatted with the value of [cache_timeout](/configuration/general/#cache_timeout). Sets the text at the bottom of the results box that states the cache timeout. `{cache_timeout}` will be formatted with the value of [cache_timeout](/configuration/general/#cache_timeout).
### text_limiter_title #### text_limiter_title
| Type | Default Value | | Type | Default Value |
| ------ | ----------------- | | ------ | ----------------- |
@@ -192,7 +189,7 @@ Sets the text at the bottom of the results box that states the cache timeout. `{
Sets the title text for the site-wide rate limit page. Users are redirected to this page when they have accessed the site more than the [specified](/configuration/general/#rate_limit_site) limit. Sets the title text for the site-wide rate limit page. Users are redirected to this page when they have accessed the site more than the [specified](/configuration/general/#rate_limit_site) limit.
### text_limiter_subtitle #### text_limiter_subtitle
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------------------------------------------------------------------- | | ------ | ------------------------------------------------------------------------------------- |
@@ -200,7 +197,7 @@ Sets the title text for the site-wide rate limit page. Users are redirected to t
Sets the subtitle text for the site-wide rate limit page. Users are redirected to this page when they have accessed the site more than the [specified](/configuration/general/#rate_limit_site) limit. `{rate_limit_site}` will be formatted with the value of [rate_limit_site](/configuration/general/#rate_limit_site). Sets the subtitle text for the site-wide rate limit page. Users are redirected to this page when they have accessed the site more than the [specified](/configuration/general/#rate_limit_site) limit. `{rate_limit_site}` will be formatted with the value of [rate_limit_site](/configuration/general/#rate_limit_site).
### text_415_title #### text_500_title
| Type | Default Value | | Type | Default Value |
| ------ | ----------------- | | ------ | ----------------- |
@@ -208,7 +205,7 @@ Sets the subtitle text for the site-wide rate limit page. Users are redirected t
Sets the title text for the full general error page. Sets the title text for the full general error page.
### text_415_subtitle #### text_500_subtitle
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -216,7 +213,7 @@ Sets the title text for the full general error page.
Sets the subtitle text for the full general error page. Sets the subtitle text for the full general error page.
### text_415_button #### text_500_button
| Type | Default Value | | Type | Default Value |
| ------ | ----------------- | | ------ | ----------------- |
@@ -224,7 +221,7 @@ Sets the subtitle text for the full general error page.
Sets the button text for the full general error page. Sets the button text for the full general error page.
### text_help_bgp_route #### text_help_bgp_route
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -232,7 +229,7 @@ Sets the button text for the full general error page.
Sets the BGP Route query help text, displayed when the **?** icon is hovered. Sets the BGP Route query help text, displayed when the **?** icon is hovered.
### text_help_bgp_community #### text_help_bgp_community
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -243,7 +240,7 @@ Sets the BGP Community query help text, displayed when the **?** icon is hovered
!!! note !!! note
Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string. Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string.
### text_help_bgp_aspath #### text_help_bgp_aspath
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -254,7 +251,7 @@ Sets the BGP AS Path query help text, displayed when the **?** icon is hovered.
!!! note !!! note
Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string. Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string.
### text_help_ping #### text_help_ping
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -262,7 +259,7 @@ Sets the BGP AS Path query help text, displayed when the **?** icon is hovered.
Sets the Ping query help text, displayed when the **?** icon is hovered. Sets the Ping query help text, displayed when the **?** icon is hovered.
### text_help_traceroute #### text_help_traceroute
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -273,7 +270,9 @@ Sets the Traceroute query help text, displayed when the **?** icon is hovered.
!!! note !!! note
Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string. Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string.
### primary_font_url # Fonts
#### primary_font_url
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -281,7 +280,7 @@ Sets the Traceroute query help text, displayed when the **?** icon is hovered.
Sets the web font URL for the primary font. This font is used for all titles, subtitles, and non-code/preformatted text. The value is passed as a Jinja2 variable to the head block in the base template. Sets the web font URL for the primary font. This font is used for all titles, subtitles, and non-code/preformatted text. The value is passed as a Jinja2 variable to the head block in the base template.
### primary_font_name #### primary_font_name
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -289,7 +288,7 @@ Sets the web font URL for the primary font. This font is used for all titles, su
Sets the web font name for the primary font. This font is used for all titles, subtitles, and non-code/preformatted text. The value is passed as a Jinja2 variable to generate `hyperglass/hyperglass/static/sass/hyperglass.scss`, which ultimately get passed to CSS. Sets the web font name for the primary font. This font is used for all titles, subtitles, and non-code/preformatted text. The value is passed as a Jinja2 variable to generate `hyperglass/hyperglass/static/sass/hyperglass.scss`, which ultimately get passed to CSS.
### mono_font_url #### mono_font_url
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |
@@ -297,7 +296,7 @@ Sets the web font name for the primary font. This font is used for all titles, s
Sets the web font URL for the monospace/code/preformatted text font. This font is used for all query output text, as well as the command title and command name tag. The value is passed as a Jinja2 variable to the head block in the base template. Sets the web font URL for the monospace/code/preformatted text font. This font is used for all query output text, as well as the command title and command name tag. The value is passed as a Jinja2 variable to the head block in the base template.
### mono_font_name #### mono_font_name
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | ------------------------- |

View File

@@ -1,83 +0,0 @@
Commands are defined in `hyperglass/hyperglass/configuration/commands.toml`. Formatted as a nested array of tables, each table defines the commands that will be used to execute the queries on the routers.
Each table contains three nested tables:
##### dual
Commands that are IP protocol agnostic:
- `bgp_community`
- `bgp_aspath`
##### ipv4
Commands that are IPv4-specific:
- `bgp_route`
- `ping`
- `traceroute`
##### ipv6
Commands that are IPv6-specific:
- `bgp_route`
- `ping`
- `traceroute`
#### Default Configuration
```toml
[[cisco_ios]]
[cisco_ios.dual]
bgp_community = "show bgp all community {target}"
bgp_aspath = 'show bgp all quote-regexp "{target}"'
[cisco_ios.ipv4]
bgp_route = "show bgp ipv4 unicast {target} | exclude pathid:|Epoch"
ping = "ping {target} repeat 5 source {src_addr_ipv4}"
traceroute = "traceroute {target} timeout 1 probe 2 source {src_addr_ipv4}"
[cisco_ios.ipv6]
bgp_route = "show bgp ipv6 unicast {target} | exclude pathid:|Epoch"
ping = "ping ipv6 {target} repeat 5 source {src_addr_ipv6}"
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
[[cisco_xr]]
[cisco_xr.dual]
bgp_community = 'show bgp all unicast community {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
bgp_aspath = 'show bgp all unicast regexp {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
[cisco_xr.ipv4]
bgp_route = 'show bgp ipv4 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
ping = "ping ipv4 {target} count 5 source {src_addr_ipv4}"
traceroute = "traceroute ipv4 {target} timeout 1 probe 2 source {src_addr_ipv4}"
[cisco_xr.ipv6]
bgp_route = 'show bgp ipv6 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
ping = "ping ipv6 {target} count 5 source {src_addr_ipv6}"
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
[[juniper]]
[juniper.dual]
bgp_community = "show route protocol bgp community {target}"
bgp_aspath = "show route protocol bgp aspath-regex {target}"
[juniper.ipv4]
bgp_route = "show route protocol bgp table inet.0 {target} detail"
ping = "ping inet {target} count 5 source {src_addr_ipv4}"
traceroute = "traceroute inet {target} wait 1 source {src_addr_ipv4}"
[juniper.ipv6]
bgp_route = "show route protocol bgp table inet6.0 {target} detail"
ping = "ping inet6 {target} count 5 source {src_addr_ipv6}"
traceroute = "traceroute inet6 {target} wait 1 source {src_addr_ipv6}"
```
Every attempt has been made to filter out as much "noise" as possible from the command output.
##### `{target}`
Maps to search box input.
##### `{src_addr_ipv4}`
Maps to [src_addr_ipv4](configuration/devices.md/#src_addr_ipv4)
##### `{src_addr_ipv6}`
Maps to [src_addr_ipv6](configuration/devices.md/#src_addr_ipv6)

View File

@@ -1,160 +1,103 @@
Devices/routers are defined in `hyperglass/hyperglass/configuration/devices.toml`. `devices.toml` is effectively an array of hash tables/dictionaries/key value pairs: `devices.toml` is structured as three separate hash table/dictionaries for devices, credentials, and proxies. All values are strings.
# Routers
| Parameter | Function |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **address** | IP address hyperglass will use to connect to the device. |
| **asn** | ASN this device is a member of. |
| **src_addr_ipv4** | Source IPv4 address used for ping and traceroute queries. |
| **src_addr_ipv6** | Source IPv6 address used for ping and traceroute queries. |
| **credential** | Name of credential (username & password) used to authenticate with the device. Credentials are defined as individual tables. See [here](/configuration/authentication.md) for more information on authentication. |
| **location** | Name of location/POP where this device resides. |
| **name** | Hostname of the individual device. |
| **display_name** | Device name that will be shown to the end user on the main hyperglass page. |
| **port** | TCP port for SSH/HTTP connection to device. |
| **type** | Device type/vendor name as recognized by [Netmiko](https://github.com/ktbyers/netmiko). See [supported device types](extras/supported-device-types) for a full list. If using FRRouting and the [hyperglass-frr](https://github.com/checktheroads/hyperglass-frr) API, specify `frr`. |
| **proxy** | Name of SSH proxy/jumpbox, if any, used for connecting to the device. See [here](/configuration/proxy.md) for more information on proxying. If not using a proxy, specify an empty string, i.e. `""`. |
#### Example
```toml ```toml
[[router]] [router.'pop1']
address = "10.0.0.1" address = "192.0.2.1"
asn = "65000" asn = "65000"
src_addr_ipv4 = "192.0.2.1" src_addr_ipv4 = "192.0.2.251"
src_addr_ipv6 = "2001:db8::1" src_addr_ipv6 = "2001:db8::1"
credential = "default" credential = "default"
location = "pop1" location = "pop1"
name = "router1.pop1" name = "router1.pop1"
port = "22" display_name = "Washington, DC"
type = "cisco_xr"
proxy = "jumpbox1"
[[router]]
address = "10.0.0.2"
asn = "65000"
src_addr_ipv4 = "192.0.2.2"
src_addr_ipv6 = "2001:db8::2"
credential = "default"
location = "pop2"
name = "router1.pop2"
port = "22" port = "22"
type = "cisco_ios" type = "cisco_ios"
proxy = "jumpbox2" proxy = "jumpbox1"
[[router]] [router.'pop2']
address = "10.0.0.3" address = "192.0.2.2"
asn = "65000" asn = "65000"
src_addr_ipv4 = "192.0.2.3" src_addr_ipv4 = "192.0.2.252"
src_addr_ipv6 = "2001:db8::3" src_addr_ipv6 = "2001:db8::2"
credential = "default" credential = "frr_api_pop2"
location = "pop3" location = "pop2"
name = "router1.pop3" name = "router1.pop2"
port = "22" display_name = "Portland, OR"
type = "juniper" port = "8080"
proxy = "jumpbox3" type = "frr"
proxy = ""
``` ```
### Device Keys # Credentials
#### address The credential table stores the username and password for a device. SSH Key authentication is not yet supported. If using FRRouting and the [hyperglass-frr](https://github.com/checktheroads/hyperglass-frr) API, the username can be any arbitrary value (it is not used), and the password is the PBKDF2 SHA256 *hashed* API key (**not** the API key itself).
IP address hyperglass will use to connect to the device. #### Example
#### asn ```toml
[credential.'default']
username = "hyperglass"
password = "secret_password"
ASN this device is a member of. [credential.'frr_api_pop2']
username = "doesntmatter"
#### src_addr_ipv4 password = "$pbkdf2-sha256$29000$bI0xJqQUQoixtjZGSAnhvA$FM0oUc.Y3kuvl9ilQmMuULTD1MjzD64Ax9rFNUgAl.c"
Source IPv4 address used for `ping` and `traceroute` queries.
#### src_addr_ipv6
Source IPv6 address used for `ping` and `traceroute` queries.
#### credential
Name of credential (username & password) used to authenticate with the device. Credentials are defined as individual tables. See [here](/configuration/authentication.md) for more information on authentication.
#### location
Name of location/POP where this device resides.
#### name
Display name/hostname of device.
#### port
TCP port for SSH connection to device.
#### type
Device type/vendor name as recognized by [Netmiko](https://github.com/ktbyers/netmiko). See [supported device types](#supported-device-types) for a full list.
#### proxy
Name of SSH proxy/jumpbox, if any, used for connecting to the device. See [here](/configuration/proxy.md) for more information on proxying.
### Supported Device Types
Updated **2019-04-28** from [Netmiko](https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py#L76).
```console
a10
accedian
alcatel_aos
alcatel_sros
apresia_aeos
arista_eos
aruba_os
avaya_ers
avaya_vsp
brocade_fastiron
brocade_netiron
brocade_nos
brocade_vdx
brocade_vyos
checkpoint_gaia
calix_b6
ciena_saos
cisco_asa
cisco_ios
cisco_nxos
cisco_s300
cisco_tp
cisco_wlc
cisco_xe
cisco_xr
coriant
dell_dnos9
dell_force10
dell_os6
dell_os9
dell_os10
dell_powerconnect
dell_isilon
eltex
enterasys
extreme
extreme_ers
extreme_exos
extreme_netiron
extreme_nos
extreme_slx
extreme_vdx
extreme_vsp
extreme_wing
f5_ltm
f5_tmsh
f5_linux
fortinet
generic_termserver
hp_comware
hp_procurve
huawei
huawei_vrpv8
ipinfusion_ocnos
juniper
juniper_junos
linux
mellanox
mrv_optiswitch
netapp_cdot
netscaler
oneaccess_oneos
ovs_linux
paloalto_panos
pluribus
quanta_mesh
rad_etx
ruckus_fastiron
ubiquiti_edge
ubiquiti_edgeswitch
vyatta_vyos
vyos
``` ```
!!! warning "Security Warning"
These values are stored in plain text, so make sure the accounts are restricted. Instructions for creating restricted accounts on common platforms can be found [here](extras/securing-router-access).
# Proxies
The proxy table stores the connection parameters for an SSH proxy.
When a proxy server is defined in the `[router]` table, the defined proxy name is matched to a configured proxy as shown above. When the connection to the device is initiated, the hyperglass server will first initiate an SSH connection to the proxy, and then initiate a second connection to the target device (router) *from* the proxy server. This can be helpful if you want to secure access to your routers.
!!! warning "Security Warning"
These values are stored in plain text, so make sure the accounts are restricted.
| Parameter | Function |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **address** | IP address hyperglass will use to connect to the device. |
| **username** | Username for SSH authentication to the proxy server/jumpbox. SSH Key authentication is not yet supported. |
| **password** | Plain text password for SSH authentication to the proxy server/jumpbox. |
| **type** | Device type/vendor name as recognized by [Netmiko](https://github.com/ktbyers/netmiko). See [supported device types](extras/supported-device-types) for a full list. |
| **ssh_command** | Command used to initiate an SSH connection _from_ the proxy server to the target device. `{username}` will map to the target device (router) username as defined in its associated credential mapping. `{host}` will map to the target device IP address as defined in `devices.toml`. |
#### Example
```toml
[proxy.'jumpbox1']
address = "10.1.1.1"
username = "hyperglass"
password = "secret_password"
type = "linux_ssh"
ssh_command = "ssh -l {username} {host}"
[proxy.'jumpbox2']
address = "10.1.1.2"
username = "hyperglass"
password = "secret_password"
type = "linux_ssh"
ssh_command = "ssh -l {username} {host}"
```
!!! note "Compatibility"
Hyperglass has only been tested with `linux_ssh` as of this writing.

View File

@@ -4,7 +4,7 @@ From `hyperglass/hyperglass/configuration/config.toml`:
| Type | Default Value | | Type | Default Value |
| ------ | ------------- | | ------ | ------------- |
| String | `"65000"` | | String | `"65000"` |
Your network's _primary_ ASN. Number only, e.g. `65000`, **not** `AS65000`. Your network's _primary_ ASN. Number only, e.g. `65000`, **not** `AS65000`.
@@ -12,7 +12,7 @@ Your network's _primary_ ASN. Number only, e.g. `65000`, **not** `AS65000`.
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
| Boolean | `False` | | Boolean | `False` |
Enables Flask debugging. May be used to enable other module debugs in the future. Enables Flask debugging. May be used to enable other module debugs in the future.
@@ -26,8 +26,8 @@ Google Analytics ID number. For more information on how to set up Google Analyti
### message_error ### message_error
| Type | Default Value | | Type | Default Value |
| ------ | --------------------- | | ------ | ----------------------- |
| String | `"{input} is invalid."` | | String | `"{input} is invalid."` |
Message presented to the user when invalid input is detected. `{input}` will be formatted as the input received from the main search field. For each command, input is validated via regular expression in the following patterns: Message presented to the user when invalid input is detected. `{input}` will be formatted as the input received from the main search field. For each command, input is validated via regular expression in the following patterns:
@@ -45,16 +45,16 @@ Message presented to the user when invalid input is detected. `{input}` will be
### message_blacklist ### message_blacklist
| Type | Default Value | | Type | Default Value |
| ------ | ------------------------- | | ------ | --------------------------- |
| String | `"{input} is not allowed."` | | String | `"{input} is not allowed."` |
Message presented to the user when an IPv4 or IPv6 address matches the `blacklist.toml` array. `{input}` will be formatted as the input received from the main search field. For information on how this works, please see the [blacklist documentation](/configuration/blacklist). Message presented to the user when an IPv4 or IPv6 address matches the `blacklist.toml` array. `{input}` will be formatted as the input received from the main search field. For information on how this works, please see the [blacklist documentation](/configuration/blacklist).
### message_rate_limit_query ### message_rate_limit_query
| Type | Default Value | | Type | Default Value |
| ------ | -------------------------------------------------------------------------------------------- | | ------ | ----------------------------------------------------------------------------------------------- |
| String | `"Query limit of {rate_limit_query} per minute reached. Please wait one minute and try again."` | | String | `"Query limit of {rate_limit_query} per minute reached. Please wait one minute and try again."` |
Message presented to the user when the [query limit](#rate_limit_query) is reached. `{rate_limit_query}` will be formatted as the [`rate_limit_query`](#rate_limit_query) parameter. For information on how this works, please see the [rate limiting documentation](/ratelimiting/query). Message presented to the user when the [query limit](#rate_limit_query) is reached. `{rate_limit_query}` will be formatted as the [`rate_limit_query`](#rate_limit_query) parameter. For information on how this works, please see the [rate limiting documentation](/ratelimiting/query).
@@ -63,7 +63,7 @@ Message presented to the user when the [query limit](#rate_limit_query) is reach
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
| Boolean | `True` | | Boolean | `True` |
Enables or disables the BGP Route query type. Enables or disables the BGP Route query type.
@@ -71,7 +71,7 @@ Enables or disables the BGP Route query type.
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
| Boolean | `True` | | Boolean | `True` |
Enables or disables the BGP Community query type. Enables or disables the BGP Community query type.
@@ -79,7 +79,7 @@ Enables or disables the BGP Community query type.
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
| Boolean | `True` | | Boolean | `True` |
Enables or disables the BGP AS Path query type. Enables or disables the BGP AS Path query type.
@@ -87,7 +87,7 @@ Enables or disables the BGP AS Path query type.
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
| Boolean | `True` | | Boolean | `True` |
Enables or disables the Ping query type. Enables or disables the Ping query type.
@@ -95,38 +95,64 @@ Enables or disables the Ping query type.
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------- | ------------- |
| Boolean | `True` | | Boolean | `True` |
Enables or disables the Traceroute query type. Enables or disables the Traceroute query type.
### rate_limit_query ### rate_limit_query
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------ | ------------- |
| String | `"5"` | | String | `"5"` |
Sets the number of queries **per minute** allowed by `remote_address` of the request. For information on how this works, please see the [rate limiting documentation](/ratelimiting/query). Sets the number of queries **per minute** allowed by `remote_address` of the request. For information on how this works, please see the [rate limiting documentation](/ratelimiting/query).
### rate_limit_site ### rate_limit_site
| Type | Default Value | | Type | Default Value |
| ------- | ------------- | | ------ | ------------- |
| String | `"120"` | | String | `"120"` |
Sets the number of site loads **per minute** allowed by `remote_address` of the request. For information on how this works, please see the [rate limiting documentation](/ratelimiting/site). Sets the number of site loads **per minute** allowed by `remote_address` of the request. For information on how this works, please see the [rate limiting documentation](/ratelimiting/site).
### cache_timeout ### cache_timeout
| Type | Default Value | | Type | Default Value |
| -------- | ------------- | | ------- | ------------- |
| Integer | `120` | | Integer | `120` |
Sets the number of **seconds** to cache the back-end response. For information on how this works, please see the [caching documentation](/caching). Sets the number of **seconds** to cache the back-end response. For information on how this works, please see the [caching documentation](/caching).
### cache_directory ### cache_directory
| Type | Default Value | | Type | Default Value |
| -------- | ------------------------------------ | | ------ | -------------------------------------- |
| String | `"hyperglass/hyperglass/.flask_cache"` | | String | `"hyperglass/hyperglass/.flask_cache"` |
Sets the directory where the back-end responses are cached. For information on how this works, please see the [caching documentation](/caching). Sets the directory where the back-end responses are cached. For information on how this works, please see the [caching documentation](/caching).
### enable_max_prefix
| Type | Default Value |
| ------- | ------------- |
| Boolean | `false` |
Enables or disables a maximum allowed prefix size for BGP Route queries. If enabled, the prefix length of BGP Route queries must be shorter than the `max_prefix_length_ipv4` and `max_prefix_length_ipv6` parameters. For example, a BGP Route query for `192.0.2.0/25` would result in the following error message:
<img src="/max_prefix_error.png" style="width: 70%"></img>
### max_prefix_length_ipv4
| Type | Default Value |
| ------- | ------------- |
| Integer | `24` |
If `enable_max_prefix` is enabled, the maxiumum prefix length allowed for IPv4 BGP Route queries.
### max_prefix_length_ipv6
| Type | Default Value |
| ------- | ------------- |
| Integer | `64` |
If `enable_max_prefix` is enabled, the maxiumum prefix length allowed for IPv6 BGP Route queries.

View File

@@ -11,6 +11,98 @@ hyperglass/configuration/
└── requires_ipv6_cidr.toml └── requires_ipv6_cidr.toml
``` ```
## `requires_ipv6_cidr.toml` ## Blacklist
Blacklisted querys are defined in `hyperglass/hyperglass/configuration/blacklist.toml`
The blacklist is a simple TOML array (list) of host IPs or prefixes that you do not want end users to be able to query. For example, if you have one or more hosts/subnets you wish to prevent users from looking up (or any contained host or prefix), add them to the list.
#### Example
```toml
blacklist = [
'198.18.0.0/15',
'2001:db8::/32',
'10.0.0.0/8',
'192.168.0.0/16',
'172.16.0.0/12'
]
```
When users attempt to query a matching host/prefix, they will receive the following error message by default:
<img src="/blacklist_error.png" style="width: 70%"></img>
## Commands
Commands are defined in `hyperglass/hyperglass/configuration/commands.toml`. A table for each NOS (Network Operating System) contains three nested tables: `dual`, `ipv4`, and `ipv6`.
| Table | Function | Commands |
| --------- | ----------------------------- | ------------------------------- |
| **dual** | Protocol agnostic commands | `bgp_community` `bgp_aspath` |
| **ipv4** | IPv4-specific commands | `bgp_route` `ping` `traceroute` |
| **ipv6** | IPv6-specific commands | `bgp_route` `ping` `traceroute` |
#### Variables
The following variables can be used in the command definitions.
- `{target}` Maps to search box input.
- `{src_addr_ipv4}` Maps to [src_addr_ipv4](configuration/devices.md/#src_addr_ipv4)
- `{src_addr_ipv6}` Maps to [src_addr_ipv6](configuration/devices.md/#src_addr_ipv6)
#### Example
```toml
[[cisco_ios]]
[cisco_ios.dual]
bgp_community = "show bgp all community {target}"
bgp_aspath = 'show bgp all quote-regexp "{target}"'
[cisco_ios.ipv4]
bgp_route = "show bgp ipv4 unicast {target} | exclude pathid:|Epoch"
ping = "ping {target} repeat 5 source {src_addr_ipv4}"
traceroute = "traceroute {target} timeout 1 probe 2 source {src_addr_ipv4}"
[cisco_ios.ipv6]
bgp_route = "show bgp ipv6 unicast {target} | exclude pathid:|Epoch"
ping = "ping ipv6 {target} repeat 5 source {src_addr_ipv6}"
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
[[cisco_xr]]
[cisco_xr.dual]
bgp_community = 'show bgp all unicast community {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
bgp_aspath = 'show bgp all unicast regexp {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
[cisco_xr.ipv4]
bgp_route = 'show bgp ipv4 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
ping = "ping ipv4 {target} count 5 source {src_addr_ipv4}"
traceroute = "traceroute ipv4 {target} timeout 1 probe 2 source {src_addr_ipv4}"
[cisco_xr.ipv6]
bgp_route = 'show bgp ipv6 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
ping = "ping ipv6 {target} count 5 source {src_addr_ipv6}"
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
[[juniper]]
[juniper.dual]
bgp_community = "show route protocol bgp community {target}"
bgp_aspath = "show route protocol bgp aspath-regex {target}"
[juniper.ipv4]
bgp_route = "show route protocol bgp table inet.0 {target} detail"
ping = "ping inet {target} count 5 source {src_addr_ipv4}"
traceroute = "traceroute inet {target} wait 1 source {src_addr_ipv4}"
[juniper.ipv6]
bgp_route = "show route protocol bgp table inet6.0 {target} detail"
ping = "ping inet6 {target} count 5 source {src_addr_ipv6}"
traceroute = "traceroute inet6 {target} wait 1 source {src_addr_ipv6}"
```
## IPv6 CIDR Format Required
Some platforms (namely Cisco IOS) are unable to perform a BGP lookup by IPv6 host address (e.g. 2001:db8::1), but must perform the lookup by prefix (e.g. 2001:db8::/48). `requires_ipv6_cidr.toml` is a list (TOML array) of network operating systems that require this (in Netmiko format). Some platforms (namely Cisco IOS) are unable to perform a BGP lookup by IPv6 host address (e.g. 2001:db8::1), but must perform the lookup by prefix (e.g. 2001:db8::/48). `requires_ipv6_cidr.toml` is a list (TOML array) of network operating systems that require this (in Netmiko format).
#### Example
```toml
requires_ipv6_cidr = [
"cisco_ios",
"cisco_nxos"
]
```

View File

@@ -1,45 +0,0 @@
Proxy servers are defined in `hyperglass/hyperglass/configuration/devices.toml`. Each proxy definition is a unique TOML table, for example:
```toml
[proxy.'jumpbox1']
address = "10.1.1.1"
username = "hyperglass"
password = "secret_password"
type = "linux_ssh"
ssh_command = "ssh -l {username} {host}"
[proxy.'jumpbox2']
address = "10.1.1.2"
username = "hyperglass"
password = "secret_password"
type = "linux_ssh"
ssh_command = "ssh -l {username} {host}"
```
When a proxy server is defined under the `[[router]]` heading in `devices.toml`, the defined proxy name is matched to a configured proxy as shown above. When the connection to the device is initiated, the hyperglass server will first initiate an SSH connection to the proxy, and then initiate a second connection to the target device (router) *from* the proxy server. This can be helpful if you want to secure access to your routers.
#### address
IP address hyperglass will use to connect to the device.
#### username
Username for SSH authentication to the proxy server/jumpbox. SSH Key authentication is not yet supported.
#### password
Plain text password for SSH authentication to the proxy server/jumpbox.
!!! warning "Security Warning"
These values are stored in plain text. Make sure the accounts are restricted and that the configuration file is stored in a secure location.
#### type
Device type/vendor name as recognized by [Netmiko](https://github.com/ktbyers/netmiko). See [supported device types](#supported-device-types) for a full list.
!!! note "Compatibility"
Hyperglass has only been tested with `linux_ssh` as of this writing.
#### ssh_command
Command used to initiate an SSH connection *from* the proxy server to the target device. `{username}` will map to the target device (router) username as defined in its associated credential mapping. `{host}` will map to the target device IP address as defined in `devices.toml`.

View File

@@ -1,8 +1,6 @@
More than likely, you'll want to "lock down" what commands can be executed with the credentials you've provided in `hyperglass/hyperglass/configuration/devices.toml`. It is **strongly** recommended to use a low privilege read only account and not your full administrator account. Even though Hyperglass is coded to only run certain commands to begin with, you're more than likely still exposing the server Hyperglass runs on to the internet, and on that server is a plain text file with your router's credentials in it. Take precautions. More than likely, you'll want to "lock down" what commands can be executed with the credentials you've provided in `hyperglass/hyperglass/configuration/devices.toml`. It is **strongly** recommended to use a low privilege read only account and not your full administrator account. Even though Hyperglass is coded to only run certain commands to begin with, you're more than likely still exposing the server Hyperglass runs on to the internet, and on that server is a plain text file with your router's credentials in it. Take precautions.
# Creating Restricted Accounts # Cisco IOS
## Cisco IOS
On Cisco IOS, **parser views** are the recommended tool to restrict access. Basic instructions for configuring Cisco IOS parser views for the default enabled query types are below: On Cisco IOS, **parser views** are the recommended tool to restrict access. Basic instructions for configuring Cisco IOS parser views for the default enabled query types are below:
@@ -21,7 +19,7 @@ username hyperglass privilege 15 view hyperglass secret <secret>
!!! note "Terminal" !!! note "Terminal"
The `terminal length` and `terminal width` commands are required by Netmiko for session handling. If you remove these, Hyperglass will not work. The `terminal length` and `terminal width` commands are required by Netmiko for session handling. If you remove these, Hyperglass will not work.
## Cisco IOS-XR # Cisco IOS-XR
On Cisco IOS-XR, **taskgroups** are the recommended tool to restrict access. Basic instructoins for configuring Cisco IOS-XR taskgroups for the default enabled query types are below: On Cisco IOS-XR, **taskgroups** are the recommended tool to restrict access. Basic instructoins for configuring Cisco IOS-XR taskgroups for the default enabled query types are below:
@@ -42,7 +40,7 @@ username hyperglass
!!! warning "IOS-XR" !!! warning "IOS-XR"
I have not yet figured out a way to enable all the extended options for `ping` and `traceroute` (source IP, count, etc.) without adding the `group operator` statement to the taskgroup. If anyone knows of a way to do this, I welcome a docs PR. I have not yet figured out a way to enable all the extended options for `ping` and `traceroute` (source IP, count, etc.) without adding the `group operator` statement to the taskgroup. If anyone knows of a way to do this, I welcome a docs PR.
## Juniper # Juniper
On JunOS, **system login classes** are the recommended tool to restrict access. Basic instructoins for configuring Juniper JunOS login classes for the default enabled query types are below: On JunOS, **system login classes** are the recommended tool to restrict access. Basic instructoins for configuring Juniper JunOS login classes for the default enabled query types are below:

View File

@@ -0,0 +1,82 @@
# HTTP API
- FRRouting via [hyperglass-frr](https://github.com/checktheroads/hyperglass-frr) API.
# Netmiko
Updated **2019-04-28** from [Netmiko](https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py#L76).
```console
a10
accedian
alcatel_aos
alcatel_sros
apresia_aeos
arista_eos
aruba_os
avaya_ers
avaya_vsp
brocade_fastiron
brocade_netiron
brocade_nos
brocade_vdx
brocade_vyos
checkpoint_gaia
calix_b6
ciena_saos
cisco_asa
cisco_ios
cisco_nxos
cisco_s300
cisco_tp
cisco_wlc
cisco_xe
cisco_xr
coriant
dell_dnos9
dell_force10
dell_os6
dell_os9
dell_os10
dell_powerconnect
dell_isilon
eltex
enterasys
extreme
extreme_ers
extreme_exos
extreme_netiron
extreme_nos
extreme_slx
extreme_vdx
extreme_vsp
extreme_wing
f5_ltm
f5_tmsh
f5_linux
fortinet
generic_termserver
hp_comware
hp_procurve
huawei
huawei_vrpv8
ipinfusion_ocnos
juniper
juniper_junos
linux
mellanox
mrv_optiswitch
netapp_cdot
netscaler
oneaccess_oneos
ovs_linux
paloalto_panos
pluribus
quanta_mesh
rad_etx
ruckus_fastiron
ubiquiti_edge
ubiquiti_edgeswitch
vyatta_vyos
vyos
```

View File

@@ -1,9 +1,11 @@
# Download # Download
## System Requirements #### System Requirements
!!! warning "Compatibility" !!! warning "Compatibility"
To date, Hyperglass has only been installed tested on Mac OS X 10.14 and Ubuntu Linux 18.04. Installation instructions are specific to Ubuntu 18.04. Installation instructions for additional operating systems are forthcoming (contribution welcome!). To date, Hyperglass has only been installed tested on Ubuntu Linux 18.04, and was developed on macOS 10.14. Installation instructions are specific to Ubuntu 18.04. Installation instructions for additional operating systems are forthcoming (contribution welcome!).
#### OS Dependencies
Hyperglass is written and tested on Python 3.7, but should be backwards compatible with any Python 3 version (albeit untested). If needed, install Python 3 and PyPi 3 on your system: Hyperglass is written and tested on Python 3.7, but should be backwards compatible with any Python 3 version (albeit untested). If needed, install Python 3 and PyPi 3 on your system:
@@ -11,34 +13,38 @@ Hyperglass is written and tested on Python 3.7, but should be backwards compatib
# apt install -y python3 python3-pip # apt install -y python3 python3-pip
``` ```
## Clone the repository #### Clone the repository
```console ```console
$ cd /opt/ $ cd /opt/
$ git clone https://github.com/checktheroads/hyperglass $ git clone https://github.com/checktheroads/hyperglass
``` ```
## Install Required Python Modules # Install
#### Python Dependencies
```console ```console
$ cd /opt/hyperglass/hyperglass $ cd /opt/hyperglass/
$ pip3 install -r requirements.txt $ pip3 install -r requirements.txt
``` ```
## Clone Example Configuration Files #### Migrate Configuration Files
``` ```console
$ cd /opt/hyperglass/hyperglass/configuration/ $ cd /opt/hyperglass/
$ for f in *.example; do cp $f `basename $f .example`; done; $ python3 manage.py migrateconfig
``` ```
## Test the Application All `*.example` files in `hyperglass/hyperglass/configuration/` will be copied to `.toml` extension for use by hyperglass. This is a non-destructive copy, so if you already have `*.toml` files in this directory, they will *not* be overwritten.
# Test
At this stage, Hyperglass should be able to start up with the built-in Flask development server. This will be enough to verify that the application itself can run, and provie a means to test branding customizations, router connectivity, etc., prior to placing a production-grade WSGI & web server in front of Hyperglass. At this stage, Hyperglass should be able to start up with the built-in Flask development server. This will be enough to verify that the application itself can run, and provie a means to test branding customizations, router connectivity, etc., prior to placing a production-grade WSGI & web server in front of Hyperglass.
```console ```console
$ cd /opt/hyperglass/hyperglass/ $ cd /opt/hyperglass/
$ python3 app.py $ python3 manage.py testserver
``` ```
You should now be able to access hyperglass by loading the name or IP on port 5000 in a web browser, for example: `http://10.0.0.1:5000`. Note that the Flask development server is **not** suited for production use. This will simply verify that the application and dependencies have been correctly installed. Production deployment will be covered in the next sections. You should now be able to access hyperglass by loading the name or IP on port 5000 in a web browser, for example: `http://10.0.0.1:5000`. Note that the Flask development server is **not** suited for production use. This will simply verify that the application and dependencies have been correctly installed. Production deployment will be covered in the next sections.

View File

@@ -0,0 +1,79 @@
More than likely, you'll be exposing Hyperglass to the internet. It is recommended practice to run most web applications behind a reverse proxy, such as Nginx, Apache, Caddy, etc. This example uses Nginx, but can easily be adapted to other reverse proxy applications if you prefer.
#### Example
The below Nginx example assumes the default [Gunicorn](installation/wsgi) settings are used.
```nginx
server {
listen 80;
listen [::]:80ipv6only=on;
client_max_body_size 1024;
server_name lg.domain.tld;
location /static/ {
alias /opt/hyperglass/hyperglass/static/;
}
location / {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://[::1]:8001;
}
}
```
This configuration, in combination with the default Gunicorn configuration, makes the hyperglass front-end dual stack IPv4/IPv6 capable. To add SSL support, Nginx can be easily adjusted to terminate front-end SSL connections:
```nginx
server {
listen 80;
listen [::]:80;
server_name lg.domain.tld;
return 301 https://$host$request_uri;
}
server {
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate <path to certificate>;
ssl_certificate_key <path to private key>;
client_max_body_size 1024;
server_name lg.domain.tld;
location /static/ {
alias /opt/hyperglass/hyperglass/static/;
}
location / {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://[::1]:8001;
}
}
```
[Let's Encrypt](https://letsencrypt.org/) provides automatic (and free) SSL certificate generation and renewal. There are a number of guides available on how to integrate Let's Encrypt with Nginx (or your reverse proxy of choice). Some examples:
- Digital Ocean: [How To Secure Nginx with Let's Encrypt on Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04)
- NGINX: [Using Free Lets Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/)

View File

@@ -0,0 +1,26 @@
More than likely, you'll want to run Hyperglass as a service so that it automatically starts on server boot. Any service manager can be used, however Ubuntu `systemd` instructions are included as a reference.
For easy installation, migrate the example `systemd` service:
```console
$ cd /opt/hyperglass/
$ python3 manage.py migratesystemd
```
This copies the example systemd service to `/etc/systemd/system/hyperglass.service`
#### Example
```ini
[Unit]
Description=Hyperglass
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/hyperglass
ExecStart=/usr/local/bin/gunicorn -c /opt/hyperglass/hyperglass/gunicorn_config.py hyperglass.wsgi
[Install]
WantedBy=multi-user.target
```

View File

@@ -6,45 +6,38 @@ Gunicorn is a WSGI server written in Python.
## Install ## Install
```console ```console
# pip3 install gunicorn $ pip3 install gunicorn
``` ```
## Configure ## Configure
Locate your `gunicorn` executable with `which gunicorn`. Migrate the example Gunicorn configuration file:
```console
$ cd /opt/hyperglass/
$ python3 manage.py migrategunicorn
```
Open `hyperglass/gunicorn_config.py`, and adjust the parameters to match your local system. For example, make sure the `command` parameter matches the location of your `gunicorn` executable (`which gunicorn`), the `pythonpath` parameter matches the location where hyperglass is installed, and that the `user` parameter matches the user you're running hyperglass as:
```python
import multiprocessing
command = "/usr/local/bin/gunicorn"
pythonpath = "/opt/hyperglass/hyperglass"
bind = "[::1]:8001"
workers = multiprocessing.cpu_count() * 2
user = "www-data"
timeout = 60
```
### Permissions ### Permissions
Gunicorn requires read/write/executable access to the entire `hyperglass/hyperglass` directory in order to read its configuration and execute the python code. If running gunicorn as www-data, fix permissions with: Gunicorn requires read/write/executable access to the entire `hyperglass/hyperglass` directory in order to read its configuration and execute the python code. If running gunicorn as `www-data`, fix permissions with:
```console ```console
# chown -R www-data:www-data /opt/hyperglass/hyperglass # cd /opt/hyperglass/
# chmod -R 744 /opt/hyperglass/hyperglass # python3 manage.py fixpermissions --user <user> --group <group>
``` ```
<!-- # Supervisor Installation !!! note "File Ownership"
If the `--user` and `--group` options are not specified, `www-data` will be used.
To make cross-platform service functionality easier, it is recommended to use [`supervisord`](http://supervisord.org/) to manage the Hyperglass application. If you prefer, `systemd` or your service manager of choice may be used.
Install supervisord:
```console
# apt install -y supervisor
```
Create supervisord configuration for Hyperglass:
```console
# nano /etc/supervisor/conf.d/hyperglass.conf
[program:hyperglass]
command = /usr/local/bin/gunicorn -c /opt/hyperglass/hyperglass/gunicorn_config.py hyperglass.wsgi
directory = /opt/hyperglass/
user = www-data
```
Start supervisord:
```console
# systemctl start supervisor
# systemctl status supervisor
``` -->

BIN
docs/max_prefix_error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/requires_ipv6_cidr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

0
file.test Normal file
View File

View File

@@ -39,7 +39,7 @@ def frr(cmd, ipprefix, device):
msg = f"{ipprefix} matched large community." msg = f"{ipprefix} matched large community."
return (msg, code.success, d_address, query) return (msg, code.success, d_address, query)
else: else:
msg = f"{ipprefix} is an invalid BGP Community Format." msg = f"<b>{ipprefix}</b> is an invalid BGP Community Format."
logger.error(f"{msg}, {code.danger}, {d_name}, {query}") logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
return (msg, code.danger, d_address, query) return (msg, code.danger, d_address, query)
# BGP AS_PATH Query # BGP AS_PATH Query
@@ -49,7 +49,7 @@ def frr(cmd, ipprefix, device):
msg = f"{ipprefix} matched AS_PATH regex." msg = f"{ipprefix} matched AS_PATH regex."
return (msg, code.success, d_address, query) return (msg, code.success, d_address, query)
else: else:
msg = f"{ipprefix} is an invalid AS_PATH regex." msg = f"<b>{ipprefix}</b> is an invalid AS_PATH regex."
logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}") logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
return (msg, code.danger, d_address, query) return (msg, code.danger, d_address, query)
# BGP Route Query # BGP Route Query
@@ -67,7 +67,7 @@ def frr(cmd, ipprefix, device):
return (msg, code.success, d_address, query) return (msg, code.success, d_address, query)
# Exception from netaddr library will return a user-facing error # Exception from netaddr library will return a user-facing error
except: except:
msg = f"{ipprefix} is an invalid IP Address." msg = f"<b>{ipprefix}</b> is an invalid IP Address."
logger.error(f"{msg}, {code.danger}, {d_name}, {query}") logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
return (msg, code.danger, d_address, query) return (msg, code.danger, d_address, query)
# Ping/Traceroute # Ping/Traceroute
@@ -93,10 +93,10 @@ def frr(cmd, ipprefix, device):
"target": ipprefix, "target": ipprefix,
} }
) )
msg = f"{ipprefix} is a valid IPv6 Adddress." msg = f"<b>{ipprefix}</b> is a valid IPv6 Adddress."
return (msg, code.success, d_address, query) return (msg, code.success, d_address, query)
except: except:
msg = f"{ipprefix} is an invalid IP Address." msg = f"<b>{ipprefix}</b> is an invalid IP Address."
logger.error(f"{msg}, {code.danger}, {d_name}, {query}") logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
return (msg, code.danger, d_name, query) return (msg, code.danger, d_name, query)
else: else:
@@ -137,7 +137,7 @@ def ssh(cmd, ipprefix, device):
msg = f"{ipprefix} matched large community." msg = f"{ipprefix} matched large community."
return (msg, code.success, d_address, d_type, command) return (msg, code.success, d_address, d_type, command)
else: else:
msg = f"{ipprefix} is an invalid BGP Community Format." msg = f"<b>{ipprefix}</b> is an invalid BGP Community Format."
logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}") logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
return (msg, code.danger, d_name, cmd, ipprefix) return (msg, code.danger, d_name, cmd, ipprefix)
# BGP AS_PATH Query # BGP AS_PATH Query
@@ -148,7 +148,7 @@ def ssh(cmd, ipprefix, device):
msg = f"{ipprefix} matched AS_PATH regex." msg = f"{ipprefix} matched AS_PATH regex."
return (msg, code.success, d_address, d_type, command) return (msg, code.success, d_address, d_type, command)
else: else:
msg = f"{ipprefix} is an invalid AS_PATH regex." msg = f"<b>{ipprefix}</b> is an invalid AS_PATH regex."
logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}") logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
return (msg, code.danger, d_name, cmd, ipprefix) return (msg, code.danger, d_name, cmd, ipprefix)
# BGP Route Query # BGP Route Query
@@ -168,7 +168,7 @@ def ssh(cmd, ipprefix, device):
return (msg, code.success, d_address, d_type, command) return (msg, code.success, d_address, d_type, command)
# Exception from netaddr library will return a user-facing error # Exception from netaddr library will return a user-facing error
except: except:
msg = f"{ipprefix} is an invalid IP Address." msg = f"<b>{ipprefix}</b> is an invalid IP Address."
logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}") logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
return (msg, code.danger, d_name, cmd, ipprefix) return (msg, code.danger, d_name, cmd, ipprefix)
# Ping/Traceroute # Ping/Traceroute
@@ -185,7 +185,7 @@ def ssh(cmd, ipprefix, device):
msg = f"{ipprefix} is a valid IPv6 Adddress." msg = f"{ipprefix} is a valid IPv6 Adddress."
return (msg, code.success, d_address, d_type, command) return (msg, code.success, d_address, d_type, command)
except: except:
msg = f"{ipprefix} is an invalid IP Address." msg = f"<b>{ipprefix}</b> is an invalid IP Address."
logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}") logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
return (msg, code.danger, d_name, cmd, ipprefix) return (msg, code.danger, d_name, cmd, ipprefix)
else: else:

View File

@@ -1,4 +1,5 @@
# Module Imports # Module Imports
import re
import sys import sys
import json import json
import time import time
@@ -14,6 +15,27 @@ from hyperglass.command import parse
from hyperglass.command import construct from hyperglass.command import construct
class ipcheck:
def __init__(self):
self.ipv4_host = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)?$"
self.ipv4_cidr = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|2[0-9]|1[0-9]|[0-9])?$"
self.ipv6_host = "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))?$"
self.ipv6_cidr = "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/((1(1[0-9]|2[0-8]))|([0-9][0-9])|([0-9]))?$"
def test(self, prefix):
if IPNetwork(prefix).ip.version == 4:
if re.match(self.ipv4_host, prefix):
return {"protocol": "ipv4", "type": "host"}
elif re.match(self.ipv4_cidr, prefix):
return {"protocol": "ipv4", "type": "cidr"}
if IPNetwork(prefix).ip.version == 6:
if re.match(self.ipv6_host, prefix):
return {"protocol": "ipv6", "type": "host"}
if re.match(self.ipv6_cidr, prefix):
return {"protocol": "ipv6", "type": "cidr"}
class params: class params:
"""Sends input parameters to construct module for use by execution functions""" """Sends input parameters to construct module for use by execution functions"""
@@ -124,7 +146,9 @@ class connect:
def execute(lg_data): def execute(lg_data):
"""Ingests user input, runs blacklist check, runs prefix length check (if enabled), """Ingests user input, runs blacklist check, runs prefix length check (if enabled),
pulls all configuraiton variables for the input router.""" pulls all configuraiton variables for the input router."""
logger.info(f"Received lookup request for: {lg_data}") logger.info(f"Received lookup request for: {lg_data}")
# Create global variables for POSTed JSON from main app # Create global variables for POSTed JSON from main app
global lg_router global lg_router
lg_router = lg_data["router"] lg_router = lg_data["router"]
@@ -138,50 +162,79 @@ def execute(lg_data):
global lg_params global lg_params
lg_params = lg_data lg_params = lg_data
# Initialize general configuration parameters class, create global variable for reuse.
global general
general = configuration.general()
# Initialize status code class, create global variable for reuse. # Initialize status code class, create global variable for reuse.
global code global code
code = configuration.codes() code = configuration.codes()
# Check blacklist list for prefixes/IPs and return an error upon a match # Validate prefix input with netaddr library
if lg_cmd in ["bgp_route", "ping", "traceroute"]: if lg_cmd in ["bgp_route", "ping", "traceroute"]:
# Initialize prefix regex check class
ipc = ipcheck().test(lg_ipprefix)
try: try:
blacklist = IPSet(configuration.blacklist()) if IPNetwork(lg_ipprefix).ip.is_reserved():
if IPNetwork(lg_ipprefix).ip in blacklist: msg = f"<b>{lg_ipprefix}</b> is not a valid IP address."
msg = f"{lg_ipprefix} is not allowed." return (msg, code.danger, lg_data)
return (msg, code.warning, lg_data) elif IPNetwork(lg_ipprefix).ip.is_netmask():
# If netaddr library throws an exception, return a user-facing error. msg = f"<b>{lg_ipprefix}</b> is not a valid IP address."
return (msg, code.danger, lg_data)
elif IPNetwork(lg_ipprefix).ip.is_hostmask():
msg = f"<b>{lg_ipprefix}</b> is not a valid IP address."
return (msg, code.danger, lg_data)
elif IPNetwork(lg_ipprefix).ip.is_loopback():
msg = f"<b>{lg_ipprefix}</b> is not a valid IP address."
return (msg, code.danger, lg_data)
elif IPNetwork(lg_ipprefix).ip.is_unicast():
pass
else:
msg = f"<b>{lg_ipprefix}</b> is not a valid unicast IP address."
return (msg, code.danger, lg_data)
except: except:
msg = f"{lg_ipprefix} is not a valid IP Address." msg = f"<b>{lg_ipprefix}</b> is not a valid IP Address."
return (msg, code.danger, lg_data) return (msg, code.danger, lg_data)
# If enable_max_prefix feature enabled, require BGP Route queries be smaller than prefix size limit
if lg_cmd == "bgp_route" and general.enable_max_prefix == True: if lg_cmd == "Query Type":
try:
if (
IPNetwork(lg_ipprefix).version == 4
and IPNetwork(lg_ipprefix).prefixlen > general.max_prefix_length_ipv4
):
msg = f"Prefix length must be smaller than /{general.max_prefix_length_ipv4}. {IPNetwork(lg_ipprefix)} is too specific."
return (msg, code.warning, lg_data)
if (
IPNetwork(lg_ipprefix).version == 6
and IPNetwork(lg_ipprefix).prefixlen > general.max_prefix_length_ipv6
):
msg = f"Prefix length must be smaller than /{general.max_prefix_length_ipv4}. {IPNetwork(lg_ipprefix)} is too specific."
return (msg, code.warning, lg_data)
except:
raise
elif lg_cmd == "Query Type":
msg = "You must select a query type." msg = "You must select a query type."
logger.error(f"{msg}, {code.danger}, {lg_data}") return (msg, code.warning, lg_data)
return (msg, code.danger, lg_data)
# Initialize general configuration parameters class, create global variable for reuse.
global general
general = configuration.general()
global d global d
d = configuration.device(lg_router) d = configuration.device(lg_router)
# Checks if device type is on the requires_ipv6_cidr list
requires_ipv6_cidr = configuration.requires_ipv6_cidr(d.type)
# Check blacklist list for prefixes/IPs and return an error upon a match
if lg_cmd in ["bgp_route", "ping", "traceroute"]:
blacklist = IPSet(configuration.blacklist())
if IPNetwork(lg_ipprefix).ip in blacklist:
msg = f"<b>{lg_ipprefix}</b> is not allowed."
return (msg, code.warning, lg_data)
if lg_cmd == "bgp_route" and IPNetwork(lg_ipprefix).version == 6:
if requires_ipv6_cidr == True and ipc["type"] == "host":
msg = f"<b>{d.display_name}</b> requires IPv6 BGP lookups to be in CIDR notation."
return (msg, code.warning, lg_data)
if lg_cmd in ["ping", "traceroute"] and ipc["type"] == "cidr":
msg = f"<code>{lg_cmd}</code> does not allow networks masks."
return (msg, code.warning, lg_data)
# If enable_max_prefix feature enabled, require BGP Route queries be smaller than prefix size limit
if lg_cmd == "bgp_route" and general.enable_max_prefix == True:
if (
IPNetwork(lg_ipprefix).version == 4
and IPNetwork(lg_ipprefix).prefixlen > general.max_prefix_length_ipv4
):
msg = f"Prefix length must be smaller than /{general.max_prefix_length_ipv4}. <b>{IPNetwork(lg_ipprefix)}</b> is too specific."
return (msg, code.warning, lg_data)
if (
IPNetwork(lg_ipprefix).version == 6
and IPNetwork(lg_ipprefix).prefixlen > general.max_prefix_length_ipv6
):
msg = f"Prefix length must be smaller than /{general.max_prefix_length_ipv4}. <b>{IPNetwork(lg_ipprefix)}</b> is too specific."
return (msg, code.warning, lg_data)
if d.type == "frr": if d.type == "frr":
http = params().http() http = params().http()
try: try:

View File

@@ -165,7 +165,7 @@ class general:
) )
self.enable_max_prefix = g.get("enable_max_prefix", False) self.enable_max_prefix = g.get("enable_max_prefix", False)
self.max_prefix_length_ipv4 = g.get("max_prefix_length_ipv4", 24) self.max_prefix_length_ipv4 = g.get("max_prefix_length_ipv4", 24)
self.max_prefix_length_ipv6 = g.get("max_prefix_length_ipv6", 29) self.max_prefix_length_ipv6 = g.get("max_prefix_length_ipv6", 64)
class branding: class branding:
@@ -223,9 +223,9 @@ class branding:
"text_limiter_subtitle", "text_limiter_subtitle",
f"You have accessed this site more than {general().rate_limit_site} times in the last minute.", f"You have accessed this site more than {general().rate_limit_site} times in the last minute.",
) )
self.text_415_title = b.get("text_415_title", "Error") self.text_500_title = b.get("text_500_title", "Error")
self.text_415_subtitle = b.get("text_415_subtitle", "Something went wrong.") self.text_500_subtitle = b.get("text_500_subtitle", "Something went wrong.")
self.text_415_button = b.get("text_415_button", "Home") self.text_500_button = b.get("text_500_button", "Home")
self.text_help_bgp_route = b.get( self.text_help_bgp_route = b.get(
"text_help_bgp_route", "text_help_bgp_route",
"Performs BGP table lookup based on IPv4/IPv6 prefix.", "Performs BGP table lookup based on IPv4/IPv6 prefix.",

View File

@@ -44,9 +44,9 @@ text_location = ""
text_cache = "" text_cache = ""
text_limiter_title = "" text_limiter_title = ""
text_limiter_subtitle = "" text_limiter_subtitle = ""
text_415_title = "" text_500_title = ""
text_415_subtitle = "" text_500_subtitle = ""
text_415_button = "" text_500_button = ""
text_help_bgp_route = "" text_help_bgp_route = ""
text_help_bgp_community = "" text_help_bgp_community = ""
text_help_bgp_aspath = "" text_help_bgp_aspath = ""

0
hyperglass/file.test Executable file
View File

View File

@@ -3,6 +3,6 @@ import multiprocessing
command = "/usr/local/bin/gunicorn" command = "/usr/local/bin/gunicorn"
pythonpath = "/opt/hyperglass/hyperglass" pythonpath = "/opt/hyperglass/hyperglass"
bind = "[::1]:8001" bind = "[::1]:8001"
workers = 1 # multiprocessing.cpu_count() * 2 workers = multiprocessing.cpu_count() * 2
user = "www-data" user = "www-data"
timeout = 60 timeout = 60

View File

@@ -11,7 +11,7 @@ from flask_caching import Cache
from flask_limiter import Limiter from flask_limiter import Limiter
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
# Local Imports # Project Imports
import hyperglass.configuration as configuration import hyperglass.configuration as configuration
from hyperglass.command import execute from hyperglass.command import execute
from hyperglass import render from hyperglass import render
@@ -19,44 +19,14 @@ from hyperglass import render
# Main Flask definition # Main Flask definition
app = Flask(__name__, static_url_path="/static") app = Flask(__name__, static_url_path="/static")
# Initialize general configuration parameters for reuse
general = configuration.general() general = configuration.general()
# Flask-Limiter Config # Flask-Limiter Config
rate_limit_query = f"{general.rate_limit_query} per minute" rate_limit_query = f"{general.rate_limit_query} per minute"
rate_limit_site = f"{general.rate_limit_site} per minute" rate_limit_site = f"{general.rate_limit_site} per minute"
limiter = Limiter(app, key_func=get_remote_address, default_limits=[rate_limit_site]) limiter = Limiter(app, key_func=get_remote_address, default_limits=[rate_limit_site])
def renderCSS():
try:
render.css.renderTemplate()
except:
raise
# Render Main Flask-Limiter Error Message
@app.errorhandler(429)
def error429(e):
"""Renders full error page for too many site queries"""
html = render.html.renderTemplate("429")
return html, 429
def error415():
"""Renders full error page for generic errors"""
html = render.html.renderTemplate("415")
return html, 415
def errorQuery():
"""Renders modal error message"""
return 429
def errorGeneral(id):
"""Renders notification error message with an ID number"""
return "An unknown error occurred." + "\s" + id, 415
# Flask-Caching Config # Flask-Caching Config
cache = Cache( cache = Cache(
app, app,
@@ -68,6 +38,19 @@ cache = Cache(
) )
@app.errorhandler(429)
def error429(e):
"""Renders full error page for too many site queries"""
html = render.html.renderTemplate("429")
return html, 429
def error500():
"""Renders full error page for generic errors"""
html = render.html.renderTemplate("500")
return html, 500
def clearCache(): def clearCache():
"""Function to clear the Flask-Caching cache""" """Function to clear the Flask-Caching cache"""
with app.app_context(): with app.app_context():
@@ -77,7 +60,6 @@ def clearCache():
raise raise
# Main / Flask route where html is rendered via Jinja2
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])
@limiter.limit(rate_limit_site) @limiter.limit(rate_limit_site)
def site(): def site():
@@ -86,27 +68,26 @@ def site():
return html return html
# Test route for various tests
@app.route("/test", methods=["GET"]) @app.route("/test", methods=["GET"])
def testRoute(): def testRoute():
html = render.html.renderTemplate("test") """Test route for various tests"""
html = render.html.renderTemplate("500")
return html return html
# Flask GET route provides a JSON list of all routers for the selected network/ASN
@app.route("/routers/<asn>", methods=["GET"]) @app.route("/routers/<asn>", methods=["GET"])
def get_routers(asn): def get_routers(asn):
"""Flask GET route provides a JSON list of all routers for the selected network/ASN"""
nl = configuration.networks_list() nl = configuration.networks_list()
nl_json = json.dumps(nl[asn]) nl_json = json.dumps(nl[asn])
return nl_json return nl_json
# Flask POST route ingests data from the JS form submit, passes it to the backend looking glass application to perform the filtering/lookups
@app.route("/lg", methods=["POST"]) @app.route("/lg", methods=["POST"])
# Invoke Flask-Limiter with configured rate limit # Invoke Flask-Limiter with configured rate limit
@limiter.limit(rate_limit_query) @limiter.limit(rate_limit_query)
def lg(): def lg():
"""Main backend application initiator""" """Main backend application initiator. Ingests Ajax POST data from form submit, passes it to the backend application to perform the filtering/lookups"""
lg_data = request.get_json() lg_data = request.get_json()
# Stringify the form response containing serialized JSON for the request, use as key for k/v cache store so each command output value is unique # Stringify the form response containing serialized JSON for the request, use as key for k/v cache store so each command output value is unique
cache_key = str(lg_data) cache_key = str(lg_data)
@@ -133,11 +114,17 @@ def lg():
except: except:
raise raise
# If 400 error, return error message and code # If 400 error, return error message and code
# 200 & 400 errors are separated mainly for potential future use
elif value_code in [405, 415]: elif value_code in [405, 415]:
try: try:
return Response(response[0], response[1]) return Response(response[0], response[1])
except: except:
raise raise
elif value_code in [500]:
try:
return Response(error500(), value_code)
except:
raise
# If it does, return the cached entry # If it does, return the cached entry
else: else:
logger.info(f"Cache match for: {cache_key}, returning cached entry...") logger.info(f"Cache match for: {cache_key}, returning cached entry...")
@@ -147,5 +134,5 @@ def lg():
except: except:
raise raise
# Upon exception, render generic error # Upon exception, render generic error
log.error(f"Error returning cached entry for: {cache_key}") logger.error(f"Error returning cached entry for: {cache_key}")
return Response(errorGeneral(4152)) return Response(error500())

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Hyperglass
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/hyperglass
ExecStart=/usr/local/bin/gunicorn -c /opt/hyperglass/hyperglass/gunicorn_config.py hyperglass.wsgi
[Install]
WantedBy=multi-user.target

View File

@@ -36,10 +36,8 @@ class html:
template = env.get_template("templates/index.html") template = env.get_template("templates/index.html")
elif t == "429": elif t == "429":
template = env.get_template("templates/429.html") template = env.get_template("templates/429.html")
elif t == "415": elif t == "500":
template = env.get_template("templates/415.html") template = env.get_template("templates/500.html")
elif t == "test":
template = env.get_template("templates/429.html")
return template.render( return template.render(
# General # General
primary_asn=general.primary_asn, primary_asn=general.primary_asn,
@@ -77,9 +75,9 @@ class html:
text_results=branding.text_results, text_results=branding.text_results,
text_location=branding.text_location, text_location=branding.text_location,
text_cache=branding.text_cache, text_cache=branding.text_cache,
text_415_title=branding.text_415_title, text_500_title=branding.text_500_title,
text_415_subtitle=branding.text_415_subtitle, text_500_subtitle=branding.text_500_subtitle,
text_415_button=branding.text_415_button, text_500_button=branding.text_500_button,
text_help_bgp_route=branding.text_help_bgp_route, text_help_bgp_route=branding.text_help_bgp_route,
text_help_bgp_community=branding.text_help_bgp_community, text_help_bgp_community=branding.text_help_bgp_community,
text_help_bgp_aspath=branding.text_help_bgp_aspath, text_help_bgp_aspath=branding.text_help_bgp_aspath,

View File

@@ -25,10 +25,10 @@
<section> <section>
<div class="container has-text-centered"> <div class="container has-text-centered">
<h1 class="title is-size-1"> <h1 class="title is-size-1">
{{ text_415_title }} {{ text_500_title }}
</h1> </h1>
<h2 class="subtitle is-size-3"> <h2 class="subtitle is-size-3">
{{ text_415_subtitle }} {{ text_500_subtitle }}
</h2> </h2>
<br> <br>
<a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">Home</a> <a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">Home</a>

View File

@@ -72,120 +72,18 @@ function updateRouters(routers) {
// Submit Form Action // Submit Form Action
$('#lgForm').on('submit', function() { $('#lgForm').on('submit', function() {
submitForm();
// Regex to match any IPv4 host address or CIDR prefix
var ipv4_any = new RegExp('^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/(3[0-2]|2[0-9]|1[0-9]|[0-9]))?$');
// Regex to match any IPv6 host address or CIDR prefix
var ipv6_any = new RegExp('^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\/((1(1[0-9]|2[0-8]))|([0-9][0-9])|([0-9])))?$');
// Regex to match an IPv4 CIDR prefix only (excludes a host address)
var ipv4_cidr = new RegExp('^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|2[0-9]|1[0-9]|[0-9])?$');
// Regex to match an IPv6 CIDR prefix only (excludes a host address)
var ipv6_cidr = new RegExp('^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/((1(1[0-9]|2[0-8]))|([0-9][0-9])|([0-9]))?$');
var ipv6_host = new RegExp('^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))?$')
var cmd = $('#cmd option:selected').val();
// var routerType = $('#router option:selected').attr('type');
var ipprefix = $('#ipprefix').val();
var router = $('#router option:selected').val();
// Filters selectedRouters JSON object to only the selected router, returns all attributes passed from Flask's `get_routers`
var routersJson = selectedRouters.filter(r => r.location === router);
// Filters above to value of `requiresIP6Cidr` as passed from Flask's `get_routers`
var requiresIP6Cidr = routersJson[0].requires_ipv6_cidr
// If BGP lookup, and lookup is an IPv6 address *without* CIDR prefix (e.g. 2001:db8::1, NOT 2001:db8::/48), and requiresIP6Cidr
// is true, show an error.
$('#ipprefix_error').hide()
$('#ipprefix').removeClass('is-danger')
if (cmd == 'bgp_route' && ipv6_host.test(ipprefix) == true && requiresIP6Cidr == true) {
$('#ipprefix_error').show()
$('#ipprefix').addClass('is-danger')
$('#ipprefix_error').html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Invalid Input
</div>
<div id="error" style="display: block;" class="message-body">
This router requires IPv6 BGP lookups to be and exact match in CIDR notation.
</div>
</article>
`);
}
// If ping, and lookup is an IPv4 address *with* CIDR prefix (e.g. 192.0.2.0/24, NOT 192.0.2.1), show an error.
else if (ipv4_cidr.test(ipprefix) == true && cmd == 'ping') {
$('#ipprefix_error').show()
$('#ipprefix').addClass('is-danger')
$('#ipprefix_error').html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Invalid Input
</div>
<div id="error" style="display: block;" class="message-body">
<code>ping</code> does not allow network masks.
</div>
</article>
`);
}
// If traceroute, and lookup is an IPv4 address *with* CIDR prefix (e.g. 192.0.2.0/24, NOT 192.0.2.1), show an error.
else if (ipv4_cidr.test(ipprefix) == true && cmd == 'traceroute') {
$('#ipprefix_error').show()
$('#ipprefix').addClass('is-danger')
$('#ipprefix_error').html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Invalid Input
</div>
<div id="error" style="display: block;" class="message-body">
<code>traceroute</code> does not allow network masks.
</div>
</article>
`);
}
// If ping, and lookup is an IPv6 address *with* CIDR prefix (e.g. 2001:db8::/48, NOT 2001:db8::1), show an error.
else if (ipv6_cidr.test(ipprefix) == true && cmd == 'ping') {
$('#ipprefix_error').show()
$('#ipprefix').addClass('is-danger')
$('#ipprefix_error').html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Invalid Input
</div>
<div id="error" style="display: block;" class="message-body">
<code>ping</code> does not allow network masks.
</div>
</article>
`);
}
// If traceroute, and lookup is an IPv6 address *with* CIDR prefix (e.g. 2001:db8::/48, NOT 2001:db8::1), show an error.
else if (ipv6_cidr.test(ipprefix) == true && cmd == 'traceroute') {
$('#ipprefix_error').show()
$('#ipprefix').addClass('is-danger')
$('#ipprefix_error').html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Invalid Input
</div>
<div id="error" style="display: block;" class="message-body">
<code>traceroute</code> does not allow network masks.
</div>
</article>
`);
} else submitForm();
}); });
var submitForm = function() { var submitForm = function() {
progress.hide(); progress.hide();
var cmd = $('#cmd option:selected').val(); var cmd = $('#cmd option:selected').val();
// var cmdtitle = cmd.replace('_', ': ');
var cmdtitle = $('#cmd option:selected').text(); var cmdtitle = $('#cmd option:selected').text();
var network = $('#network option:selected').val(); var network = $('#network option:selected').val();
var router = $('#router option:selected').val(); var router = $('#router option:selected').val();
var routername = $('#router option:selected').text(); var routername = $('#router option:selected').text();
var ipprefix = $('#ipprefix').val(); var ipprefix = $('#ipprefix').val();
// var routerType = $('#router option:selected').attr('type');
$('#output').text("") $('#output').text("")
$('#queryInfo').text("") $('#queryInfo').text("")
@@ -207,7 +105,6 @@ var submitForm = function() {
</div> </div>
`) `)
/////////////////////////////////////////////////////////////
$.ajax({ $.ajax({
url: `/lg`, url: `/lg`,

View File

@@ -21,6 +21,10 @@ body
.title, .subtitle, p, a .title, .subtitle, p, a
color: findColorInvert($danger) color: findColorInvert($danger)
.has-background-danger .footer
p, a
color: findColorInvert($danger)
.footer .footer
p, a p, a
color: findColorInvert($body-background-color) color: findColorInvert($body-background-color)

167
manage.py
View File

@@ -1,75 +1,200 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Module Imports
import os import os
import grp
import pwd
import sys import sys
import glob
import click import click
import random import random
import shutil
import string import string
from logzero import logger
from passlib.hash import pbkdf2_sha256 from passlib.hash import pbkdf2_sha256
from hyperglass import render as render # Project Imports
from hyperglass import hyperglass from hyperglass import hyperglass
from hyperglass import render as render
# Initialize shutil copy function
cp = shutil.copyfile
@click.group() @click.group()
def main(): def hg():
pass pass
@main.command() @hg.command()
def clearcache(): def clearcache():
"""Clears the Flask-Caching cache"""
try: try:
hyperglass.clearCache() hyperglass.clearCache()
logger.info("Successfully cleared cache.") click.secho("Successfully cleared cache.", fg="green", bold=True)
except: except:
click.secho("✗ Failed to clear cache.", fg="red", bold=True)
raise raise
logger.error("Failed to clear cache.")
@main.command() @hg.command()
def generatekey(string_length=16): def generatekey(string_length=16):
"""Generates 16 character API Key for hyperglass-frr API, and a corresponding PBKDF2 SHA256 Hash"""
ld = string.ascii_letters + string.digits ld = string.ascii_letters + string.digits
api_key = "".join(random.choice(ld) for i in range(string_length)) api_key = "".join(random.choice(ld) for i in range(string_length))
key_hash = pbkdf2_sha256.hash(api_key) key_hash = pbkdf2_sha256.hash(api_key)
click.echo( click.secho(
""" f"""
Your API Key is: {api_key} Your API Key is: {api_key}
Place your API Key in the `configuration.py` of your API module. For example, in: `hyperglass-frr/configuration.py` Place your API Key in the `configuration.py` of your API module. For example, in: `hyperglass-frr/configuration.py`
Your Key Hash is: {key_hash} Your Key Hash is: {key_hash}
Use this hash as the password for the device using the API module. For example, in: `hyperglass/hyperglass/configuration/devices.toml` Use this hash as the password for the device using the API module. For example, in: `hyperglass/hyperglass/configuration/devices.toml`
""".format( """
api_key=api_key, key_hash=key_hash
)
) )
@main.command() @hg.command()
def testserver(): def testserver():
"""Starts Flask development server for testing without WSGI/Reverse Proxy"""
try: try:
hyperglass.render.css.renderTemplate() hyperglass.render.css.renderTemplate()
hyperglass.app.run(host="0.0.0.0", debug=True, port=5000) hyperglass.app.run(host="0.0.0.0", debug=True, port=5000)
logger.error("Started test server.") click.secho("Started test server.", fg="green", bold=True)
except: except:
logger.error("Failed to start test server.") click.secho("Failed to start test server.", fg="red", bold=True)
raise raise
@main.command() @hg.command()
def render(): def render():
"""Renders Jinja2 and Sass templates to HTML & CSS files"""
try: try:
hyperglass.render.css.renderTemplate() hyperglass.render.css.renderTemplate()
logger.info("Successfully rendered CSS templates.") click.secho("Successfully rendered CSS templates.", fg="green", bold=True)
except: except:
click.secho("✗ Failed to render CSS templates.", fg="red", bold=True)
raise raise
logger.error("Failed to render CSS templates.")
try: try:
hyperglass.render.html.renderTemplate("index") hyperglass.render.html.renderTemplate("index")
logger.info("Successfully rendered HTML templates.") click.secho("Successfully rendered HTML templates.", fg="green", bold=True)
except: except:
click.secho("✗ Failed to render HTML templates.", fg="red", bold=True)
raise
@hg.command()
def migrateconfig():
"""Copies example configuration files to usable config files"""
try:
click.secho("Migrating example config files...", fg="cyan")
hyperglass_root = os.path.dirname(hyperglass.__file__)
config_dir = os.path.join(hyperglass_root, "configuration/")
examples = glob.iglob(os.path.join(config_dir, "*.example"))
for f in examples:
basefile, extension = os.path.splitext(f)
newfile = basefile
if os.path.exists(newfile):
click.secho(f"{newfile} already exists", fg="blue")
else:
try:
cp(f, newfile)
click.secho(f"✓ Migrated {newfile}", fg="green")
except:
click.secho(f"✗ Failed to migrate {newfile}", fg="red")
raise
click.secho(
"✓ Successfully migrated example config files", fg="green", bold=True
)
except:
click.secho("✗ Error migrating example config files", fg="red", bold=True)
raise
@hg.command()
def migrategunicorn():
"""Copies example Gunicorn config file to a usable config"""
try:
click.secho("Migrating example Gunicorn configuration...", fg="cyan")
hyperglass_root = os.path.dirname(hyperglass.__file__)
ex_file = os.path.join(hyperglass_root, "gunicorn_config.py.example")
basefile, extension = os.path.splitext(ex_file)
newfile = basefile
if os.path.exists(newfile):
click.secho(f"{newfile} already exists", fg="blue")
else:
try:
cp(ex_file, newfile)
click.secho(f"✓ Migrated {newfile}", fg="green")
except:
click.secho(f"✗ Failed to migrate {newfile}", fg="red")
raise
click.secho(
"✓ Successfully migrated example Gunicorn configuration",
fg="green",
bold=True,
)
except:
click.secho(
"✗ Error migrating example Gunicorn configuration", fg="red", bold=True
)
raise
@hg.command()
@click.option("--dir", default="/etc/systemd/system")
def migratesystemd(dir):
"""Copies example systemd service file to /etc/systemd/system/"""
try:
click.secho("Migrating example systemd service...", fg="cyan")
hyperglass_root = os.path.dirname(hyperglass.__file__)
ex_file_base = "hyperglass.service.example"
ex_file = os.path.join(hyperglass_root, ex_file_base)
basefile, extension = os.path.splitext(ex_file_base)
newfile = os.path.join(dir, basefile)
if os.path.exists(newfile):
click.secho(f"{newfile} already exists", fg="blue")
else:
try:
cp(ex_file, newfile)
click.secho(f"✓ Migrated {newfile}", fg="green")
except:
click.secho(f"✗ Failed to migrate {newfile}", fg="red")
raise
click.secho(
f"✓ Successfully migrated example systemd service to: {newfile}",
fg="green",
bold=True,
)
except:
click.secho("✗ Error migrating example systemd service", fg="red", bold=True)
raise
@hg.command()
@click.option("--user", default="www-data")
@click.option("--group", default="www-data")
def fixpermissions(user, group):
"""Effectively runs `chmod` and `chown` on the hyperglass/hyperglass directory"""
hyperglass_root = os.path.dirname(hyperglass.__file__)
uid = pwd.getpwnam(user).pw_uid
gid = grp.getgrnam(group).gr_gid
try:
os.chown(hyperglass_root, uid, gid)
click.secho(
"✓ Successfully changed hyperglass/ ownership", fg="green", bold=True
)
except:
click.secho("✗ Failed to change hyperglass/ ownership", fg="red", bold=True)
raise
try:
os.chmod(hyperglass_root, 0o744)
click.secho(
"✓ Successfully changed hyperglass/ permissions", fg="green", bold=True
)
except:
click.secho("✗ Failed to change hyperglass/ permissions", fg="red", bold=True)
raise raise
logger.error("Failed to render HTML templates.")
if __name__ == "__main__": if __name__ == "__main__":
main() hg()

View File

@@ -7,27 +7,21 @@ nav:
- Installation: - Installation:
- 'Installing Hyperglass': 'installation/installing-hyperglass.md' - 'Installing Hyperglass': 'installation/installing-hyperglass.md'
- 'HTTP/WSGI': 'installation/wsgi.md' - 'HTTP/WSGI': 'installation/wsgi.md'
- 'Reverse Proxy': 'installation/reverseproxy.md' - 'Systemd': 'installation/systemd.md'
- 'Running Hyperglass as a Service': 'installation/supervisord.md' - 'Reverse Proxy & SSL': 'installation/reverseproxy.md'
- Configuration: - Configuration:
- 'Configuring Hyperglass': 'configuration.md' - 'Configuring Hyperglass': 'configuration/index.md'
- 'General Parameters': 'configuration/general.md' - 'General Parameters': 'configuration/general.md'
- 'Branding': 'configuration/branding.md' - 'Branding': 'configuration/branding.md'
- 'Devices': 'configuration/devices.md' - 'Devices': 'configuration/devices.md'
- 'Authentication': 'configuration/authentication.md' - Caching: 'caching.md'
- 'Commands': 'configuration/commands.md' - Rate Limiting: 'ratelimiting.md'
- 'Proxy': 'configuration/proxy.md'
- 'Blacklist': 'configuration/blacklist.md'
- 'Securing Router Access': 'configuration/securing-router-access.md'
- Caching:
- 'caching.md'
- Rate Limiting:
- 'ratelimiting.md'
- Development: - Development:
- 'Introduction': 'development/index.md' - 'Introduction': 'development/index.md'
- Extras: - Extras:
- 'Common AS_PATH Regular Expressions': 'extras/common_as_path_regex.md' - 'Common AS_PATH Regular Expressions': 'extras/common_as_path_regex.md'
- 'Securing Router Access': 'extras/securing-router-access.md'
- 'Supported Device Types': 'extras/supported-device-types.md'
# Theme Configuration # Theme Configuration
theme: theme:
name: 'readthedocs' name: 'readthedocs'