pperreault

SonicWall SSL VPN Session Counter

Recommended Posts

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;

 

Edited by pperreault

Share this post


Link to post
Share on other sites

@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;

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.