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:
Binary file not shown.
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 18 KiB |
@@ -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.
|
|
@@ -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>
|
|
@@ -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 |
|
||||||
| ------ | ------------------------- |
|
| ------ | ------------------------- |
|
||||||
|
@@ -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)
|
|
@@ -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.
|
||||||
|
@@ -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.
|
||||||
|
@@ -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"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
@@ -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`.
|
|
@@ -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:
|
||||||
|
|
82
docs/extras/supported-device-types.md
Normal file
82
docs/extras/supported-device-types.md
Normal 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
|
||||||
|
```
|
@@ -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.
|
||||||
|
79
docs/installation/reverseproxy.md
Normal file
79
docs/installation/reverseproxy.md
Normal 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 Let’s Encrypt SSL/TLS Certificates with NGINX](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/)
|
26
docs/installation/systemd.md
Normal file
26
docs/installation/systemd.md
Normal 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
|
||||||
|
```
|
@@ -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
BIN
docs/max_prefix_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
docs/ping_traceroute_cidr.png
Normal file
BIN
docs/ping_traceroute_cidr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
docs/requires_ipv6_cidr.png
Normal file
BIN
docs/requires_ipv6_cidr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
@@ -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:
|
||||||
|
@@ -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:
|
||||||
|
@@ -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.",
|
||||||
|
@@ -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
0
hyperglass/file.test
Executable 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
|
||||||
|
@@ -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())
|
||||||
|
12
hyperglass/hyperglass.service.example
Normal file
12
hyperglass/hyperglass.service.example
Normal 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
|
@@ -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,
|
||||||
|
@@ -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>
|
@@ -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`,
|
||||||
|
@@ -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
167
manage.py
@@ -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()
|
||||||
|
20
mkdocs.yml
20
mkdocs.yml
@@ -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'
|
||||||
|
Reference in New Issue
Block a user