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:

  1. 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.

  2. SD WAN Manager then requires an additional step to retreive a token. The token header label to be used is the X-XSRF-TOKEN.

  3. Both the token and the cookie must be included in subsequent requests.

  4. 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:

  1. 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.

  2. Since data is used requests automatically inserts the header "Content-Type" : "application/x-www-form-urlencoded".

  3. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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).

  5. 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:

  1. 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.

  2. 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

../../../../_images/cisco-sd-wan-cred.png