Skip to content

Commit f1d6650

Browse files
josgabferJose Duarte
andauthored
Add network tunnel monitor (#80)
* Add network tunnel monitor * Network Tunnel Monitor * Network Tunnel Monitor, created Network Tunnel Folder * renaming folder to NetworkTunnels * deleted umbrella wording from the description of SSEAPI * changed authentication URL to SSE --------- Co-authored-by: Jose Duarte <jose.fernandez.22@outlook.com>
1 parent a82963d commit f1d6650

File tree

6 files changed

+256
-0
lines changed

6 files changed

+256
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Cisco Secure Access/.DS_Store
2+
Cisco Secure Access/Samples/.DS_Store
3+
Cisco Secure Access/Samples/client-samples/.DS_Store
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Monitor Cisco Secure Access Network Tunnel State
2+
3+
## Use Cases
4+
5+
The Monitor Secure Access Network Tunnel State script checks the state of your organization's Network Tunnels (TunnelState), and logs the tunnel state information received from the Umbrella Network Tunnels API. If configured to generate email alerts, the script sends an email message about the state of a tunnel or the status of a Secure Access Network Tunnel API response.
6+
7+
The script alerts on these conditions:
8+
9+
* Secure Access Network Tunnel state that is `disconnected`.
10+
* Secure Access Tunnel state that is `warning`.
11+
12+
## Prerequisites
13+
14+
* Python 3.x.x
15+
* Cisco Secure Access
16+
* Create an App password for the desired email to send the notification from, save the App Password in the PASSWD environment variable
17+
* Modify the recipients variable in the tunnel_monitor_sse.py and enter the desired email address to send the notifications from
18+
19+
20+
## Before You Begin
21+
22+
* Create a Secure Access Management API key.
23+
* Install Python libraries. For more information, see `Requirements.txt`.
24+
25+
```shell
26+
pip install -r requirements.txt
27+
```
28+
* Create environment variables:
29+
* export API_KEY=VALUE
30+
* export API_SECRET=VALUE
31+
* export PASSWD=VALUE
32+
* export EMAIL=VALUE
33+
34+
35+
## Usage
36+
37+
* Run the sample script:
38+
39+
```shell
40+
python3 tunnel_monitor_sse.py
41+
```
42+
43+
44+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Copyright (c) 2022 Cisco and/or its affiliates.
5+
This software is licensed to you under the terms of the Cisco Sample
6+
Code License, Version 1.1 (the "License"). You may obtain a copy of the
7+
License at "https://developer.cisco.com/docs/licenses"
8+
All use of the material herein must be in accordance with the terms of
9+
the License. All rights not expressly granted by the License are
10+
reserved. Unless required by applicable law or agreed to separately in
11+
writing, software distributed under the License is distributed on an "AS IS"
12+
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13+
or implied.
14+
15+
----------------------------------------------------------------------
16+
"""
17+
18+
import requests
19+
from oauthlib.oauth2 import BackendApplicationClient
20+
from oauthlib.oauth2 import TokenExpiredError
21+
from requests_oauthlib import OAuth2Session
22+
from requests.auth import HTTPBasicAuth
23+
24+
25+
26+
class SSEAPI:
27+
def __init__(self, url, ident, secret):
28+
self.url = url
29+
self.ident = ident
30+
self.secret = secret
31+
self.token = None
32+
33+
def GetToken(self):
34+
auth = HTTPBasicAuth(self.ident, self.secret)
35+
client = BackendApplicationClient(client_id=self.ident)
36+
oauth = OAuth2Session(client=client)
37+
self.token = oauth.fetch_token(token_url=self.url, auth=auth)
38+
return self.token
39+
40+
''' GET API request to a SSE endpoint '''
41+
def ReqGet(self, end_point):
42+
success = False
43+
resp = None
44+
if self.token == None:
45+
self.GetToken()
46+
while not success:
47+
try:
48+
49+
bearer_token = "Bearer " + self.token['access_token']
50+
api_headers = {
51+
"Authorization": bearer_token,
52+
"Content-Type": "application/json"
53+
}
54+
resp = requests.get('https://api.sse.cisco.com/{}'.format(end_point), headers=api_headers)
55+
resp.raise_for_status()
56+
57+
success = True
58+
except TokenExpiredError:
59+
token = self.GetToken()
60+
except Exception as e:
61+
raise(e)
62+
return resp
63+
64+
''' POST API request to an SSE endpoint '''
65+
def ReqPost(self, end_point, data):
66+
success = False
67+
resp = None
68+
if self.token == None:
69+
self.GetToken()
70+
while not success:
71+
try:
72+
bearer_token = "Bearer " + self.token['access_token']
73+
api_headers = { 'Authorization': bearer_token }
74+
resp = requests.post('https://api.sse.cisco.com/{}'.format(end_point), json=data, headers=api_headers)
75+
resp.raise_for_status()
76+
success = True
77+
except TokenExpiredError:
78+
token = self.GetToken()
79+
except Exception as e:
80+
raise(e)
81+
return resp
82+
83+
''' DELETE API request to an SSE endpoint '''
84+
def ReqDelete(self, end_point, data):
85+
success = False
86+
resp = None
87+
if self.token == None:
88+
self.GetToken()
89+
while not success:
90+
try:
91+
bearer_token = "Bearer " + self.token['access_token']
92+
api_headers = { 'Authorization': bearer_token }
93+
resp = requests.delete('https://api.sse.cisco.com/{}'.format(end_point), json=data, headers=api_headers)
94+
resp.raise_for_status()
95+
success = True
96+
except TokenExpiredError:
97+
token = self.GetToken()
98+
except Exception as e:
99+
raise(e)
100+
return resp
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
certifi==2024.8.30
2+
charset-normalizer==3.3.2
3+
idna==3.10
4+
oauthlib==3.2.2
5+
requests==2.32.3
6+
requests-oauthlib==2.0.0
7+
termcolor==2.4.0
8+
urllib3==2.2.3
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
Copyright (c) 2023 Cisco and/or its affiliates.
3+
This software is licensed to you under the terms of the Cisco Sample
4+
Code License, Version 1.1 (the "License"). You may obtain a copy of the
5+
License at
6+
7+
https://developer.cisco.com/docs/licenses
8+
9+
All use of the material herein must be in accordance with the terms of
10+
the License. All rights not expressly granted by the License are
11+
reserved. Unless required by applicable law or agreed to separately in
12+
writing, software distributed under the License is distributed on an "AS
13+
IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14+
or implied.
15+
"""
16+
17+
''' Get summary information from Network Tunnels to detect Tunnels in "Disconnected" or "Warning" state '''
18+
19+
20+
# Export/Set the environment variables
21+
from SSEAPI import SSEAPI
22+
from email.message import EmailMessage
23+
import smtplib
24+
import datetime
25+
import os
26+
client_id = os.environ.get('API_KEY')
27+
client_secret = os.environ.get('API_SECRET')
28+
email_address = os.environ.get('EMAIL')
29+
passw = os.environ.get('PASSWD')
30+
recipients = ['REPLACE_THIS_VALUE_WITH_YOUR_EMAIL_ADDRESS']
31+
token_url = os.environ.get(
32+
'TOKEN_URL') or 'https://api.sse.cisco.com/auth/v2/token'
33+
34+
def send_email(tunnelInfo):
35+
"""This function will send an alert to the desired recipients"""
36+
msg = EmailMessage()
37+
msg['Subject'] = 'Network Tunnel Error Found!'
38+
msg['From'] = email_address
39+
msg['To'] = recipients
40+
msg.set_content(
41+
f'Connection Error found in Network Tunnel(s). With status DISCONNECTED. Please check your SSE Dashboard for more information')
42+
43+
msg.add_alternative("""
44+
<!DOCTYPE >
45+
<html>
46+
<head>
47+
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
48+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
49+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
50+
<title>Network Tunnel Monitor</title>
51+
</head>
52+
<body>
53+
<h1>Tunnel connection error detected at """ + str(datetime.datetime.now()) + """</h1>
54+
<p>The Tunnel Monitor script detected a connection error with: """ + tunnelInfo +"""</p>
55+
<br/>
56+
<p>Please check your SSE dashboard.</p>
57+
58+
<style type="text/css">
59+
body{
60+
margin: 0;
61+
background-color: #cccccc;
62+
}
63+
</style>
64+
65+
</body>
66+
</html>
67+
""", subtype='html')
68+
69+
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
70+
smtp.login(email_address, passw)
71+
print('login success')
72+
smtp.send_message(msg)
73+
print("Email has been sent to: ", recipients)
74+
75+
76+
# main
77+
if __name__ == '__main__':
78+
79+
# Exit out if the required API_KEY and API_SECRET are not set in the environment
80+
for var in ['API_SECRET', 'API_KEY']:
81+
if os.environ.get(var) == None:
82+
print("Required environment variable: {} not set".format(var))
83+
exit()
84+
85+
try:
86+
# Step 1: Create the API client
87+
sse_api = SSEAPI(token_url, client_id, client_secret)
88+
89+
# Step 2: Send a request checking for status of the Tunnel Groups
90+
tunnel_endpoints = 'deployments/v2/networktunnelgroups'
91+
tunnelComponents = sse_api.ReqGet(tunnel_endpoints).json()
92+
tunnelData = tunnelComponents["data"]
93+
tunnelInfo ="<ul>"
94+
for i in tunnelData:
95+
if i["status"] == "disconnected" or i["status"] == "warning":
96+
tunnelInfo = tunnelInfo + "<li>" + i["name"] + " " + i["deviceType"] + " " + i["status"] + "</li>"
97+
tunnelInfo = tunnelInfo + "</ul>"
98+
send_email(tunnelInfo)
99+
100+
except Exception as e:
101+
raise (e)

0 commit comments

Comments
 (0)