Forum Discussion

pperreault's avatar
5 years ago

SonicWall SSL VPN Session Counter

Just created a ds to count SonicWall SSL VPN sessions. Locator FMN27M. Would love some feedback and code improvements as this is my first groovy script.

  • Not a fan of just dropping the exit code as I have. There must be a better way to implement validations/error checking and output appropriate exit codes
  • Would like to see other methods for counting the users. Maybe matching on the string "User Name" and counting the lines that follow?
  • Could see this growing to include user session length
  • Would be nice to only apply the ds to a resource if ssl vpn server was running

For ease here is sample output from the firewall and the script.

=======================
Active SSLVPN Sessions:
=======================

User Name     Client Virtual IP  Client WAN IP   Login Time    Inactivity Time  Logged In            
user1          10.10.10.10         6.6.6.6  1799 Minutes  0 Minutes        01/23/2020 09:29:52  
user2      	   10.10.10.11         5.5.5.5   460 Minutes   0 Minutes        01/24/2020 07:49:31  
user3  	       10.10.10.12         4.4.4.4   368 Minutes   0 Minutes        01/24/2020 09:22:08  
user4          10.10.10.13         3.3.3.3   224 Minutes   0 Minutes        01/24/2020 11:45:54  
user5          10.10.10.14         2.2.2.2   170 Minutes   0 Minutes        01/24/2020 12:39:37  
user6          10.10.10.15         1.1.1.1   13 Minutes    0 Minutes        01/24/2020 15:15:49 

 

import com.santaba.agent.groovyapi.expect.Expect;

hostname = hostProps.get("system.hostname");
userid = hostProps.get("ssh.user");
passwd = hostProps.get("ssh.pass");

// initialize a variable to contain the actual host prompt
def actualPrompt = "";
def sslvpn_user_count = 0;

// open an ssh connection and wait for the prompt
ssh_connection = Expect.open(hostname, userid, passwd);
ssh_connection.expect(">");

// capture full prompt e.g. user@host
ssh_connection.before().eachLine
{ line ->
	actualPrompt = line;
}

// display the ssl vpn sessions
ssh_connection.send("show ssl-vpn sessions \n");
ssh_connection.expect(actualPrompt + ">");

cmd_output = ssh_connection.before();

// read thru multiline output
// rows with 9 columns are user sessions
// increment to total user sessions
cmd_output.eachLine
{ line ->

	row_length = line.split(/\s+/);
	if ( row_length.size() == 9 )
	{
		sslvpn_user_count++
	}
}

ssh_connection.send("exit");

println(sslvpn_user_count);
return 0;

 

2 Replies

  • @pperreault good stuff.

    If I were to implement this in product I would probably write a regex with capture groups to match a valid user line. That way you can be more sure than the 9 column line is actually a user entry, and you can use the capture groups to easily dump out other items of interest.

    This is a pretty common pattern in our core DataSources, most of the Nimble Storage stuff follows this pattern.

    The regexes in those scripts might look scary but it's pretty easy to make in something like https://regexr.com

    It's pretty solid looking for a first Groovy script. I would consider exiting the SSH session before you process the output, no need to keep that connection open after you have the data in a variable.


    As far as error handling, you could wrap the initial Expect session setup in a try/catch. You can also use exitValue() on your Expect client object to ensure that your `show` command returned a successful return code before parsing the output.

    Here's a pretty extreme example of a Groovy script with multiple return codes for specific issues:


     

    import rocks.xmpp.addr.Jid
    import rocks.xmpp.core.XmppException
    import rocks.xmpp.core.sasl.AuthenticationException
    import rocks.xmpp.core.session.TcpConnectionConfiguration
    import rocks.xmpp.core.session.XmppClient
    import rocks.xmpp.core.session.XmppSessionConfiguration
    import rocks.xmpp.core.session.debug.ConsoleDebugger
    import rocks.xmpp.core.stanza.model.Message
    
    import javax.net.ssl.*
    
    // Server details, required
    def host = hostProps.get("system.hostname")
    def domain = hostProps.get("xmpp.domain")
    def port = hostProps.get("xmpp.port") ?: '5222'
    
    // Account details, required
    def sender = hostProps.get("xmpp.sender")
    def sender_pass = hostProps.get("xmpp.sender.pass")
    def receiver = hostProps.get("xmpp.receiver")
    def receiver_pass = hostProps.get("xmpp.receiver.pass")
    
    // Optional, disable starttls by setting to 'false'
    def use_ssl = hostProps.get("xmpp.ssl") ?: true
    
    // Optional, set to "true" to enable debug output in wrapper.log
    def debug = hostProps.get("xmpp.debug") ?: 'false'
    
    // Optional, change default authentication mechanism
    def auth_mechanism = hostProps.get("xmpp.authmech") ?: "PLAIN"
    
    // time to wait for expected message ( in seconds )
    def timeout = hostProps.get("xmpp.message.timeout") ?: 5
    
    // Check for required props
    if (!(sender && sender_pass && receiver && receiver_pass && domain))
    {
        println 'missing required properties'
        println "xmpp.sender, xmpp.sender_pass, xmpp.receiver, xmpp.receiver_pass, and xmpp.domain are required"
        return 1;
    }
    
    // Used as a lock for synchronizing
    def received = new Object();
    
    // Setup a bogus trust manager to accept any cert
    def nullTrustManager = [
    checkClientTrusted: { chain, authType -> },
    checkServerTrusted: { chain, authType -> },
    getAcceptedIssuers: { null }
    ]
    
    // Setup a bogus hostname verifier
    def nullHostnameVerifier = [
    verify: { hostname, session -> true }
    ]
    
    // Setup an SSL Context with the bogus TM and HV to accept any cert
    SSLContext sc = SSLContext.getInstance("SSL")
    sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null)
    
    public class NullHostnameVerifier implements HostnameVerifier
    {
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }
    
    HostnameVerifier verifier = new NullHostnameVerifier();
    
    // Store send and receive timestamps so we can calculate RTT ( in millis )
    def send_time = null
    def receive_time = null
    
    // Check debug option, setup session accordingly
    if (debug == 'true')
    {
        sessionConfiguration = XmppSessionConfiguration.builder()
        .debugger(ConsoleDebugger.class)
        .authenticationMechanisms(auth_mechanism)
        .build();
    }
    else
    {
        sessionConfiguration = XmppSessionConfiguration.builder()
        .authenticationMechanisms(auth_mechanism)
        .build();
    }
    
    // Setup connection config
    def tcpConfiguration = TcpConnectionConfiguration.builder()
    .hostname(host) // The hostname.
    .port(port.toInteger()) // The XMPP default port.
    .sslContext(sc) // Use an SSL context, which trusts every server. Only use it for testing!
    .hostnameVerifier(verifier)
    .secure(use_ssl) // We want to negotiate a TLS connection.
    .build();
    
    // create the client instance
    def xmppClientSender = XmppClient.create(domain, sessionConfiguration, tcpConfiguration);
    def xmppClientReceiver = XmppClient.create(domain, sessionConfiguration, tcpConfiguration);
    
    // Add a message listener to the client instance
    xmppClientReceiver.addInboundMessageListener(
    { e ->
    
        // Get the message
        Message message = e.getMessage();
    
        // Confirm that this message is from the expected sender
        if (message.from.toString() == "${sender}@${domain}/LMSENDER")
        {
            // Confirm that this message has the expected content
            if (message.body == "TESTMESSAGE")
            {
                // Record receive time
                receive_time = System.currentTimeMillis();
    
                // Notify the parent thread that we can exit
                // synchronized must be used for notify() to work
                // received is our lock
                synchronized (received)
                {
                    // notify the main thread to resume now that we have received the expected message.
                    received.notify();
                }
            }
    
        }
    
    });
    
    // Try to connect the receiver client
    try
    {
        xmppClientReceiver.connect();
    }
    catch (XmppException e)
    {
        println 'Receiver client failed to connect';
        return 2;
    }
    
    // Try to connect the sender client
    try
    {
        xmppClientSender.connect();
    }
    catch (XmppException e)
    {
        println 'Sender client failed to connect';
        return 3;
    }
    
    // Try to login and send a message
    try
    {
        // We need to use synchronized here to call wait() on our lock object (received)
        // This will wait for a message event from the expected user with the expected content
        // The message handler will call notify() to resume this thread.
        // If we don't do this the client will close before we get the message.
        synchronized (received)
        {
            // Login with receiver account
            xmppClientReceiver.login(receiver, receiver_pass, 'LMRECEIVER')
    
            // Login with Sender account
            xmppClientSender.login(sender, sender_pass, 'LMSENDER')
    
            // Record time just before sending, so we can get RTT
            send_time = System.currentTimeMillis()
    
            // Send the message
            xmppClientSender.send(new Message(Jid.of("${receiver}@example.com"), Message.Type.CHAT, "TESTMESSAGE"))
    
            // Call wait() to block this thread until the message listener has called notify()
            // or until the timeout.
            received.wait(timeout * 1000)
        }
    
        if (send_time == null)
        {
            println "Message wasn't sent."
            return 4;
        }
    
        if (receive_time == null)
        {
            println "Message wasn't received."
            return 5;
        }
    
        // Print delivery time
        println receive_time - send_time
    
    }
    catch (AuthenticationException e)
    {
        println 'Authentication issue. Check your credentials.'
        println e;
        return 6;
    }
    catch (XmppException e)
    {
        println 'Something else went wrong with XMPP.'
        println e;
        return 7;
    }
    finally
    {
        // Close the connections
        xmppClientReceiver.close()
        xmppClientSender.close()
    }
    
    return 0;