From a417afdd8cdc41031a4fbaa8e380b4693cc15da9 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Tue, 28 May 2019 00:53:45 -0700 Subject: [PATCH] added docs, migrated input validated from JS to execute.py --- docs/blacklist_error.png | Bin 65621 -> 18523 bytes docs/configuration/authentication.md | 16 -- docs/configuration/blacklist.md | 25 -- docs/configuration/branding.md | 235 +++++++++--------- docs/configuration/commands.md | 83 ------- docs/configuration/devices.md | 227 +++++++---------- docs/configuration/general.md | 76 ++++-- docs/configuration/index.md | 94 ++++++- docs/configuration/proxy.md | 45 ---- .../securing-router-access.md | 8 +- docs/extras/supported-device-types.md | 82 ++++++ docs/installation/installing-hyperglass.md | 30 ++- docs/installation/reverseproxy.md | 79 ++++++ docs/installation/systemd.md | 26 ++ docs/installation/wsgi.md | 55 ++-- docs/max_prefix_error.png | Bin 0 -> 25693 bytes docs/ping_traceroute_cidr.png | Bin 0 -> 21323 bytes docs/requires_ipv6_cidr.png | Bin 0 -> 23773 bytes file.test | 0 hyperglass/command/construct.py | 18 +- hyperglass/command/execute.py | 115 ++++++--- hyperglass/configuration/__init__.py | 8 +- .../configuration/configuration.toml.example | 6 +- hyperglass/file.test | 0 hyperglass/gunicorn_config.py.example | 2 +- hyperglass/hyperglass.py | 69 +++-- hyperglass/hyperglass.service.example | 12 + hyperglass/render/__init__.py | 12 +- .../render/templates/{415.html => 500.html} | 4 +- hyperglass/static/js/hyperglass.js | 107 +------- .../static/sass/custom/custom_elements.sass | 4 + manage.py | 167 +++++++++++-- mkdocs.yml | 20 +- 33 files changed, 885 insertions(+), 740 deletions(-) delete mode 100644 docs/configuration/authentication.md delete mode 100644 docs/configuration/blacklist.md delete mode 100644 docs/configuration/commands.md delete mode 100644 docs/configuration/proxy.md rename docs/{configuration => extras}/securing-router-access.md (97%) create mode 100644 docs/extras/supported-device-types.md create mode 100644 docs/installation/reverseproxy.md create mode 100644 docs/installation/systemd.md create mode 100644 docs/max_prefix_error.png create mode 100644 docs/ping_traceroute_cidr.png create mode 100644 docs/requires_ipv6_cidr.png create mode 100644 file.test create mode 100755 hyperglass/file.test create mode 100644 hyperglass/hyperglass.service.example rename hyperglass/render/templates/{415.html => 500.html} (92%) diff --git a/docs/blacklist_error.png b/docs/blacklist_error.png index 08a72b0e032a825ee90f2bd62dbdd490733f0721..f9a5592a37076110e6a41b020ded004b4ffe411c 100644 GIT binary patch literal 18523 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 literal 65621 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 diff --git a/docs/configuration/authentication.md b/docs/configuration/authentication.md deleted file mode 100644 index d76be8e..0000000 --- a/docs/configuration/authentication.md +++ /dev/null @@ -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. diff --git a/docs/configuration/blacklist.md b/docs/configuration/blacklist.md deleted file mode 100644 index d1572c3..0000000 --- a/docs/configuration/blacklist.md +++ /dev/null @@ -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: - - diff --git a/docs/configuration/branding.md b/docs/configuration/branding.md index f4f7885..aef3e38 100644 --- a/docs/configuration/branding.md +++ b/docs/configuration/branding.md @@ -10,9 +10,10 @@ } -From `hyperglass/hyperglass/configuration/config.toml`: +From `hyperglass/hyperglass/configuration/configuration.toml` `[branding]` table. -### site_title +# Site Parameters +#### site_title | Type | Default Value | | ------ | -------------- | @@ -20,7 +21,7 @@ From `hyperglass/hyperglass/configuration/config.toml`: HTML `` element that is shown in a browser's title bar. -### title_mode +#### title_mode | 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. -#### 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"` - -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 +#### title | Type | Default Value | | ------ | -------------- | | String | `"hyperglass"` | -### subtitle +#### subtitle | Type | Default Value | | ------ | -------------------- | @@ -56,7 +47,7 @@ Displays only the Title text defined in the [title](#title) parameter. See [primary_asn](#primary_asn) parameter. -### enable_footer +#### enable_footer | 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`. -### enable_credit +#### enable_credit | 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. -### 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_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 +#### show_peeringdb | 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. -### 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 | | ------ | ------------- | @@ -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. -### text_location +#### text_location | Type | Default Value | | ------ | ------------- | @@ -176,7 +173,7 @@ Sets the header text of the results box. Sets the placeholder text of the location selector. -### text_cache +#### text_cache | 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). -### text_limiter_title +#### text_limiter_title | 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. -### text_limiter_subtitle +#### text_limiter_subtitle | 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). -### text_415_title +#### text_500_title | 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. -### text_415_subtitle +#### text_500_subtitle | 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. -### text_415_button +#### text_500_button | 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. -### text_help_bgp_route +#### text_help_bgp_route | 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. -### text_help_bgp_community +#### text_help_bgp_community | Type | Default Value | | ------ | ------------------------- | @@ -243,7 +240,7 @@ Sets the BGP Community query help text, displayed when the **?** icon is hovered !!! note 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 | | ------ | ------------------------- | @@ -254,7 +251,7 @@ Sets the BGP AS Path query help text, displayed when the **?** icon is hovered. !!! note 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 | | ------ | ------------------------- | @@ -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. -### text_help_traceroute +#### text_help_traceroute | Type | Default Value | | ------ | ------------------------- | @@ -273,7 +270,9 @@ Sets the Traceroute query help text, displayed when the **?** icon is hovered. !!! note 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 | | ------ | ------------------------- | @@ -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. -### primary_font_name +#### primary_font_name | 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. -### mono_font_url +#### mono_font_url | 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. -### mono_font_name +#### mono_font_name | Type | Default Value | | ------ | ------------------------- | diff --git a/docs/configuration/commands.md b/docs/configuration/commands.md deleted file mode 100644 index d094483..0000000 --- a/docs/configuration/commands.md +++ /dev/null @@ -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) diff --git a/docs/configuration/devices.md b/docs/configuration/devices.md index bdb5786..9bc6e88 100644 --- a/docs/configuration/devices.md +++ b/docs/configuration/devices.md @@ -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 -[[router]] -address = "10.0.0.1" +[router.'pop1'] +address = "192.0.2.1" asn = "65000" -src_addr_ipv4 = "192.0.2.1" +src_addr_ipv4 = "192.0.2.251" src_addr_ipv6 = "2001:db8::1" credential = "default" location = "pop1" name = "router1.pop1" -port = "22" -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" +display_name = "Washington, DC" port = "22" type = "cisco_ios" -proxy = "jumpbox2" +proxy = "jumpbox1" -[[router]] -address = "10.0.0.3" +[router.'pop2'] +address = "192.0.2.2" asn = "65000" -src_addr_ipv4 = "192.0.2.3" -src_addr_ipv6 = "2001:db8::3" -credential = "default" -location = "pop3" -name = "router1.pop3" -port = "22" -type = "juniper" -proxy = "jumpbox3" +src_addr_ipv4 = "192.0.2.252" +src_addr_ipv6 = "2001:db8::2" +credential = "frr_api_pop2" +location = "pop2" +name = "router1.pop2" +display_name = "Portland, OR" +port = "8080" +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. - -#### 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 - -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 +[credential.'frr_api_pop2'] +username = "doesntmatter" +password = "$pbkdf2-sha256$29000$bI0xJqQUQoixtjZGSAnhvA$FM0oUc.Y3kuvl9ilQmMuULTD1MjzD64Ax9rFNUgAl.c" ``` + +!!! 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. diff --git a/docs/configuration/general.md b/docs/configuration/general.md index 5da0706..8457a83 100644 --- a/docs/configuration/general.md +++ b/docs/configuration/general.md @@ -4,7 +4,7 @@ From `hyperglass/hyperglass/configuration/config.toml`: | Type | Default Value | | ------ | ------------- | -| String | `"65000"` | +| String | `"65000"` | 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 | | ------- | ------------- | -| Boolean | `False` | +| Boolean | `False` | 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 -| Type | Default Value | -| ------ | --------------------- | +| Type | Default Value | +| ------ | ----------------------- | | 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: @@ -45,16 +45,16 @@ Message presented to the user when invalid input is detected. `{input}` will be ### message_blacklist -| Type | Default Value | -| ------ | ------------------------- | +| Type | Default Value | +| ------ | --------------------------- | | 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_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."` | 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 | | ------- | ------------- | -| Boolean | `True` | +| Boolean | `True` | Enables or disables the BGP Route query type. @@ -71,7 +71,7 @@ Enables or disables the BGP Route query type. | Type | Default Value | | ------- | ------------- | -| Boolean | `True` | +| Boolean | `True` | Enables or disables the BGP Community query type. @@ -79,7 +79,7 @@ Enables or disables the BGP Community query type. | Type | Default Value | | ------- | ------------- | -| Boolean | `True` | +| Boolean | `True` | 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 | | ------- | ------------- | -| Boolean | `True` | +| Boolean | `True` | Enables or disables the Ping query type. @@ -95,38 +95,64 @@ Enables or disables the Ping query type. | Type | Default Value | | ------- | ------------- | -| Boolean | `True` | +| Boolean | `True` | Enables or disables the Traceroute query type. ### rate_limit_query -| Type | Default Value | -| ------- | ------------- | -| String | `"5"` | +| Type | Default Value | +| ------ | ------------- | +| 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). ### rate_limit_site -| Type | Default Value | -| ------- | ------------- | -| String | `"120"` | +| Type | Default Value | +| ------ | ------------- | +| 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). ### cache_timeout -| Type | Default Value | -| -------- | ------------- | -| Integer | `120` | +| Type | Default Value | +| ------- | ------------- | +| 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). ### cache_directory -| Type | Default Value | -| -------- | ------------------------------------ | -| String | `"hyperglass/hyperglass/.flask_cache"` | +| Type | Default Value | +| ------ | -------------------------------------- | +| 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). + +### 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. diff --git a/docs/configuration/index.md b/docs/configuration/index.md index e9e727a..a08eb19 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -11,6 +11,98 @@ hyperglass/configuration/ └── 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). + +#### Example + +```toml +requires_ipv6_cidr = [ +"cisco_ios", +"cisco_nxos" +] +``` diff --git a/docs/configuration/proxy.md b/docs/configuration/proxy.md deleted file mode 100644 index 1455843..0000000 --- a/docs/configuration/proxy.md +++ /dev/null @@ -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`. diff --git a/docs/configuration/securing-router-access.md b/docs/extras/securing-router-access.md similarity index 97% rename from docs/configuration/securing-router-access.md rename to docs/extras/securing-router-access.md index 23dd180..d420bef 100644 --- a/docs/configuration/securing-router-access.md +++ b/docs/extras/securing-router-access.md @@ -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. -# 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: @@ -21,7 +19,7 @@ username hyperglass privilege 15 view hyperglass secret <secret> !!! note "Terminal" 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: @@ -42,7 +40,7 @@ username hyperglass !!! 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. -## 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: diff --git a/docs/extras/supported-device-types.md b/docs/extras/supported-device-types.md new file mode 100644 index 0000000..b3460ea --- /dev/null +++ b/docs/extras/supported-device-types.md @@ -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 +``` diff --git a/docs/installation/installing-hyperglass.md b/docs/installation/installing-hyperglass.md index a843c45..86db78a 100644 --- a/docs/installation/installing-hyperglass.md +++ b/docs/installation/installing-hyperglass.md @@ -1,9 +1,11 @@ # Download -## System Requirements +#### System Requirements !!! 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: @@ -11,34 +13,38 @@ Hyperglass is written and tested on Python 3.7, but should be backwards compatib # apt install -y python3 python3-pip ``` -## Clone the repository +#### Clone the repository ```console $ cd /opt/ $ git clone https://github.com/checktheroads/hyperglass ``` -## Install Required Python Modules +# Install + +#### Python Dependencies ```console -$ cd /opt/hyperglass/hyperglass +$ cd /opt/hyperglass/ $ pip3 install -r requirements.txt ``` -## Clone Example Configuration Files +#### Migrate Configuration Files -``` -$ cd /opt/hyperglass/hyperglass/configuration/ -$ for f in *.example; do cp $f `basename $f .example`; done; +```console +$ cd /opt/hyperglass/ +$ 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. ```console -$ cd /opt/hyperglass/hyperglass/ -$ python3 app.py +$ cd /opt/hyperglass/ +$ 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. diff --git a/docs/installation/reverseproxy.md b/docs/installation/reverseproxy.md new file mode 100644 index 0000000..7d8dbf4 --- /dev/null +++ b/docs/installation/reverseproxy.md @@ -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/) diff --git a/docs/installation/systemd.md b/docs/installation/systemd.md new file mode 100644 index 0000000..5275183 --- /dev/null +++ b/docs/installation/systemd.md @@ -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 +``` diff --git a/docs/installation/wsgi.md b/docs/installation/wsgi.md index 890b763..ad21cca 100644 --- a/docs/installation/wsgi.md +++ b/docs/installation/wsgi.md @@ -6,45 +6,38 @@ Gunicorn is a WSGI server written in Python. ## Install ```console -# pip3 install gunicorn +$ pip3 install gunicorn ``` ## 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 -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 -# chown -R www-data:www-data /opt/hyperglass/hyperglass -# chmod -R 744 /opt/hyperglass/hyperglass +# cd /opt/hyperglass/ +# python3 manage.py fixpermissions --user <user> --group <group> ``` -<!-- # Supervisor Installation - -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 -``` --> +!!! note "File Ownership" + If the `--user` and `--group` options are not specified, `www-data` will be used. diff --git a/docs/max_prefix_error.png b/docs/max_prefix_error.png new file mode 100644 index 0000000000000000000000000000000000000000..cb2846bbd2b218501cad10229868570bde99b2b9 GIT binary patch literal 25693 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 B<G%m^ literal 0 HcmV?d00001 diff --git a/docs/ping_traceroute_cidr.png b/docs/ping_traceroute_cidr.png new file mode 100644 index 0000000000000000000000000000000000000000..bae277180c330bc8a523408c11732e2d70ec97a4 GIT binary patch literal 21323 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 V%TG;F;02l!sPlG~)#g44zXE_o&cgrz literal 0 HcmV?d00001 diff --git a/docs/requires_ipv6_cidr.png b/docs/requires_ipv6_cidr.png new file mode 100644 index 0000000000000000000000000000000000000000..47e66002a8536c96079030469c50adfbfa344a13 GIT binary patch literal 23773 zcmeFXbyQqU(=Q4ELV_m*_YeYsV8LaO;7)MY;O;(z5FofDKybI<?hF##ZP3A8X7B+9 zIVA7<edl?f=iYVyxoe%Z&iZEU>AiRFuCA`?s_I|Y-l58hQrH-z7)VG+*fP@MDo98u z(1+`WCyyUWzlFMfBqWSRD={%;88I<xWoHL-D_b)pB<axjM091<UBX_F&m<Z1C)f8d z{ig3@VlV}@_s^vnXkMWbG{0n7(NTVeOxD1Vx$^UcHdD5i7AiqU6Lt94Di%W9!WK-y zPN>i6QVKkS&&A&bdNtxP!UIp?BSOMReuJ0W^$zLdQFYL28)eOGgD0u|6A>izqju7j zq_?r3C`n15;0*)r%W&bbmf{}6`7XBz#66=nx74pkNEi~lq0A;`)V_}^*%L&5U?c6} zt<7ksGym9f|FRh*9q<IM<TCI!TES%kE2l$V{$m4u=LynEzkEI$8qz!L<4I?&H`0_< z0-XuI+|kh$NXD9kiT#q7uR8NdNRlRRh>g2)K^rnxx+AY-6kl^lbXK<xJ%M%OKb=To zx*uJ?Tpo}n8a>R4CQf2H!_0t^@KxW{3?V%|{q|XG>ql>TXohjwEUR^t_RG;rK68%N zN3U!q15vhlleW8JHu}xQN7$t{DA;>DhaCG2`rj*ElXC=}BElxKUjbP{X!<h-BO{X5 z$qihO$ipXIKH4%BqyuL0>Zu5a!1mce<<YrttZ~EZP4-td)s3;hxTU1ibC=7rtJG{Q z89UpkM7Xc~_g4lnhct6?F_es<+qnqYo9xsl{qwdPfvJXMjC@^c`TjfPD8hIcmhEJy zftGk7F<cd+TP1zz_P;n(D^&XAR?R1O`moo>h<y_p-we-B3_Sa^i{MA_wd`j!hQz!C zF3}Ssw>v#sTYLx8)xi|Op9udx-!h6+r2ZQ76BF9oN;V5Hk4qxkrzg3w81Vs*$B={D zv5E$9cu_oZRs8WDy^PgKd_=hN8H*Zqu*13nc_Z*;1<C=k*$Q>xqu%zXju@PQE-UXE z9@_`lk6}^<AhTf^1UMZb%fH9_63Bo@pc2eU1L#Ao3MQx4>?6bY(wW7}@Ju$~GKN?O z+wOB%rb!_dKi0H}u$V>G;23rxx;GN``<yY|9n6T&{S|^GIQw0M6)5r{C@a#QPg5UR zbTm3rwqUaaF|DpU;`@@cp}{**j+i_jp((%m{!$jxEZAAZEBn3japA}Bc(sJ;Ul~Q! zv-duf7UE3lPa#=jos&dLsE%{ikj#@DhD5!u9@E~b-6`F9cgF6D-$da@1o%oR<=?B! zW=)JAfgc<Y+MR2ZV0yw%_rqGAc8f8eh$G_aQ@060<zp?M5-M(-lra4uNEjsAuHB&X z!YHHypO;*b#Nx9{N6^ZT23tKLdnza|^d%I>o*4dAsncey^jPT9Q;-Vu%<yx0w_*Eo zNAwE)(|4$V&-gT7+FzHvvZW`dsd;XXAN=&2xOO)3co4CSJe@st!t?!5&CuvjnDnbS z?*7;pl54TxZ?ZD<GBYxPxuYNXs@UhgrHT-KtB;M2V~wSXS^mKA!Y@pKE<H|RPO>OV zFH11bH!fQ?Nsc{tR%JhTATK!=D&3m!T6$k*GA~hfTQ*TTBcY8M@LDWpJn{Iu@jyxZ zn?ydQNqP<%x%k>R?AYNL2%|2OkJv>JxuT-sh;9^fBE2S^hlKstu<Si$K+)U1*L%<Q z)b}a`HI_uuX)+iMU-`dplLP9BDjH|Kuj#}Hat=DhKnY`z@RJBOX<BpdRt<v_fZi$_ ziWs(QD_3L!JJQgo;??38<9TI+WU*yiWevYu4J-{<4G6@i#Ag&{sz<1IswEVYD>wdp zm(N(fS2Cc&tOm%tR>s!g(7@Ex&C|#O7jb8mWkHKsl$Q&D5`OYxAG-8u)lKVr&>}x5 z{LC+Yt>jX_B{7`HiAI{1pvaV0JK0k^RdZAGODCkZspfSpc1=zVk2Q~_@HovL@6N0J z1F+R3$;|W=_Jn?^ZUI6$I&(X-cY2|CCJX*ySwvXUKO9(nhlxUm5-eIKn$}?|7M7J_ zoM4={3ZN~C4g8Qi4$QGPot*{$u+g*kT|nio<XBBQ;MryG{=uJoqQInp80s^3ttPML zuNHNYI2AdK+#230XLe;?V|LeZ)$*;csg<u6G`Z@N9T`}*&QUL(*0nLUNpNdC2attD zMi29+5T@{8DiAsoQfkdQ$kk$3K};6R5yK-(ik6xVJl5ql8J32|%iGj2yuFI))h61W z@h+#db-vzFMR&zH#e&p3S8f+YmohhCWuMVAi}6?qF_w82`NS?3lrqj4@foo)6nh<e z`Fh!U3n)L-7H$oH*7OVz4@|wA@0t+X8Q(b`pX_&u+@@pwo>;D#rI}ct>hawp39d?I zm+>NlKf}pTcUiqH@rDhE3>3J%Cf>s$%n4?PI>10ZAiyVbyl2XUM{mb?m5FCbQPHo^ z4>7#SONfSW?QxK4B4YGul-^^%uYT|Uv@19$I6ee`TaRH#vGhzdQj72d*)h!ot+UTY z4nr4%lbUXgtLuALg|lAb@*itI0<^+sxhm_|)Yd$mn2#GScP{rXL%Wy4;cqDwD-`;a z8bH5{`orjW5`Ml_F!Tl{oxR(tnhgE%>&LJ8U*$u1=5#JELu1uqIY&-D9-6h9G*^36 zOYyO!nv6(S$vHcnguh7quKy-SkH`M0N5pnN(|W7Sxg6lfH9PaGL(6$JZ+UOA$3nF1 z>u(LCGI|_Fr~33_=V;lUZD2mGO0By17>SAUW%{Lbjrdd?b2OG@nu*x4`LR)yjiT2` z!RGj&QhAJihv9hRgr`}@K&)QIVunVRW~D}LDOjh~_<V)*I=Raxt5(Kl0lZr>sLRu` z<Z)Yx{R`WQ3<$Aotn^$*D6psKoKBsBx3TRmAbN?c@vK9zg|(K=72F*vPhnSr*%t>) zmj-<-E!rQo)l|;hm~8fksY-!wZ=I}lPwL7J-4}2V+Mz432GFG4cBRSO+TvQ4zG!o@ z`=mP&6bQ1IODChll*572K?z_K4A^h&<_ek}dfUp)7OR$=qfde{YQsK<MI+`AFmgv> zWq;*cqwx;b5YLcnTm+^4&#CgaMV%U(Y5?Kqe!1)PAsN;`)qjTn1W{~KQScH1OImx{ z^7A!H-F+zZ=*!3e>5eUkFEgPD-qqJ#*kRZb^c^zC{fWZ#cfR*(?L~EyI0gHoJ84Ch zx!i=@q6$nYJt>XDD9ldG0E^qvx?28v?mLsa!EqOz?SWCI)Ma3RCk?D~s(UdUKM^&t zhowM|(pndMu_JpB?{jE;sIQ4qW&ql9Ig~|jU0-uc+~2n6h4A`Ju9}}RH|r{-)uly( z=I`cb&1R-u7w+8U>P<atMzhl~Jr<#QZToKUc|cFAZfi#?0G<Pd!|Bej&#DoIg3|s+ zTgD?@{Zji>rj$fNv0f@CYKK;v4w4`)tXb?-sx#69U&7;qaXg!yX;}6$2xl8mAjnEG zPG00?e7m+-IBE$W>}Cxyd}^Q&wRum2km|PSnz6WZy{~o=boafCJQ|)_D207rrtkwD z`7Ma={F=lip?WP$2voUqJ*e*5j$&mulzg4&-|feIYkpRG?$ZwMRV7hXeUu-=$AOWF z7x?WPvZv1rBw2E#DSM=^;TGGwB%;;#NF1nl_@g6>991YtGHBUg6ubzn=`-GN!8{1^ z6N$GI6z$yH&k+UU{oLT{M(N=`OO<zA=Qv3ePLfEz`zguEw&}^&8j&v$c%S5`%(pMC ztmKf5z6mO{3ZbnSp5yv5Vskqy?-o5^*{6=u+Ac^)gml0E$TBLgj*yU$)2-CBT(uPB z`Ar?{Sd2e9n3%D6+BrV7MnV$w<bSxdGjlbj_O!FLcj5O0y!_LG|Ka*~HS0_2KTTY1 z057!^l&QrWoXx1YSU6eOUJ7AQQ&S5%e>CS;5tsZo`NJLHrKPK@BR?yvhldA?2M3FT zvjr<VA0Hnp+dI~G@0cH2FuQo!yBd2k+q=;ItCRogN8HTC)Y;0>)yl!1`ggy^CJt_{ zfR`_SGy3n}zs70iY4yK3*}ME(tOo&Ee?MVmXJKRg@4g>M1%KD_D_eP**=mVf*_qk9 zJn#@=XJg|Q{6p}MNB^7UUr4q8LdwheSIWOU`8TB?>u(AEBGJFp^{4hhUqTpytpBZh zAq;|(FJuqXL2f0kr1o%r^m}d|etHjohJT+QuE`o!F~vk5&R=B2Mb$i!4<KGG>Zf<T z$4WWb(s}X{$r6k-q2g<6&e$X@@61`cA}17Lfw7B2dMssLZRSuIqxjZNvg{j`SGnRY z+pZ%p#2plDe+V(yKFFy>P!!L=kH`2du$NoGE_VJcGaf%x)O1v=U!dZNAR+&?DE$6N z{RuSwq|x!i6Y5V$kN#44B7YVzMEz^y-?WUCNFA-ug~?g4|D&IWN1p|j|6cX~SL=Y5 zK%Z%|$&!IJkJ5oE67%lUSh4?<Lj)<{1obCR#l$>wNsS^AQxT-8CC3Fi%G98l0{^70 zeWo~qG5hsD<ohk|CgyiAWKzS<N#zRNoZ^GMW4RDql4E9GQS=|(yS*rc^n}D_~2 zU_onYH2dNLpZ!gn44RqQ%&^!Febf2kpS5Y9|G}>k<GZb2K>XBz!;sHM$8v6%5Kq}{ zs}^g-z~3iEq`joZo``3uRmtE*Y)9+$!C(?Vi1j_r`@an=^Yf!=G99PMRj;%c6pJ8! zo^lM{C;yxW7Uh;MJFhfx$I<Z6z`yhQs+t)_0CTi4Ab7rbYO+TC_c>pCLEX?>B%}Wq z<M?AMzut;CMw5Ks`)7ZjRwxTzyV3loK~dvXBXt1Gm4E(y?x+=!Kl`L6PX+x=Um`<~ zP~ksBi*WuXPXI646R4B53ejK9_4kO_G4U2nALsG^9~D43O2n#2JDzcq{FgoJkxwP# zn>V#T)ldBkKfnoU0sV#{*vIMLZUh^79!8nz_#k2DoC?QpNknvv#@Akwz1#9bxE;%q zjnxO%+BAB3F9)9tR-1zAdJR&I5pIwkMHr-Rh={lvq-(3WbQWNzQ;@Ik*H5WolVcqp zWi^5;XS;KbZx`#6Z$SylIbXC{cvtRp*SX2<d4{N~@{Gp@fU3D{RA#8CYFmc}fZGB@ ztjtvbIZL&4a^Z&))%>$j%-k*aRKV@%m_exrV*@`-njawW)ahY_?ZI!@prsCnJwC$c zJRlHe8BzNDoyXG;YI-K$3hr%(Q*5o>_NNNR0IA%LEyKDMLm89XUp2spm95xq;j=|o zpR+3x;C;*~{~RR+sT5@jEXu9%LN9~;EI9OHxnKB7-@DRrlnXGx>1B0e+kBhVe|MR4 zUlEw*`_bm!XX>G4(sw)j*CVwyu$#VR#d7-v9nd!q#eq^Msz0WQ_KdoL=u5opAGdsm zmhSJC0J-XW0dgwWudflc@E8(Y@i>{WB&u6-%qkXb)o~v!^kbTmxEUpis+N!oyGi?L zbEQsnXnwfB+2^$l7i6DK2YTf%^Q=VZmmny0w*|{ylFe++R|{_!&w=LAG+W~<uP%k_ zDuk)P?@V<sqb+*<>^uM$jmxf{qlbqPrVH20IdvhU3iGh?RzzB4Bf`{Q7tmyDN0iYf zH(?)(_!DHlsy{dj6Mg(2XW1Z>mUR^N8}mds0e?>4Ky5B+C9$oy+g8bSyE^on_wAZu zA)Z$T8lWXf8w&leD{U+@>3#tna!7VF=d4-{oqP5wU9Ceq&*n;n88fBqM(>7R-6}XL zO-ybWJUD5<IgN;;A=QBI;|FuA=y-6J;i|yqwil!ef)kO(<q2Gkx@I`g<xyR03v910 z&Npe^ScToZ#E-|}4R!9bcMID$ZBl4wg@=4hGJTStecn^Pst<T@*%BV)&j|0Ft>1Aa z09!k=WaVy=$q-?`itcV>(%T3HJG2PT^niGJg)%C~lvcn)k@UjnJ7BO$^YFVTTyqN? z>*n(TKFy~%cE;tc9K8k}+lsd*7B*U-y2B<_vHdm#xK3<Rfnx-*T42U~av>x1*3ey1 zYN3;Yj3s0Aat?hw_}pf0447uuJ)rIm`=)w`J~L6~#}qDmvx))XvR13TPMNOy7G>F} z0a)cf_rY7V0T-GN*Ol5#nzNjR-0XfbcfV%0dxyiy+HhZE)M%A@_R#~Nn$KDhxk!BK z4)u1o)n2}EgY5B?=|g4;%C4OBd@k2G74_FA)Hf!LdmrZ?BQX8-SFxewbC92!x;_b4 zg`X&E0hdGZ62<bO9$_iEMpex--S+S6>xTEQ5-B0>3j=lYChIH8h@I0b_l3X=?S1oe zU-;<(0aik}mKm-6?U??)ETT9?fZEqyxgT@~XF`mAaX9#%Gp{wHXtPDA)l@R;2WqQU z${+E#XFlo?s4sKyIhbJBCPz(X2j2KB&xK?RS4ZdHd?cGL*($&Kijp!{!WX{FNcHIy z|93E@#(jvml+xsXR;Y@yM`mbnAryn!41ev_z{nn%Wkk7^jbExxZZFoZwLk+N)%TT_ zWEQ~;Pfw=i-k|-1Y?(<({dqdt7UAQGMECni!%H!xeMnE#&h*>$8nU~q(AJX+a*&-R zNZ)Vp<f|^d8Tq%{UN8E?kLZV!uY6p3mPH+BxFFoV4$VfFy1VQ!?|`w|$%NhYK@6z( zF`MOCbN*3!am`XzI+d_tvxb`wEQd-@Ss!kLE3};`!K5$kKMZP_bstn%^gCELEVFzG zP$(>L9=AHQ0|DxvD$3kPVC;NYaLYD`({npT7m6uh!nm!{h9q-4w^M>$_xXXvP<41o z_8|s%HCvg|7j)s2bsxte@WrE<<RrXgl&)N-GUbb7VjZ^aukL2Vt+!Vu$r%^BahdZ{ zDD=u>)b|dz0XDl1ZBEeNv(@YJ=FQlKU@{T(H26He`{U9Ks-IR2UgHY<i9>787?>KU zX8cQ-ol28LKNwBC-CJ(l?CEJv&|pr>tFq|r;l5l{>@ZI7GeCf`Z5@buhPW=;R&^wW z)9Tu(ibFSgZfb43-(Cfd`mQFJ$)Wwkzn>b20=#l@hkx+!{pbK>o5>J3Dv5&Xkj@pC zaotN1{V?>k)40~b-K|WsThpoI>d_Td7i}zgjssO%oEUuFNHSO6aE!v_rch1R>{1SD zDYk=H;u}h+IcjQAc^)?JI$c2c9juyR6SHA@6;p%3Z_3aNBwCS!oBLs$PYuXtpiFM} zf-47<hIT|a#j7Iacz%NfKIOHG`f4K4^9C(Hr-GZuD2MdQDT(&<vzAb;yTN`%mSvx8 zSRh?>RxP2e2RZ`|ZTB1?fB;@G+D!?l&E7>{(?5Q1x_G{I=7KHgKC=+sD@>`NkV)i< zm~o3}*c7`@vhG-JKGgsZ>i3=ybYFZ<as5zwqrQmKy0P1GO4#=Eh!Mm4amc~KlujcN z<Eg&^^bz9i<a1Ba{<aY#0pWk)G%ebP=x5sD@UOK3zmKQE!!hgj=X7am(~o+go29ZV zg#WGEn@`IEO;XpS5AilYrb7l<R>JslAVtMk&fjL2omn(rWqhT~k^H+`(h9RcL-9yR zMv8rOiNS>(!ftQgEf9VNz|H>b(_n_kgen}i;4h#hnintrIPJ(;mJ5N?Y{T}NozT%s z25XCP+&F~KCuVNKUnqzdSY+AXi#B$nnY*!THZJ;IK+7<_8pMCc+6=$Sb`}?SPE{+J zPG%x2m)<N+n9j)b=Q8I@Wc!3`)A?&->;k>GCQ1E;TmOYoklr{u#%yA=mmw&But=)D zl2(7_JoBviL;-QVAfd5v9@gM-2E3ySFhIg?V{xOHTUM$a%5WTKs&n<Kq{==bs(<uJ zRyi6t6LE~OE&2f#R}CRY=9dauq3k>QSLD!nSg!24D1Sq&fLYWNY$#E)J$O%&{Dqso z(CV5K%h%>$SE7N8rrY+j%UwrD(7eJ?ooexr{rDbOu&-9JxR9)=DW_O1f2E8Ft4pug zXY6N5f{z<lGzN6FcJh9;C(eO{bh*^W=QI#zH$_oIL?K1B0hSeJtkr0`D7A6)H$RYX zMjRLY9EfsieWB>wO3=BP0wY}T(v&|Mm1VxmdF=iaR`(|W3+85lbhmGi>X*D43ghp8 zg&Q8HaOUf?0@OO)w^PSPaNJ8FjNZt(M55R{<(thb_f4L8cGH%o&8FfW%K}C{C58vt z&m0h@Zc!uZA3QwVd^AZsHPH9BbQZfdo0^5{W*}Pbk%=>xK(}I;-<_85ulJ|)mgfP} zux}FukX@bgUB-;96{0_ia()1;o6nv8;M?{l%nh<dswSZSLd{5W+2Hkt>2+kSS+OoX z73k`dYvP%sqrc<O9f^LrTYA$hm465C?H;|tWYa!l{F{b-?-$fuOSkkhkvbCVn_TkL zJ`^57H|y8Yr_8l`Tenko=w$>?K<U+e*ZRl#<puhK!}>%gT&rICpPQS);jOm5*QC=b ztyktYtfug>YQrBrE5!^AOzr4&zu|%=&sAu~&}o6;{+>12Po_eSoo?#rUVHxbbELvO zVGwL(#;+XHtsJy89GMw<I;ATD&#K+hY<h~Gp$E|^r=+x&!BuK1;Cqs68&AqZ)rNy@ zRTx+M=u_|;RD43~C%=Xq{A=aPWm?(x8cfB6KhiWTt!#q~GY(&mryShpb_0}G>Homs znulG&d@$gTn^r#ktt+=xx%Hkdx{Q_deZg8^N;l|W8>7OLi@r#}Eo>7Fo@u6mZWG}{ z(>#E{biS8ISZBc~GL4F$D|RUk_8IaiFb`ZuNO^N}(poKS7RZW-DI^)lXsOk7^uFHa zaUoi06r;Zc>Dq&mU<Q;2dWkD#$EV3P<uA?ZFG$<Q`21vl$2Fl}ZZ`L`ZVsAP3oB*d zpL6EBO`i=FotdGS`h(5hrh@LOqi1qy53YU&7rq;(a-STt2l`(7DJh(q-c3I$%R3lp zcuN3<&%4Pjr|EyAU~yvgscP<G`r;4OU{v0nD$CL>5IRNddNlagIej}n=gW@IXlL2k z%~~&Sajaj7IUB8X0NU_C;6?W-E9vH||1vA-hgJL)w!okL*BU;9tD|PI^@X!jwch^e zRk3bHm$Ce}VyieVf=CCAeJACqpQY>*n2Wq0;}6QlEgCeoGbOZ<D?S7c<!yZScD}{Z z3E?Vk(J`+Mf6d%{I{s!Zh4~3|K1+mn$oB3N8K^XLu2X_}%9=)F+<PzCh?xO&KF=k; z+G30^&x*#VG6lHVGKSa+@VFcPu_gbVx!sE$Opcqz#HP9B+h2iq<x|3>F3X6UsqiFo zg}vlQ-pZV3B3&42I1mQ`#LA>s{`q#TYf8^uF&5}bY$p3YhsryP2Cakb(?KIL=Um$B zR#g%($i@2+KZvGRxQV6JcEUqOjD$0nrg?|1^V_{*<S2cE#r_e`ZyRc#f7n;mD$M@D zH+an=S%E&RJa>&5Watzut7W6Dxb{$8v0B6Z*62q1Y421BBSrdG`CfB$_`^m>LFvxD z8!Zi@b6lkT%gxp+uVtm|w2NaN{X8hphxceR`I&GFxsKiprGbj#VDX!l1a-~@pECQE z56A%PW^)Kt9lvi#qB*9JfUkk4eAC6gSr?z!as}TqD?2k?VJJdv3Pl8ftj5XFYJRRc z-gLS*KGtAtM^zIjgHx+y1Kw}W%?4kIS`=)_9+7F>=Ttl_sK2JF#Uxny&70Zp8RwSq z&rx^xbr6AxVyGUYzl8s|i5Ujo;^2nyzx+q=Q4kKaNj8Yb?Tw}!j0`q<F6U;|+>~x8 zP$<_VVNe}x19l)l<17W0iR80y?MYHaaLwEu93kK#1($TIHA?u|=X_Q1Z*CInU!+Do zD3uAxfOn0Viq|blmbbwM3n4P&rppm$lT>NCh#3l*=Q+X8E<(6fh$riKb73}{`{NEc zv|h-SdG+Q48^<(rk`U)!W(1LNAxP<Y8Os62aB_tL0KZcdag=UPcxkHIikw|HXwp6n z`{AuvXsOa5+)z7VR!Vp-m7W3ZsxyzK;xqD?ygpxjem7#}W1Y>bAWl>wXyl%+OR30w zEXr5vK_-wrldQc}tIyF3_i({O1>nrr%P<QR@}UA#K|@NOJjQE!!o8jPny~$145f{N zeL#2ZPQ6vS4vu2+6Z!jVBLCDonudZmmuP>WvC6{&C)0)VCvIP&4(J#PJgFH_jb2_H zjHEHB{`I3w1b@gXo9|GQ$R!e{@l;8%V5O9#t2%sy#8DD`!2&*%)ZWEBx$45nzU00z zB~y;<KgXB07;zRk8De>R=)#f_lm1po;DzG!Jjj2Bt9rFS>B+7030?X8=O8BEAHd7t zDNn>0^sL;v=ytHL{`SOX{)ZnnsRQ8PI<#&GZ9z|hMteV*sW5oZq0zn63z(m>qab10 z;6T+D?T@qS?Q&b@_%jcx_BrpeX6uR~Iyd;ymk<VVyP3vhJ_ET<v@Gp~P6<}Cnln8q zo&>?>xB40f!GM-zcmCC*1oy=(=65(g*TNi!>GY}`oBKX_bkNTlr$+&XLpR;HqGRXc z@drs~T*3ORs#W2yCl*GTap5O>egOD9n1z?Fx?cK%?<Vs1B3F_1u({+pb3lSe|K1=H zA-iF=_0_&&buqTi+_xr&ng9zhU92e})uyIozV-#RWl{)3PrC%S-IzvU@`rejKWde5 zbZoC(mO=OyUL9WVGT3zpOVCth@^v47d+yPgH#}wc&|%@vVpq%==Q0wA+g;zn>np-3 z-Lf6?loIG2`BWAgVw65V8BAhWliOFngS)dpL%V|B8|=SS;MS|JELgJb4jeu{YCBk7 zPCpBTT(?`?(FON}I;3(LXMEm}TerBfN4WUw?diszcV_7iUbl5?>9@JWhZ!`#c2ae` zGs#a89ButIMttlWW>BMdBka)JzZ3@9uub!GKp)0-Tvd0NW1Pv;<7h-@-uSQ(I6B_6 zO^PdI)-!rB%>%j)-8^%CFWNY>q#zH0=Y+Ls{>~d1Uf7*w0yw;>cgTZ$u>F4=_P~nO z1)FODdgbZ0=~+G9=#a&86KT^+A2*w|#9~a4tI7=GcUlM^^2)2pzV?jAp{a=CzBi#R z$EFkp^Y*ipKz3`zy}!r1lL!9J<veWs=tLa1Q~u=TD$-G3JR*n-Bs@LpWpP4g^x?ib zxv)TmkJ+B>?Ug0CFk@>NjB^z=SHJec3}JDbTq-nIr>YzHDK;1}?Us?J0rw5t!}eFy z-nM+}dTy=mmv~tfrCL$Cs}nwWGUx`BDAONRC|w|Ulbkwi=6bL4idV_FE;siVAZ5sa z;6Wj$pEFK$t4jJvr-}(|8Q~Ut+8RvN1tJpZ;X=yQqc#kmyty_`0jhGXQJA<zW4ykN zTQ0yxo_^#%@$M-Rbp!nkvWw5dorp8q1LAgu{Sf$fUNHdsVgJ=;EaU+teGpmt(*vA9 z#*Nkecdj--5cLVvi`wbQAFN^aVr<qXl4CTTx#gV!)6ADY2<@S`aev5ZIMlDp{|N*4 zngJ5w;kFYSUSP0-N`C(U!=F+7X8!I0+4cLDEfW0h?!TxX>fb+bdFmCj_&e3|*ZTiy z|3^vx7wBj036yRS|8s}kU+4F?aLQMxVa4Eq{p_DF;uF4a@mKM80EjRb{-CtK6kjDC z78*?2_<xWSKcG5ey&{6&{`!|}e}LA5Nma6`JpE6LLV6VRJMGyMuk-h}5&y&Bzg7GH zt|IE?l{^23*K+swlLIQ8NJyNPzu(6lrlH?NO(S|)X{{0&NayFf7^<o`a^Bayr<u#D zDJhXIe3CTI_Fj@E%r=fdCpSBQ^WgYqbo;D1{r+}w83DZL()5GQ>?2xx`L_ADTVO6D zeq|hG^j)#-$DcgOlcK54JO`v+i9Gt}(NFEjX$mg<^)Yjel7Qa(CGvCXza0U&&okH3 z)ly$b{y&k^iF5-R1>Z!XN`LzM5hwd8%tysZh4^U<(%+9)iAV(Sjgwr?f3QP-F3pI{ z2$kuUG5+UhU&ZJ>Qd|5s_-*2!?4+rSPzxLA3Fw1j|G_Rs#QCYRlM;)PEbZT<d$|1{ z0sp^eFjeqTfQZPukA03O&)a|8SEEkY%n7*eWctw;WTbQHx2Bl(Mh&$2`42X3r~6M# z#2YNn$)KFDe!T?UDMnKXv-sbhhopKep*>sP@=sG27dd;d#Tb^~bCOsd6e{~gB$`O4 zf1>-;<hj$5*W5<!s_N!v+qpcu@Q@9M(_<>3i>^Ymw+6gYJrTYV)?lF8z@m@;)N0)3 z@jBmRE-YM0c3E>NZsC+rK4JE&GjQ=P?F>3@VBbcU!Rc_WCw_m1myYzJi6t%N>5CEj z)?gVh#|Rb`HAO=kkMWNP%Hyh1)<gq$Gd783V6{y8?pU9DZXoWx2eQO<oLqSfQ~3Zb z?zEpg%cx)Eg4`n9|NgdsT7e^9A!Zo}v3gwnZh%13fQe_(wRy^?A-*+A@-miID%Wfh zv5BtQ19=!S37yYHY8=OmnM##Bf$Da#Oix78;?jF4XuirIUxAWdPB?|B+5PsV^1BiC zFahGsyD9(N_*4&IT?|`;3|cSf?nZ1+jrC<bw3vA~-Sf3VD*YPr{2U7UMgLeVhoZRa z{Wb61jZ67dv4K3aR835$26&gd0srWW>kge|QKgTLM>`dYF$CVpj0LB=f%%-Lxn>3x z@(rea8uC64CeVKA?Kv;b^BbH$`MBMtlCM{rnPQkW`u$C{p7nh3qNV|DV|jx{*pjM{ zt-$lL5%YJ4;!)u)GG%&=dE!X}DowWPI1TqO?9_-8eBY}U>HyU*53a4cA;F$Bj{oU- zrSnyR)g#-v8busRLF$=h&y<hD-jZJhUcfDyu@g8(1$74qL6SI>v#jA7P^@EoX#Re6 z^xz_HfpkY_UhaM7)76&zUx$%1-UAIE0(1QemYn?~LsXxKB#!OEKxXJzBfO_?uN*rF zMbSf~ZcxXZiq1uhnT%$4wCmPMKsEUICszej->zH=<Y0-t(&KCJ$FzR_6PCNnh&TOi z{hRsUeDsg_ST118^gE7Xm6AzTOD7~n>g8TvGd%6x$$~CJd+$EfjS3<38UcnheA}s~ zW|KXmW(fGQ?AB0$3?x{ouS;Qme&5l_cHz2qs`!Kah|soR<0klq#vqy!U=j~ITxjFM zxMXi+8D&11|5{Xc`{dZtJ~K0#cwVX}irg}djn{LymWMR&-g(ey-@~v!9Rix4^S}bC z7isW_i!<_OGu!HcQa6~C#UEp*kw0G!Z3-<&1AMTOGZ!ST=<wLA8Z4L1pXIEY=n?>o zF9$}OE5r$zuUsCjifL$rwwh8RO#C8mt_N|hpbNuQ`}G-Uha&6w6uI7|uSL<c_ou|% zl4S?o^#kA5j${}=mOou|v^lzp?F!i}$Y@IxzJOMajzvwCEicy49{#f7iXP})c{Q;v z7)1%No~Ts?-WaE3Qi%F*O!F)e`HD?0IJMVIlJS%zv|M4*A1+WTZiTW4??&=Wiai>~ zdsttv>8`Id@d^!3Dd(;sge1bd)fG9L$F2+>&`Z&+(G-?$x$jDQ2gWrkN(pOG$T&l` z25!UHarYzH<F6C%I`MwM#}V#5ttRn09Z}}QFnY+FxHTx0TmZkt^|*K9u#vC|1_KR+ zCBiqs3ovK?URP|iMw!5qlj$utXOGy4iRxubzQdZy&+owHx^Hg1-D@~zN8KsjZVnH9 zY!DMT{!!aS9X4}(IzpA_0e7#m?tWjS+?X#oX{Jo-;OFBWv#Z*p-F;KEZIG}=#}s_o z5eJ#$So-#m=;=!bfcnXD%m;>}0v@S9itA?wFnAr;RMV^|29OP{XfP!8ma&i^p!9DP zoV+~?=udpu_5(fgEM~wOS-uCy1-XiWgjVx%CE{d)D`>^-a1NBcprLkt91wq=o6zWa z{XpNb%VY~{iJGWB)y>K2Nim6Qiy(aZWJ~wq3;o$5U##cc4p+9Me4d}Ck+taReRm%f z>D5MM+DSX~{@_9HJEHN+m0EtLlC8Fx`-(Mn2wxi*7e|6Y6Yx#sQy4<#(+GnSPJkLC zl%8Jdo@&2eW$|7Zt>wD>xwN<wdNpo-RDHEMFwCJwuW}8I$&f9O>ui|BStsGG)n^kD z=EsuIK20r_5icuJk8PC0Um>cCt5kB|nGzedNB9xsNdW(9)h}~Zr<Jaj`ZLf*3a(Yt z#+@~}_ls#fh>Kgg>d1F0b)A7UKCbFdpA<BRDZVA242**tDlfue_qqcFAPr}|?{Z5S zwLMo&-+*b9OI8yco3wbcE+`y$(RS>&=pbe^7|ZTDezDJ8g7)NcrLGRk5U}!mTeF~M z1n~w+`qVsf9PC?nL|bHRexfTpCzkep<M$Y!p&;X`jnE@=?C@Hw)|)pg^x*N)BetfI z^*7Rv#quVrZzoGo!+7k9Vn`fxh@(&yB_3dhQg?k1XRpKqsL`7x{)*c9Tw2X(**GB+ zT%t4fR-sivGI_4pGumBgDvn@KsIq#A7*+agOwspMH^njaZ6A7FxRF&W<uIB0v!OH7 zr@s0ew8W#U1s@yCx;uXxjM^f^fvpm8cBF_uD0-F^(srHQ!sT7@f<>oi-YryULs;l` z?>qQPI!qwfNEfG$i{jk^EI2aV&d$=3Uk&)c<Ss=>MW$l=4pKDu%ZgUqEN#ez@%w>V zpI}l1G0)Rf%ij`kI`w!=Jo%N=AolFi68urS-o25WW010(+eej*XP<#*$yG{Ph}%w& zmet*g?l5D|6duS&aU2k`+B9rfR*SH$-foy>3F^k0pEeKmszI0BX4n17xhfW2v%##a zMU6f<dUMfWYPj?{S~y01kf?v2H9@8hlQk~IeuOES8}_mMnQ($@o!tkH?AS&s|5?V? zrrOxo!~Ao;CRPjU(Hh^JH(~I_oEG3AhW5#vpmwWC?A&C%y0d=rf@|G??0E=j0sAik z1%BGf`Lj);3xF%|j-a{TvFEIQvsy{`-Qe@#xUIzA`PS~3XG(fx;L^CDjG?$qIl|ez zgITzTL-(Hf#fAOuY<_x@db2n`v8j}|@9~w&?o9JHYG&oTMRaphL$cXv(kzz*l<zqx z2>8NgDCukUD_O}oc{QirxN&r*ZK4Auiqi|<juY?)+H1LG<n@E+re-TbIYa)Z(~`SO z7GK=FGgX94)lQU>ge?3>$q1np<BK-*LUC(Rem|ApyO5#ym@aAKy@>Ry>m$N4Q$Sdz zZ5zyHZnF{nL={hV@^HbTReAZwi><77=i6MB_ol^3>)gKoI<way@G>s1__B<&UFi0z zM`U)pm~b$-mTB{Zsf^n=Os5%L3vE@X^9_`0B$Z)u=;HIztrYq~=r%uB>|}NNn^AX# zW(wr0h5DbSFEW^N^?MCE3T>Nh6I8n{da-$ySYV2tV&D>km%1Rx%X-KMWekEhba?|y ziRMKdsF@1n!sZp3l>T-Db4eCyb_@IHKZQ+flP*V(UcRlf$pi$)%GCI4SEW*>67;0+ z<o8fh+Y0XlY)&vG08Qp@ueM?NcFZPTiSL4ObGVdufdyItmA3u9+73%-7uw6}g<rU` zzxvG>ND10y>qafZjrKO!!Jih39pqyn&Hmru^igrF;J4M>EJg>Vdno6$_WDje=gPJ3 zPbQ-mN{qX7?U`0w!}jLO6|K^uC7q<iX@f6ngZXTx?OrR@96pcG-IU5{;Q6UJl92d@ zLl|DF%cymw!M5OQO)S>%6@8iZKrDnAw?=(9edq0*%f=Kn&9>$KdBu#+9RDK$<P|J3 zO1592DDLcd^^}(^Bb>`~%qcU2qdoRd_xjZ}4ISY30`)iIw5q4uGZ|mGZ>3WP!V=@R zqAi|FJ3Q<~3hH?}&vGL~O2T|*(j8_u9e2)*LYep-cfNLbb7JvBjIj)Nk4JRHHl{V3 zbZB{_1?LT@bj?~HKH~vjzPRY>SQ(C@0woTofah;L0TC0cOQ)n*2MwQ&(=>)DU--T< z4!!oK-5$#llXRLhBG_RnAECwXX-n_nr6^giXA9#J@>FKaqV6=p<#%NZo^)98Y#e7e zW@}R|U8B%APU7_oazK9{d364cV$Y<fs;s$sN4kpS=*KI7fu)rf%@R4$X3gWk3RYs_ znbY7oo|4BY*$M@!Z8NoPA-$CT^IJ|3qCq?QEuEYf?wo_SEbrtr)<ZE{`0I*T1KOQz zsF2f^VfB8d7ktndA#)L=>eg*T$E=|r&Fnu)V!N)N-aQPvcf@aJ2<3v?Bsz!E07HTC zBq$*F<M&~40uSQ4zzApa(92VZA^z~bUp~ij<*8g4-3FGJi(4^KyiIwtwxq%j3jwQ@ z$7oK!W}{1%9YA*zIL^0s2$n`#7)0_zGt)<ILGjGqNcXj_H_+LPyW}FcmLi6jy24Oq zI%P5|Rvb1Nhb~pGuLd1;+*;Z3n_kCfLB;i2G-DUX$vrDG#$E8&{Z=oA?0P;eUBEz< zhmT6yn-gzfw|@1U4tm`>D^h8aeo~N2??K7toBGjDl^Ra2`TfPIU4Az|G|j%w!J^|a ze*{fsTrlcT7pPZkzrE>s(_^q7=Q;&@6GgYHlR(OCO>3CkF^#>iDU-hMP>IOyKw7|u zzV(bgBjXhMbqS{Ozp<}>$U?5V=$3x~-d&nTvpYBRkn%>Jv5XmH>XGv~&*EsAzk5uq zZY%__(ct*O(_Y3Vx^|5DBycF*idbCiDu||e__ZDJlYlBnFP0IX9anDu%{?XW^6sG* zrnc!<w$DEfJIU*ALujP2=5ct;##<F$rE`xnQH*f%Mp9;{X;)d6xdM$dYd2%~T+AB> zau02C4m!$EaIjwS*e-(QZ2LGaLFJ3W!RtZ>EzD)e?0@izPB(?u`z0gQb-GjLX|YWy zmBq>pPy)%9K9k2T6~nJ5MjIIoZL$08fNxCqa@i$|+UD#rJCU#td{+d{E;i`$Gmh?= z{A0?r_RdugN#|h;ORder&UdX~#(V9aKB}lk9t0ftFIo@Xw&|(Z1Lvykn%IxR-bHn& zmE3hy-7nqtO0Sxjl`dO;w7zSJFz<;2iWgR`8Jpn``M-)$d@5etF!(zqGGn+hdxv!2 zx1z>?a!%R8q5KNg7+|+thN){l_@dwJR=t7V!)Pu$Nfd;*yXInZ9jgYHUeTHbj>f|4 zd(Y}erEm@@Pi}VIYY$Fw^CO;k-A(5c1Qg6qVMS0)vu-p}^=|WntDTz!Zyi_K+aMim z&HfNV%L~V(Px)R00_Ers9EwZ8C)byw8AEBrzEQk&z*`BVE1z7A*!D_Vf8BbX5Z7v; zq2E;z9w<5A(A^iG!GKyf)4kTWgDr*UZMCtEkPgkOp|~I_Ef5#uGU=uF<N#j%6;&%) zAwuM?Jvp9gVY@P4b#mPe3|B7O$qSI$Pkdr~Yi=jKc>L>pIAJ=VN`mF-qVCN}d)4S2 zNRaxD1O_5uaNzyyl&7K@^eazrnG)gL=b`{#0aDoUdd#&qzfFd<#IP0hX;uwxWuw7D za=iwc%NK62y^F;9T!w{57((F7KCR~kZig^o|EDXr@r<q?j9^eIK-b0L-52cB@dzid z{-AM)2JYjFn>t9shZg*8!^#^X)U6Rx*qhO$*R7IsqL+K|4o_R|mttZ-q3h^q24qKS zNOl-Y1oJ^TA@^9Zw@bPxc9+`%)3{z`Cpm!LLH1PN&CRlUPb!8{hC#y;9lSUw2`swC zt!d=;>X8rp=7sCR{ft9^q+8hIuuWm%6<dG2`^0@hbSRULey03t>cxE0YY#qQpsS&g z<Go^nHkaz_w%ff^Bl0I99s%Av-?L^cQ&yv@az`D(-hmD2mMTI=h%&0rPzDPI)+cb4 z=!A~#Cn50gZAiKmxk!`R7Ie7{w`HP0e<kU{?epl=@P5hedtl4r_K@#jLPn2UI2v&7 z%5H2lR?Hvux^;FSAK4DXfo<S?B)|OT#M}Rh?2#@v3+8~>C3}~TwiiRSbmBm0fx=V; zv^u{-zwEn9Xmb_%66qYz<t?xK1}nnu{hTPtl?`mfsA;k4oN^hzC517sJak91t6>!A zCc7`#77k@IfbW6K;}_oF6yMAk?vkG@zA+t497IvrBdquGvRFhi^dDgxAR^z_1Tj_k z?Oz-5`erQD7n*Nklim0p@pbm9mOoPjeVC-j#f?ShX%Wb!^187?<m187T$ZbMMGQNV z?xf#w`9n|em9Et_N=g=YHv#7<XBv-NT%F1-)D{)Q;+G64C-D~qLe-rzc#R#CJ)&?3 z_=)K-%yD<+t<c9y8;T#TL8p38pzb#n-9XfF5&>T$hh)x<+jIfe8sKZtt<D(506#yk zvQg`rkIj@E*zamh8%&XQJmFzT-cUblhV0q{G0rR$Nk646qmqJ$T&#v*=z@w9M-^y$ znR&pjO<e-8bAU+F_4N2N4dLTo)5UTeDTyAvt!qh%OZq9g@G!b^EGMO)we%rhx*Xfj z#W;*tZ=gG<VslP+=f10zBH=x79Wz`r42+aE+EhcTqZ%{*y+}%$(bdOhl(K&3vOMVV zW(-F$C=$h1T`B>OxiB8Hd7{!$rpYpV?_>QAj~4c&kCeAq^e#<v?!$(Mj4-W@J9<;z z=@60GugEFoI|XJ~eG5jTEYhFCNe_5-M_KBjWCTf861ypWj)yCy5RN#|bjWIdSx#K& z7}C)SM$46fY!3q$f{)2EFOLE%!4#X~T`LD_i{nBUo@JruA?~EIUng11V#_=UJZfe@ zw>SEvNy<xZE5m%!Q$=prWXnyX!ZP?hX=RS^*cTpHpGB;KOx6-mx(hX?TevA0!tFia zt!=7~pVv0rds`XZJuUVql~M|gAr=aO2%pR_pY4m<z+?WdI(wZfs-?0j*jdg@aRTt; z?HXCms0qL(%^8=`I!#JHTr$7S(APihPF?76>Cvs*ohbL?o}=_5DEm%`#~~KXqty49 z!V&i?E!F_jMxB)gow$MEIqAs*f(2Ura*=<m_SPv#A>4=Ub*lVuM)S?DZ@_p0reepd zY+YMF`x9AgR#rO5J_&)xBylBP`aG72`l83(W&E~)lm#YLuRh2YyQ#QyM`KYvASezi zPdNP@r~im>&dg;yBB(cn)XJF=`qR=he~4-EnhVp8xZwS@b2;|~i8*GP@tA19`{<~z zE=MB5Q6spA0U!@;w0@f!$Q#x2b(w--w~TWDr;Qf>tJ^)+^afHP*ZMR9u{O1+Q!EM0 z1EnwjWkzBT8#3EP{~dW_!YaE{#G{PzABn+C-`@^>H_{l^UV8Yz)vM11r^N5FgBXEj ztPEuf-|>}dFlhD;@Qa3OYu5|6&L|4bV^0xmQ!QO}M$@A!FEb^!&6gQCjc%C|V(<E9 zsRnXdiIQu1eZI+ouHkNC-LZ50v})ImN8%A%lv>;QzTR^2v)(a81v$y!W~D3)`MYgA z`X;*7II@UenFRwK9p=^(L9OF_BjSdlHK5~t`pOV|J#ygj-3`PCOyKmZxWU6#AMnav zfo!D7yQ?wzaUk<o*c^_V-{kuDRgcoJw3!Hmq5qAowrOTR>&?z8z>eQskz#XSs%uX` zLg0``tbF<TwutrvsPEYovLQ`fM!EdZ?FX(IPrn-T_(br;J_+hGq_z%O(cN$lzcAQW z%MN?Us+HDTb^Ii^SY)Iso@cxKEPNS|9aWPfWVG3VtIPA{i_5@KlSUhH&6#sAhG3tR zuOi%YkE=68wJPnTwd_32)Gccg@K<|bve5DJvxZ;Ym%>KnWZ>SC-4ITbd+_@s!+y=T zVFLW+z%;5uBiN;}OND&lZ*$@Ct?XlTz5HR)1Dm7YN6q|r>89AagqDy^)8dl9a9FMc zCj>{UI!VzJud*S&YKy`R1CxVF1o-V4alJZAUh$KRXd^MeV}g*MoQGEf$<wHG7)*{9 zvNlm@fN86#Gsn4;BuieUFy!jAk!^1Z8$D&C3P7f8k_})}l;yEw%rbsf13i<Ww8y)F z*Bjz0iwL-_U1Gb*mc;$d3o$<>-u;6zmzAfog5SL=-0ev^>M&bSZW;+J3x>c7OxnU) zd27B<x6-iY<uC&5KsfFuMRn82=7@`hQiT%$ocZxr1NHu-*S8XX;ckFIRI=jsT=CNT z07>m~9hPVZ@7fa6;_!hkjR&VJ__8xp>0<Vo5!#f7UR}95fVTEk)2Y`QSD_pcaVQ&u zO{WdHyY!P1S&$ma&-UvQiKrgd1g(y&zJSNe3D+E(AiiZ_eK*U+9^AUU*>UkJkggYU z$*A<DJgJzMfSB0fbEVE`Pp;fhwr4Op83ejI`sX?Lq_9}%!{eeYbRKtMvt0#EZ~xSy z|Jh)wj_X;+AvdMYJ;J!$sz%8)SCNc{A=p;g7tW!VYqPoU`>=vp8pfuhGcFQ;Cv#{G zUUZbL>p`r;4IhsQx33I-Q(APW&&?d3ihoNs10<A}dzF+dY;hIJbIZs?sF6&cUIfeC zez%gB>jN146k>ol<De4WIsY*#O$~YWb($BF9WMYlJ#tK&03%^Od>GvHs{TAjGKQ4r zkXmuw)$y0ZaOWN))?5j~B&gl!Sp<Q)PenkD^)mA>hB(bH*1xcV{TMa8`Ub<Xb{4c> zgLo!%)PR{=v|y#y<nGcBZ-|Ha2erJ<`QH(vyn}x)As#}%arMe?tTkB9fjxQi;b#@Q zr;EH%H<896{ivQ@>Br)?TuAGjuTgBKl*n?p=;SnUbZ#{64YoZecj=fGeLmf5t#>-O zYQA*~tPK`+n#U+-h#dd9oTsu&R(iglblQe>EfeI#Uw%TM)W#VbnxN1pGsT;h-|ZKC z4D#j*(-yO^E~Pjx)U2<Y*r}vH%m|)qTt_c*T28xgU-Ze)Del?Z62v`#m&p5Cd~x-1 z`dV4x2bz2{Tub^B!y;7?rK5p3A_8C+5vAT#Ugw8pGgQd4O==of;hMpBYPAPBZE@^) zyStbb+g@Q3C|Mc^oElSq6=>Ok^g+*aT9=ri*l568(G=jlb{3zmQd813nv1KUEgZR* z5MN$bQV;ipx^a3wBn#x9Hd-rC^z0t_%+8Rm>#$k6`7C{&7$mNK#9B`KY~8hXUzmki zM;Jy2u1m2LK6dfDoD%|!L05@zjC^f2PlCXObc-j;+Ed>%vYbWh4)&(uC6cEtz+MUv zGEShr<>Yvs4@JgM2F<9u?aNEAE{i5l-N_lpYek2K-HXfzFgeMA#m!x!FKgd=s7Uhe z1O>qh=Kf&jmBxiV0z<wja%$2HQNU*3`}ywig}>tKHo)RkhNk>&QC>7)6xkH-ISBm8 z(9Y9kk=$l%bkg)k$Zx3TTk{}}4SSa%jG1d+@OA0D8)i@8$!)~ss&3@F5JPTHw^;-@ zMwiX;2c~cvDXLq8Z~uPjaYsLg#zOBg*aj8c7P1U$h^F9E4#wTJi<mU~E`SN4)zpvg zC5{)IAyyqSiJa_Yyk#@}LTFb=x%$z2lXdCLr+<QT<)C|Dcf{tobUCVJ>PQ4*{&p-u zKC>5$9!%mPd{H^n&}?SZc)q5TuWhPGOoCf~qu*t6MSWv3TJzhVU%UXOazL$pvE;r$ z?z$lRD&qPHk#wywhv|S%SQOLlQ8A`T95-DnDh-}f%uBA;p4-|Mix4KNDJ4NCV{=rF z*b#y!jJOtQ-Oi&8(Uce2n=2Nd@Hp&NUW7X}ecT#MLf7k6<$lI85z*|UF-YOi;6v<) znnNIGzEJgrA4>pBaozGsG+;vOB^Jm&$6E1f7KN%m`a0UZhvLo+3~qgiCb@u@v0$0f zvnnW+?4<N1hl5hjXIfXVmm6h>t++$hgV&C%;)@={HOYk+s%5vqy;R5n{Sn_2LB~r2 ztj}VKL}v;(f}SsV_1Fq`HrI<<`mD&8bC8!!QycIupcf)$83XKaztS(BxEJ^iRdj-8 zRJ`=Ti+hz}>0Y17A@<J{uEss~whrWR50t3^527B|geoSD*eC>^`XG6vIT=<vw^<mN zCOL%&rai@h8>H<z8}3ImG(Y6AdM9i>h~a>F>LEtct>sacpcx+3&7Hjr83zz4I}7%D zuO_6`d~uafsa2A<l+2*`wu1k$8!@+kDpMpUb25mN1A5OPvP(C~>VKTOtdx*H8Xezs zn%dIFqy0KDZ|0zBhJz)ZCz2R*xoDUgzyQ?=Hu8N+Q7Ap=xJs2^x=N(+u{8m<j`Agn z{f3vogDZ;r;|d3RqCSKpI+`Wn+JvNHrb-ADP0K%~j8qY!czWQW_$tjlY5oIT)$t(( zlqOc;MrJtKF#RM+*Qpn%_(iNh?RJmk0*8$n3I34*db}YwxhB-B!u^apKCF2j_mfEo zpl^jM(_?{CFgD7#yzPIrbLQ_*wrw02A)3e7Dr1)|WKXh1sgS5BOUa0A+1H859#i&0 zlVXS%*|#j&jb*ZCiI|xzV-m)UrC}JOcRa`Q)ARZZ-sAoCKJWWHuI0SW<G!x*^SvB! zbVgQmNeP=!@q|zv9zpr0U!)W!2SW>ACednGEe%K6oVne5tP+edPNOmN<t#!1c7gEY z*nE2l{UX=pWUy7c`eLhl5DL^Mh<)Fzl~XdG?0PHl8`Ft8kp>;wT1-oEYRuM<9Q))g z=GmXf2B(TDc!wF_mVumsP1_L`h#j>6066{RO91UEG>JYSKt&;f>vaIhjw+pvvF%Ij zoGPPn!4o^!m3tURDn#McGI)keTyl!ucr_L5CTLOCfNzQp1r&hpho6hEcO4G`gzK== zc<*9I!Xc}B&%-po7yeAPCeS`zUHgNoORDp9puyntE3aQb^;?%GZ-TyUi<-8?4fy$N zRfPn4BgYz4-o#h4u|CLI0(mL6!h9i<1Yb4!#;KdT^hfT3beoSl!^q%>KQnv#&FO)E zZ@MJ6e3Xv;iEYky>lV?MWRqrL^Ta-_f&;}qxot(|Zz5^jstGIUv0O(1S3iyjA?K5U zX`~j)GWubTvWpT{%nCh8hYOrF(0-_e16A15!i0^Qe|(EWF)^LhEQ!dfy2-!sdoiBZ z1Zk;ME-6P^?_!mF^h><+%zMl<rI2D81Kq*KH&sWeMd{VNwK{X0`bE#T8?45^ZL^tC zTKz(|z`_sql=z-4VjO(NApDx-h-xnp$d-r|xif%X8TPoUV|prSKKV080TTsim7e|j z3ngsjtYTkBv2#E)!2m}oWzh?VVsSMwf=Y;**Jkz6tO+;=z=XE~@r_sS&C{1Tg1OKS z4S;c-xsi@m)0v`c?R`Sc`Zmgt&srxSVHY0;>aU!-4qDE>*lX+Rg;TImseBQ|GcK*) zEkb7Jd=34UC|@=N3jA*VYixC$$>svSS7V-843NHCcHI{?(z0Q0N?JC;8-e_U9zMII zy}+@Tr|MZu<`3zv;Ja|*ZdW~ZY$>??$az2(3u{%K$lKtGzjyDyanc?=B7Q5wig~3C zm|JE-txno1c;P#i-O~>aN>zgm`FpuslVO$Z`h-D1b7W2r(ltZO+z&QWB^i2Oi+Y0P zFCh}K*BT|Rct(9lkK~(xw3p1{lF8Hag+s=n;~us3B(U#DX6T1*T-!0(e^$PglvO1v zGQ-k_;{!uhx}S#Vi-f--Q4*opu@x8gLWRf7vY{!o-5>?R3nMbzV77t3x#<J0Ka-NE zPU!zDr~jbFz^DZm%;c_H%$CpdY(};eq-d0if7eV1^ZO2rUsJF?n~v{I&ZJ#d3)&EH zpZQpi1>@&(o3G}CpLsBdprBwtlXcz`W`f>o(1{d`UO~bj0{Wqhtl(qvzOx|u;qScL zE&d1f+2B7os!77a26nQ@a!uG<%(jB$<@~d@eibowIo8y4OZZg(1i22~$y!5rW3t&U z!6)sr-)UuadI=!YV|Qg5&(*fxRR9GI>yVk{?&UQv^aV9K=u2%$*Sl>-<0_u)v!3cp zY!u4=@^Y(e8NDIBNvJ(*`@wA;I`{+CPi&fLTGae<KleF~ojM+%Y)U8&ik_?7-ky!3 z&(~I^5}!)fcosfRX<{B}X>U-rp1Ty%O7mDw|Jm#~B|BOWpdRJLJYKTm(`?2iqAOlv zrBuBoTDg6apyx56KKd+3|0}(GF+0bMq+}t}1){i+f9O?*VJ4&Ll<#9xC4bz8LjxDk zJfKHgYmPG>aR3kzK`$w9wO8s7`$YUfNhssPw9@*fI}N1JVjCY}B)Azx6p}<nW%3ca zmC8y7+0t!^_hBtZi1kCBo&n7pyH^@_Cia$bh&H-QTJ4kCw~F=MrdE|9dz8uS>L+u; z?|RRR*rzW8HMltNcZcSVtOrJrI6RIo#EKez2y84qk3C>PmLxNX8q<06g`y<2y^GoU zmXF87@AM=IN}YO;Zdy2S&xt;pGa=N~@^neoKh8a3bLQ^AcOmpyaH+j~5j)&2G!_R- z*iB%ekv#6Sm~QR2-me4;udc>UDZeJ)5Vfw@yAe#^3J?@Jem-}&jX^A=lZNZht-8s` zSul=}Y}Z8PYzwdZraUE+Rv-1$a`+Rzg)Ed`mR=Z8AFi7@mQKK0+bJ0S$nu_vO`*;@ zc_on1ka|0Zjp~jyd5hAlM@=d)FY*W}J3Q1XB&_S)mjIMkhi}7kq#1x`txd?xP=32X z2Os<3g*ABT@oMG5=X3RT@Wqpx*`KFL5xx75iAzS9`^X>o(W0faOHj3g|FgX**BphN zL`4?8WWlC$)GZR#y-5)cO7ePB3_O=wsE9ZZ{XA^x(^1p6jlq|TGQf}1NrRA+6y&0* zWI;ofCLd`HU-GbX6_bp#7t;y8vpjiL0@L=VhxASvToqRaWIYbAy<M!A<$20p$~kF} z`zao-v~LqWQ`7%iXZ+N*=(evKil9948<rN^1+BU~EuYc)rLNlxL6rFBp>exbH_hO) z1ox^aM4q(tGecEko!0;sJ~hg!)-7OTxQHgZjn3LnwfW@Sc(a#@JatB1;dPk2r5QU< z`~@xn1hhEJ5VI64*b+TYBR!jAj`32t^NdA|jv+Pl2kvVh)4nhws=r_PE$|BLE!VH7 zC1(xZqu*6j)`nL%X&|0)IsED1^X&a97tniiMyPu(#7V%X@vhr!9M@TG4yp?7$(V>a zxx*t+Sm%?6cm}ydl2KC@c}i+FSgZ@pVD_)r$%XvFPA)ytv1C-Pyhoeb15aB|MX?16 zAAfr(dKXyF*wGCM&FlXN)Q+XLup`Cndif@$oW_Zk`9(f9^?d+c;f$5?wr+WBM#)6R zD+pN0K3)f~`&a0Wmd65TjO=Xq$|}SJldRm=L&-56F88|3rjlA`B$N&hqwRCaMI0Px z_x&uIqqAO%zh*|YDQ{lZe$*i<ko9=;iP;i$z*K^TZ&`E|)KfIAYmX8zz=-=KzI9K} ztYtAax7C4o!aJ1P#DxZ#K>SW;`X}umX?GXiom&ye8089KPwM<rsk#swx)|iZExRZU z?^mop$sPDr`7@PQ*{8$R!%F$3yJZi^c}r5ufz2h0oqvd#HNmtB+GkqDdm_s`I9C=V zoPGJz535oO)9IjPLSXge=At<~B%qLUbPo<XdTDp#<eA6m8KHG}giK^e<xc-67hlJ8 zGi5gQ+&wI!B30GG_G+JX@k^OweLGPxG(y?-$wEKWtPFs1qPgjrhE1X9EuaG$aGjs) z%o;&qGBqw~+IX;-wQQ~_%238ubI-C{<eG@WopAJc_hM3KYgb*2Kx$;3U9UhngOk~e z^uM@-(`4;qE~i9V*fiM(G?Jrsch3}WGSDqFwW<O{h^S%QJMi>#imLha*SJ_7U00ou zalmf}2lkmb?H&jF5B>!l?K-hh*J``VFc5eLi@nPA@#cuu6NROY?YZ|TaXlBXI6<ie z;hXOSPPb#-8vP@H_CnvyzJ|BzIlmA&<`0X>)5#^PtGWGpbQ2%s{bz_)(KaofHX|Z; z{AZZM^k7>lUuzWu>nou5w5JlB;a~z_{yuVVSvT>EW2EeM#w417?RoUq(rAQ4CND7N zq>A>1_EsSB9W8XMM`N_y*{iYQ9*!IT+&iLy9W3xu0sPVBEm;wJ`?Y9M`b;e<WPk|+ zeZueGWm&<vGsHz89R=9~s@rf#hfTvgyqe-oA~j)K84O0~1YmrQ7H9H|D;uolg|oqc zqj<o8r&>V$%vADZjsewXR7_c+xt`)-jW%WJWtTmf<-VxFk>z;+Y-|ZR?8&Axr6bSU zngfMo)2s16jrh%V1~HuH_&Lx~yvw?4BS6hGnC&M0nnb_f?V;x<3$y)O%HOBXvTzn$ zNil?R6`G`dUIK~TP#^S0E!X(ERvI9su{>tYYcYF^=$GnJgW>*8jLNS0hwa(l2`h{f z1W)R16l**O*Fe#(9BCqE2gWtzQ|J@};*h<m92`zi&e*wL>lsR53E760+c|0(S)Rmg zY!UarjpN4%Spj21@r`r@8d`end(Dcq)+RAvfan%HQ+I7F{YG*X`br}!idgug$3HhL z`diR|8cigeU`Nllf9Qgp>iH<dUyFiqvAUM63-7Bt_X+SKE&V3JGy__aZes>V#_GlV z*8C*&Yegg9+=*4$X=t<2xD#Oai3qja;a2Z3P9bqK(^~72{jjDppwF|`sMIUZvfNs5 zR`er1eu0#TA`4PDIWgT@%1Knk-=@eMqD@ZkX)$MFU=1g_q_hd2By~z*UCnuj-vWqL zn=hMuW75mXQqjuOH`Q2ZrxT$kX>sqRQvHBi=aqN+#?^Z^S2-7deu45{c*!XC=NL~X zCLA@M1sjPAcGJOdcbDf!&5tnK$J69eJ!M}014rvJkk{?5PS^q3%q_y;oJaiflzA_V z95`^BPfYpyHSvrCc;J<ia~c!J-U^2U_+e(Ab$_f2Xs^p%VV|<&%w{rsM?wYno&Oi6 zCPsIpE@8YFL%d^he$3<_L>s|y0Ck)*JfN$eBbX9-vKidxM108a_rKDP8Z-P?LIm2b z-zqX*$~B9>(_=LC4{Duyoe^}FCh%&L|Dw5##V;{zd`Ho<hhf?O|L*_e@~@EjABh(Z z1yW|R*ed|9v9qAuyMJpO7&3(Yw4=<|@E2dN|1R1<%o)!%AcGOAzE{-PlCMfXLmZ-0 zh&-An>#J)hqlK8OEn=q1lRyg%kF>)t$V>dba)`u2id!$r2~_h&g9OUwoGodPJm-qb zcQn3v$TH;Ch_Z3PNSrI3=K^Lj!=w#oFDQllxA=nbM9M%sRU%)wKUG?WgXaXq7(W2L zabqd-knW9gKMyK=z<Q;i0j0jx?5fhUtU@#Q*o<dDPXAVl9N}G)Wm;K(;|vsV3LGK1 zLrfFy`9ath^!}~>IB1f=@&Wy?=hSnUD#eeFfeUMDvq>GI));5l5TQMZQHL~kUWOq@ z@eTwSA2N;~Wf?X~B8cUX)^wRMs7~xJfz(5MTHIvVmO<)lhxGUPI|l3CFrq4ch|doQ zhHYHk1#s9zX);p3U%r#UAwG@u7`BhD0|JMHj<`1?2jj2xMjqlbHS{2N55G}8WWo)w cNB4pIs`{$e?v1N6#79gfSIw`KUv`N84=}7AT>t<8 literal 0 HcmV?d00001 diff --git a/file.test b/file.test new file mode 100644 index 0000000..e69de29 diff --git a/hyperglass/command/construct.py b/hyperglass/command/construct.py index e0c4b01..bdb800a 100644 --- a/hyperglass/command/construct.py +++ b/hyperglass/command/construct.py @@ -39,7 +39,7 @@ def frr(cmd, ipprefix, device): msg = f"{ipprefix} matched large community." return (msg, code.success, d_address, query) 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}") return (msg, code.danger, d_address, query) # BGP AS_PATH Query @@ -49,7 +49,7 @@ def frr(cmd, ipprefix, device): msg = f"{ipprefix} matched AS_PATH regex." return (msg, code.success, d_address, query) 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}") return (msg, code.danger, d_address, query) # BGP Route Query @@ -67,7 +67,7 @@ def frr(cmd, ipprefix, device): return (msg, code.success, d_address, query) # Exception from netaddr library will return a user-facing error 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}") return (msg, code.danger, d_address, query) # Ping/Traceroute @@ -93,10 +93,10 @@ def frr(cmd, ipprefix, device): "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) 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}") return (msg, code.danger, d_name, query) else: @@ -137,7 +137,7 @@ def ssh(cmd, ipprefix, device): msg = f"{ipprefix} matched large community." return (msg, code.success, d_address, d_type, command) 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}") return (msg, code.danger, d_name, cmd, ipprefix) # BGP AS_PATH Query @@ -148,7 +148,7 @@ def ssh(cmd, ipprefix, device): msg = f"{ipprefix} matched AS_PATH regex." return (msg, code.success, d_address, d_type, command) 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}") return (msg, code.danger, d_name, cmd, ipprefix) # BGP Route Query @@ -168,7 +168,7 @@ def ssh(cmd, ipprefix, device): return (msg, code.success, d_address, d_type, command) # Exception from netaddr library will return a user-facing error 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}") return (msg, code.danger, d_name, cmd, ipprefix) # Ping/Traceroute @@ -185,7 +185,7 @@ def ssh(cmd, ipprefix, device): msg = f"{ipprefix} is a valid IPv6 Adddress." return (msg, code.success, d_address, d_type, command) 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}") return (msg, code.danger, d_name, cmd, ipprefix) else: diff --git a/hyperglass/command/execute.py b/hyperglass/command/execute.py index eb7d91c..dfd06af 100644 --- a/hyperglass/command/execute.py +++ b/hyperglass/command/execute.py @@ -1,4 +1,5 @@ # Module Imports +import re import sys import json import time @@ -14,6 +15,27 @@ from hyperglass.command import parse 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: """Sends input parameters to construct module for use by execution functions""" @@ -124,7 +146,9 @@ class connect: def execute(lg_data): """Ingests user input, runs blacklist check, runs prefix length check (if enabled), pulls all configuraiton variables for the input router.""" + logger.info(f"Received lookup request for: {lg_data}") + # Create global variables for POSTed JSON from main app global lg_router lg_router = lg_data["router"] @@ -138,50 +162,79 @@ def execute(lg_data): global lg_params 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. global code 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"]: + # Initialize prefix regex check class + ipc = ipcheck().test(lg_ipprefix) try: - blacklist = IPSet(configuration.blacklist()) - if IPNetwork(lg_ipprefix).ip in blacklist: - msg = f"{lg_ipprefix} is not allowed." - return (msg, code.warning, lg_data) - # If netaddr library throws an exception, return a user-facing error. + if IPNetwork(lg_ipprefix).ip.is_reserved(): + msg = f"<b>{lg_ipprefix}</b> is not a valid IP address." + return (msg, code.danger, lg_data) + elif IPNetwork(lg_ipprefix).ip.is_netmask(): + 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: - 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) - # 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: - 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": + + if lg_cmd == "Query Type": msg = "You must select a query type." - logger.error(f"{msg}, {code.danger}, {lg_data}") - return (msg, code.danger, lg_data) + return (msg, code.warning, lg_data) + + # Initialize general configuration parameters class, create global variable for reuse. + global general + general = configuration.general() global d 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": http = params().http() try: diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py index d34a586..64cd320 100644 --- a/hyperglass/configuration/__init__.py +++ b/hyperglass/configuration/__init__.py @@ -165,7 +165,7 @@ class general: ) 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_ipv6 = g.get("max_prefix_length_ipv6", 29) + self.max_prefix_length_ipv6 = g.get("max_prefix_length_ipv6", 64) class branding: @@ -223,9 +223,9 @@ class branding: "text_limiter_subtitle", 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_415_subtitle = b.get("text_415_subtitle", "Something went wrong.") - self.text_415_button = b.get("text_415_button", "Home") + self.text_500_title = b.get("text_500_title", "Error") + self.text_500_subtitle = b.get("text_500_subtitle", "Something went wrong.") + self.text_500_button = b.get("text_500_button", "Home") self.text_help_bgp_route = b.get( "text_help_bgp_route", "Performs BGP table lookup based on IPv4/IPv6 prefix.", diff --git a/hyperglass/configuration/configuration.toml.example b/hyperglass/configuration/configuration.toml.example index 427f166..95a21a0 100644 --- a/hyperglass/configuration/configuration.toml.example +++ b/hyperglass/configuration/configuration.toml.example @@ -44,9 +44,9 @@ text_location = "" text_cache = "" text_limiter_title = "" text_limiter_subtitle = "" -text_415_title = "" -text_415_subtitle = "" -text_415_button = "" +text_500_title = "" +text_500_subtitle = "" +text_500_button = "" text_help_bgp_route = "" text_help_bgp_community = "" text_help_bgp_aspath = "" diff --git a/hyperglass/file.test b/hyperglass/file.test new file mode 100755 index 0000000..e69de29 diff --git a/hyperglass/gunicorn_config.py.example b/hyperglass/gunicorn_config.py.example index f7eba13..cba6ac7 100644 --- a/hyperglass/gunicorn_config.py.example +++ b/hyperglass/gunicorn_config.py.example @@ -3,6 +3,6 @@ import multiprocessing command = "/usr/local/bin/gunicorn" pythonpath = "/opt/hyperglass/hyperglass" bind = "[::1]:8001" -workers = 1 # multiprocessing.cpu_count() * 2 +workers = multiprocessing.cpu_count() * 2 user = "www-data" timeout = 60 diff --git a/hyperglass/hyperglass.py b/hyperglass/hyperglass.py index 5e83f8e..6493008 100644 --- a/hyperglass/hyperglass.py +++ b/hyperglass/hyperglass.py @@ -11,7 +11,7 @@ from flask_caching import Cache from flask_limiter import Limiter from flask_limiter.util import get_remote_address -# Local Imports +# Project Imports import hyperglass.configuration as configuration from hyperglass.command import execute from hyperglass import render @@ -19,44 +19,14 @@ from hyperglass import render # Main Flask definition app = Flask(__name__, static_url_path="/static") +# Initialize general configuration parameters for reuse general = configuration.general() + # Flask-Limiter Config rate_limit_query = f"{general.rate_limit_query} 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]) - -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 cache = Cache( 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(): """Function to clear the Flask-Caching cache""" with app.app_context(): @@ -77,7 +60,6 @@ def clearCache(): raise -# Main / Flask route where html is rendered via Jinja2 @app.route("/", methods=["GET"]) @limiter.limit(rate_limit_site) def site(): @@ -86,27 +68,26 @@ def site(): return html -# Test route for various tests @app.route("/test", methods=["GET"]) def testRoute(): - html = render.html.renderTemplate("test") + """Test route for various tests""" + html = render.html.renderTemplate("500") return html -# Flask GET route provides a JSON list of all routers for the selected network/ASN @app.route("/routers/<asn>", methods=["GET"]) def get_routers(asn): + """Flask GET route provides a JSON list of all routers for the selected network/ASN""" nl = configuration.networks_list() nl_json = json.dumps(nl[asn]) 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"]) # Invoke Flask-Limiter with configured rate limit @limiter.limit(rate_limit_query) 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() # 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) @@ -133,11 +114,17 @@ def lg(): except: raise # If 400 error, return error message and code + # 200 & 400 errors are separated mainly for potential future use elif value_code in [405, 415]: try: return Response(response[0], response[1]) except: raise + elif value_code in [500]: + try: + return Response(error500(), value_code) + except: + raise # If it does, return the cached entry else: logger.info(f"Cache match for: {cache_key}, returning cached entry...") @@ -147,5 +134,5 @@ def lg(): except: raise # Upon exception, render generic error - log.error(f"Error returning cached entry for: {cache_key}") - return Response(errorGeneral(4152)) + logger.error(f"Error returning cached entry for: {cache_key}") + return Response(error500()) diff --git a/hyperglass/hyperglass.service.example b/hyperglass/hyperglass.service.example new file mode 100644 index 0000000..222ac26 --- /dev/null +++ b/hyperglass/hyperglass.service.example @@ -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 diff --git a/hyperglass/render/__init__.py b/hyperglass/render/__init__.py index 770372e..6d5a7bf 100644 --- a/hyperglass/render/__init__.py +++ b/hyperglass/render/__init__.py @@ -36,10 +36,8 @@ class html: template = env.get_template("templates/index.html") elif t == "429": template = env.get_template("templates/429.html") - elif t == "415": - template = env.get_template("templates/415.html") - elif t == "test": - template = env.get_template("templates/429.html") + elif t == "500": + template = env.get_template("templates/500.html") return template.render( # General primary_asn=general.primary_asn, @@ -77,9 +75,9 @@ class html: text_results=branding.text_results, text_location=branding.text_location, text_cache=branding.text_cache, - text_415_title=branding.text_415_title, - text_415_subtitle=branding.text_415_subtitle, - text_415_button=branding.text_415_button, + text_500_title=branding.text_500_title, + text_500_subtitle=branding.text_500_subtitle, + text_500_button=branding.text_500_button, text_help_bgp_route=branding.text_help_bgp_route, text_help_bgp_community=branding.text_help_bgp_community, text_help_bgp_aspath=branding.text_help_bgp_aspath, diff --git a/hyperglass/render/templates/415.html b/hyperglass/render/templates/500.html similarity index 92% rename from hyperglass/render/templates/415.html rename to hyperglass/render/templates/500.html index b9bbfca..c838602 100644 --- a/hyperglass/render/templates/415.html +++ b/hyperglass/render/templates/500.html @@ -25,10 +25,10 @@ <section> <div class="container has-text-centered"> <h1 class="title is-size-1"> - {{ text_415_title }} + {{ text_500_title }} </h1> <h2 class="subtitle is-size-3"> - {{ text_415_subtitle }} + {{ text_500_subtitle }} </h2> <br> <a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">Home</a> diff --git a/hyperglass/static/js/hyperglass.js b/hyperglass/static/js/hyperglass.js index 6ef47e4..ce80879 100644 --- a/hyperglass/static/js/hyperglass.js +++ b/hyperglass/static/js/hyperglass.js @@ -72,120 +72,18 @@ function updateRouters(routers) { // Submit Form Action $('#lgForm').on('submit', function() { - - // 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(); + submitForm(); }); + var submitForm = function() { progress.hide(); var cmd = $('#cmd option:selected').val(); - // var cmdtitle = cmd.replace('_', ': '); var cmdtitle = $('#cmd option:selected').text(); var network = $('#network option:selected').val(); var router = $('#router option:selected').val(); var routername = $('#router option:selected').text(); var ipprefix = $('#ipprefix').val(); - // var routerType = $('#router option:selected').attr('type'); $('#output').text("") $('#queryInfo').text("") @@ -207,7 +105,6 @@ var submitForm = function() { </div> `) - ///////////////////////////////////////////////////////////// $.ajax({ url: `/lg`, diff --git a/hyperglass/static/sass/custom/custom_elements.sass b/hyperglass/static/sass/custom/custom_elements.sass index a0ea77e..4405427 100644 --- a/hyperglass/static/sass/custom/custom_elements.sass +++ b/hyperglass/static/sass/custom/custom_elements.sass @@ -21,6 +21,10 @@ body .title, .subtitle, p, a color: findColorInvert($danger) +.has-background-danger .footer + p, a + color: findColorInvert($danger) + .footer p, a color: findColorInvert($body-background-color) diff --git a/manage.py b/manage.py index 69d0ae0..32bec03 100755 --- a/manage.py +++ b/manage.py @@ -1,75 +1,200 @@ #!/usr/bin/env python3 + +# Module Imports import os +import grp +import pwd import sys +import glob import click import random +import shutil import string -from logzero import logger from passlib.hash import pbkdf2_sha256 -from hyperglass import render as render +# Project Imports from hyperglass import hyperglass +from hyperglass import render as render + +# Initialize shutil copy function +cp = shutil.copyfile @click.group() -def main(): +def hg(): pass -@main.command() +@hg.command() def clearcache(): + """Clears the Flask-Caching cache""" try: hyperglass.clearCache() - logger.info("Successfully cleared cache.") + click.secho("✓ Successfully cleared cache.", fg="green", bold=True) except: + click.secho("✗ Failed to clear cache.", fg="red", bold=True) raise - logger.error("Failed to clear cache.") -@main.command() +@hg.command() 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 api_key = "".join(random.choice(ld) for i in range(string_length)) key_hash = pbkdf2_sha256.hash(api_key) - click.echo( - """ + click.secho( + f""" 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` 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` -""".format( - api_key=api_key, key_hash=key_hash - ) +""" ) -@main.command() +@hg.command() def testserver(): + """Starts Flask development server for testing without WSGI/Reverse Proxy""" try: hyperglass.render.css.renderTemplate() 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: - logger.error("Failed to start test server.") + click.secho("✗ Failed to start test server.", fg="red", bold=True) raise -@main.command() +@hg.command() def render(): + """Renders Jinja2 and Sass templates to HTML & CSS files""" try: hyperglass.render.css.renderTemplate() - logger.info("Successfully rendered CSS templates.") + click.secho("✓ Successfully rendered CSS templates.", fg="green", bold=True) except: + click.secho("✗ Failed to render CSS templates.", fg="red", bold=True) raise - logger.error("Failed to render CSS templates.") try: hyperglass.render.html.renderTemplate("index") - logger.info("Successfully rendered HTML templates.") + click.secho("✓ Successfully rendered HTML templates.", fg="green", bold=True) 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 - logger.error("Failed to render HTML templates.") if __name__ == "__main__": - main() + hg() diff --git a/mkdocs.yml b/mkdocs.yml index 5d053f4..6b71239 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,27 +7,21 @@ nav: - Installation: - 'Installing Hyperglass': 'installation/installing-hyperglass.md' - 'HTTP/WSGI': 'installation/wsgi.md' - - 'Reverse Proxy': 'installation/reverseproxy.md' - - 'Running Hyperglass as a Service': 'installation/supervisord.md' + - 'Systemd': 'installation/systemd.md' + - 'Reverse Proxy & SSL': 'installation/reverseproxy.md' - Configuration: - - 'Configuring Hyperglass': 'configuration.md' + - 'Configuring Hyperglass': 'configuration/index.md' - 'General Parameters': 'configuration/general.md' - 'Branding': 'configuration/branding.md' - 'Devices': 'configuration/devices.md' - - 'Authentication': 'configuration/authentication.md' - - 'Commands': 'configuration/commands.md' - - 'Proxy': 'configuration/proxy.md' - - 'Blacklist': 'configuration/blacklist.md' - - 'Securing Router Access': 'configuration/securing-router-access.md' - - Caching: - - 'caching.md' - - Rate Limiting: - - 'ratelimiting.md' + - Caching: 'caching.md' + - Rate Limiting: 'ratelimiting.md' - Development: - 'Introduction': 'development/index.md' - - Extras: - '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: name: 'readthedocs'