Cisco SD-WAN Manager (Formerly called Viptela)
Cisco SD-WAN also leverages a token based authentication mechanism. The differences between the Token Authenticator out of the box support and the SD-WAN manager are as follows:
SD WAN Manager requires an initial login to get a session ID. The session ID is returned in the response as a cookie. This is the
JSESSIONID
cookie.SD WAN Manager then requires an additional step to retreive a token. The token header label to be used is the
X-XSRF-TOKEN
.Both the token and the cookie must be included in subsequent requests.
A Logout of the session SHOULD be implemented.
It should be noted that the token lifetime is good for the duration of the session. The session lifetime is currently defined at 24hours. The session automatically expires after 30 minutes of inactivity. There is currently a limit of 100 sessions supported on the SD-WAN manager. The implementation proposed below will NOT logout and use the fact that our implementation will use a 12 hour ttl (This is set in the credential) and the fact that old sessions will expire after 30 minutes of use. This means that there is a 30 minute period where the SL1 monitoring will consume 2 out of the hundred sessions twice every 24 hours.
The code below leverages the three hooks as follows:
get_token_request_args= get_session_request_args,
process_response=get_token,
modify_session=modify_request
The first hook is mapped to the get_session_request_args
function. This
function is getting the session since that is needed before a token can be
requested. The default behavior is to send a request to the token retrieval
endpoint specified in the credential, with a payload of user name and password.
For SD_WAN Manager, the payload is slightly different and it requires form data
instead of a json payload. Thus, this function does the following:
It sets the correct payload. In this case
data
is used instead ofjson
along with thej_username
andj_password
instead of username and password.Since
data
is used requests automatically inserts the header"Content-Type" : "application/x-www-form-urlencoded"
.It stores the host URL in the metadata as this will be needed to obtain the token as shown in the next hook.
The second hook is mapped to the get_token
function since its primary
purpose is to initiate a second request to obtain a token. The function
provides the following:
Initializes the ttl and the csrf token. Since SD-WAN manager only supports a static ttl, this value is retrieved from the
refresh_info
parameter that reflects the value that was entered in the credential. Additionally, the token is initialized to a null string.Next, the token needs to be retrieved. This is put in a try block such that any errors are caught and processing will continue with only using
JSESSIONID
. (Some API commands do not require the CSRF token.)Note
in this example the credential
token_field
is not needed since the only thing the payload contains is the token and thus this field is used to contain the url of the token auth endpoint. To retrieve the token an additional request is generated with theJSESSIONID
cookie and sent to the token auth endpoint.The response is checked (in this case if an html form is received it indicates an error occurred) and if valid the payload is saved in the
csrf_token
.The ttl is returned (again this is just the value that is put in the credential ttl since SD_WAN Manager only supports static TTL).
Since subsequent requests require both the token and the
JSESSIONID
the two are concatenated together along with the ”//” to enable easier split which will be needed in the next hook. Note it is important to store both token andJSESSIONID
in the token field since that is persistent across API requests. It cannot be stored in metadata as that would not persist across the API request.
The third hook is mapped to the modify_request
function. Its job is to
insert the JSESSIONID
and the token into API requests. The
modify_request
hook is called for every API request while the first two
hooks are only called when the session/token are refreshed (in this example
every 12 hours). This hook performs the following:
It adds the cookie
JSEESIONID
to the session. In this case the token includes both theJSESSIONID
and the token so split is used to separate the two.Next it checks to see if an actual token exists and if so it sets the token in the header specified in the credential.
Is should be noted that auth_info
includes the token (which was set in the
previous hook) and the token_label
which is retrieved from the credential.
Complete Snippet Code
from silo.apps.errors import error_manager
with error_manager(self):
from silo.low_code import *
from silo.apps.collection import create_collections, save_collections
# =====================================
# =========== User Editable ===========
# =====================================
from silo.auth import create_token_authenticator
from silo.auth.base_token import *
def get_session_request_args(credential,metadata):
payload={
"data": {
"j_username": credential.fields["username"],
"j_password": credential.fields["password"]
}
}
metadata["token_url"]=credential.fields["token_key"]
return(payload)
def get_token(response,logger,refresh_info,metadata):
ttl=refresh_info.ttl
csrftoken=""
try:
token_response = requests.get(url=metadata["token_url"], cookies={"JSESSIONID":response.cookies["JSESSIONID"]}, verify=False)
logger.debug(f"Performing a get at {url} cookies= JSEESIONID : {response.cookies['JSESSIONID']} ")
logger.debug(f"Request response headers={token_response.headers.items()} payload={token_response.text}")
if b"<html>"not in token_response.content:
csrftoken=token_response.text
else:
logger.debug("CSRF Token request failed")
except:
logger.debug("CSRF Token request failed")
return token_response_fields(
expires_in=ttl,
token=response.cookies["JSESSIONID"]+'//'+csrftoken,
)
def modify_request(session,auth_info,logger):
session.cookies["JSESSIONID"] = auth_info.token.split("//")[0]
if auth_info.token.split("//")[1]!="":
session.headers[auth_info.token_label]=auth_info.token.split("//")[1]
viptela = create_token_authenticator(
name="viptela",
description="Viptela Authenticator.",
get_token_request_args= get_session_request_args,
process_response=get_token,
modify_session=modify_request)
# List any custom substitutions that need to occur within the snippet arguments
custom_substitution = {}
# =====================================
# ========= End User Editable =========
# =====================================
collections = create_collections(self)
snippet_framework(collections, custom_substitution, snippet_id, app=self)
save_collections(collections, self)
Snippet Argument
low_code:
version: 2
steps:
- http:
uri: /dataservice/system/device/vedges
verify: false
- json:
- jmespath:
index: True
value: "data[*].{_index: serialNumber, _value: deviceModel}"
Credential
