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: .. code-block:: python 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 of ``json`` along with the ``j_username`` and ``j_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 the ``JSESSIONID`` 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 and ``JSESSIONID`` 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 the ``JSESSIONID`` 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** .. code-block:: python 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""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** .. code-block:: yaml low_code: version: 2 steps: - http: uri: /dataservice/system/device/vedges verify: false - json: - jmespath: index: True value: "data[*].{_index: serialNumber, _value: deviceModel}" **Credential** .. image:: ../../../../_static/authentication/images/cisco-sd-wan-cred.png