cancel
Showing results for 
Search instead for 
Did you mean: 

Pulse Secure vADC

Sort by:
This Document provides step by step instructions on how to set up Brocade Virtual Traffic Manager for Microsoft Lync 2013.
View full article
This Document provides step by step instructions on how to set up Brocade Virtual Traffic Manager for Microsoft Exchange 2013.
View full article
An interesting use case cropped up recently - one of our users wanted to do some smarts with the login credentials of an FTP session.   This article steps through a few sample FTP rules and explains how to manage this sort of traffic.   Before you begin   Make sure you have a suitable FTP client.  The command-line ftp tool shipped with most Unix-like systems supports a -d flag that reports the underlying FTP messages, so it's great for this exercise.   Pick a target FTP server.  I tested against ftp.riverbed.com and ftp.debian.org , but other ftp servers may differ for subtle reasons.   Review the FTP protocol specification - it's sufficient to know that it's a single TCP control channel, requests are of the form 'VERB[ parameter]\r\n" and responses are of the form 'CODE message\n'.  Multi-line responses are accepted; all but the last line of the reponse include an additional hyphen ('CODE-message\n').   Create your FTP virtual server   Use the 'Add a new service' wizard to create your FTP virtual server.  Just for fun, add a server banner (Virtual Server > Connection Management > FTP-Specific Settings):     Verify that you can log in to your FTP server through Stingray, and that the banner is rewritten: Now we're good to go!   Intercepting login credentials   We want to intercept FTP login attempts, and change all logins to 'anonymous'.  If a user logs in with 'username:password', we're going to convert that to 'anonymous:username' and discard the password.   Create the following Request Rule, and assign it to the FTP virtual server:   log.info( "Recieved connection: state is '" . connection.data.get( "state" ) . "'" ); if( connection.data.get( "state" ) == "" ) { # This is server-first, so we have no data on the first connect connection.data.set( "state", "connected" ); break; } if( connection.data.get( "state" ) == "connected" ) { # Get the request line $req = string.trim( request.endswith( "\n" ) ); log.info( " ... got request '" . $req . "'" ); if( string.regexmatch( $req, "USER (.*)" ) ) { connection.data.set( "user", $1 ); # Translate this to an anonymous login log.info( " ... rewriting request to 'USER anonymous'" ); request.set( "USER anonymous\r\n" ); } if( string.regexmatch( $req, "PASS (.*)" ) ) { $pass = $1; connection.data.set( "pass", $pass ); $user = connection.data.get( "user" ); # Set the appropriate password log.info( " ... rewriting request to 'PASS ".$user."'" ); request.set( "PASS ".$user."\r\n" ); } }   Now, if you log in with your email address (for example) and a password, the rule will switch your login to an anonymous one and will log the result:   Authenticating the user's credentials   You can extend this rule to authenticate the credentials that the user provided.  At the point in the rule where you have the username and password, you can call a Stingray authenticator, a Java Extension, or reference a libTable.rts: Interrogating tables of data in TrafficScript in your TrafficScript rule:   #AD authentication $ldap = auth.query( "AD Auth", $user, $pass ); if( $ldap['Error'] ) { log.error( "Error with authenticator 'AD Auth': " . $auth['Error'] ); connection.discard(); } else if( !$ldap['OK'] ) { log.info("User not authenticated. Username and/or password incorrect"); connection.discard(); }  
View full article
When Stingray load-balances a connection to an iPlanet/SunONE/Sun Java System Web Server server or application, the connection appears to originate from the Stingray machine. This can be a problem if the server wishes to perform access control based on the client's IP address, or if it wants to log the true source address of the request, and is well documented in the article IP Transparency: Preserving the Client IP address in Stingray Traffic Manager.   Stingray has an IP Transparency feature that preserves the client's IP address, but this requires a Stingray Kernel Modules for Linux Software (pre-installed on Stingray Virtual Appliances and available separately for Stingray software) and is currently only available under Linux. As an alternative, the mod_remoteip module is a good solution for Apache; this article presents a similar module for iPlanet and related webservers.   How it works   Stingray automatically inserts a special X-Cluster-Client-Ip header into each request, which identifies the true source address of the request. The iPlanet/Sun NSAPI module inspects this header and corrects the calculation of the source address. This change is transparent to the web server, and to any applications running on or behind the web server.   Obtaining the Module   Compile the module from source:   https://gist.github.com/5546803   To determine the appropriate compilation steps for an NSAPI module for your instance of iPlanet, you can first build the NSAPI examples in your SunONE installation:   $ cd plugins/nsapi/examples/ $ make cc -DNET_SSL -DSOLARIS -D_REENTRANT -DMCC_HTTPD -DXP_UNIX -DSPAPI20 \ -I../../include -I../../include/base -I../../include/frame -c addlog.c ld -G addlog.o -o example.so   You can build the iprewrite.so module using similar options. Set NSHOME to the installation location for iPlanet:   $ export NSHOME=/opt/iplanet $ cc -DNET_SSL -DSOLARIS -D_REENTRANT -DMCC_HTTPD -DXP_UNIX -DSPAPI20 \ -I$NSHOME/plugins/include -I$NSHOME/plugins/include/base \ -I$NSHOME/plugins/include/frame -c iprewrite.c $ ld -G iprewrite.o -o iprewrite.so $ cp iprewrite.so $NSHOME/plugins   Configuring the Module   To configure the module, you will need to edit the magnus.conf and obj.conf files for the virtual server you are using. If the virtual server is named 'test', you'll find these files in the https-test/config directory.   magnus.conf   Add the following lines to the end of the magnus.conf file. Ensure that the shlib option identifies the full path to the iprewrite.so module, and that you set TrustedIPs to either '*', or the list of Stingray back-end IP addresses:   Init fn="load-modules" funcs="iprewrite-init,iprewrite-all,iprewrite-func" \ shlib="/usr/local/iplanet/plugins/iprewrite.so" Init fn="iprewrite-init" TrustedIPs="10.100.1.68 10.100.1.69"   The TrustedIPs option specifies the back-end addresses of the Stingray machines. The iprewrite.so module will only trust the 'X-Cluster-Client-Ip' header in connections which originate from these IP addresses. This means that remote users cannot spoof their source addresses by inserting a false header and accessing the iPlanet/Sun servers directly.   obj.conf   Locate the 'default' object in your obj.conf file and add the following line at the start of the directives inside that object:   <Object name=default> AuthTrans fn="iprewrite-all" ...   Restart your iPlanet/Sun servers, and monitor your servers' error logs (https-name/log/errors).   The Result   iPlanet/Sun, and applications running on the server will see the correct source IP address for each request. The access log module will log the correct address when you use %a or %h in your log format string.   If you have misconfigured the TrustedIPs value, you will see messages like:   Ignoring X-Cluster-Client-Ip '204.17.28.130' from non-Load Balancer machine '10.100.1.31'   Add the IP address to the trusted IP list and restart.   Alternate Configuration   The 'iprewrite-all' SAF function changes the ip address for the entire duration of the connection. This may be too invasive for some environments, and its possible that a later SAF function may modify the IP address again. You can use the 'iprewrite-func' SAF function to change the ip address for a single NSAPI function. For example, BEA's NSAPI WebLogic connector ('wl_proxy') is normally configured as follows:   <Object name="weblogic" ppath="/weblogic/"> Service fn=wl_proxy WebLogicHost=localhost    WebLogicPort=7001 PathTrim="/weblogic" </Object>   You can change the IP address just for that function call, using the iprewrite-func SAF function as follows:   <Object name="weblogic" ppath="/weblogic/"> Service fn=iprewrite-func func=wl_proxy WebLogicHost=localhost    WebLogicPort=7001 PathTrim="/weblogic" </Object>
View full article
This Document provides step by step instructions on migrating Cisco ACE configuration to Stingray Traffic Manager.
View full article
Imagine you're running a popular image hosting site, and you're concerned that some users are downloading images too rapidly.  Or perhaps your site publishes airfares, or gaming odds, or auction prices, or real estate details and screen-scraping software is spidering your site and overloading your application servers.  Wouldn't it be great if you could identify the users who are abusing your web services and then apply preventive measures - for example, a bandwidth limit - for a period of time to limit those users' activity?   In this example, we'll look at how you can drive the control plane (the traffic manager configuration) from the data plane (a TrafficScript rule):   Identify a user by some id, for example, the remote IP address or a cookie value Measure the activity of each users using a rate class If a user exceeds the desired rate (their terms of service), add a resource file identifying the user and their 'last sinned' time Check the resource time to see if we should apply a short-term limit to that user's activity   Basic rule   # We want to monitor image downloads only if( !string.wildMatch( http.getPath(), "*.jpg" ) ) break; # Identify each user by their remote IP. # Could use a cookie value here, although that is vulnerable to spoofing # Note that we'll use $uid as a filename, so it needs to be secured $uid = request.getRemoteIP(); if( !rate.use.noQueue( "10 per minute", $uid ) ) { # They have exceeded the desired rate and broken the terms of use # Let's create a config file named $uid, containing the current time http.request.put( "http://localhost:9070/api/tm/1.0/config/active/extra/".$uid, sys.time(), "Content-type: application/octet-stream\r\n". "Authorization: Basic ".string.base64encode( "admin:admin" ) ); } # Now test - did the user $uid break their terms of use recently? $lastbreach = resource.get( $uid ); if( ! $lastbreach ) break; # config file does not exist if( sys.time()-$lastbreach < 60 ) { # They last breached the limits less than 60 seconds ago response.setBandwidthClass( "Very slow" ); } else { # They have been forgiven their sins. Clean up the config file http.request.delete( "http://localhost:9070/api/tm/1.0/config/active/extra/".$uid, "Authorization: Basic ".string.base64encode( "admin:admin" ) ); }   This example uses a rate class named '10 per minute' to monitor the request rate for each user, and a bandwidth class named ‘Very slow’ to apply an appropriate bandwidth limit.  You could potentially implement a similar solution using client-side cookies to identify users who should be bandwidth-limited, but this solution has the advantage that the state is stored locally and is not dependent on trusting the user to honor cookies.   There's scope to improve this rule.  The biggest danger is that if a user exceeds the limit consistently, this will result in a flurry of http.request.put() calls to the local REST daemon.  We can solve this problem quite easily with a rate class that will limit how frequently we update the configuration.  If that slows down a user who has just exceeded their terms of service, that's not really a problem for us!   rate.use( "10 per minute" ); # stall the user if necessary to avoid overload http.request.put( ... );   Note that we can safely use the rate class in two different contexts in one rule.  The first usage ( rate.use( "name", $uid ) ) will rate-limit each individual value of $uid ; the rate.use( "name" ) is a global rate limit that will limit all calls to the REST API .   Read more   Check out the other prioritization and rate shaping suggestions on splash, including:   Dynamic rate shaping slow applications The "Contact Us" attack against mail servers Stingray Spider Catcher Evaluating and Prioritizing Traffic with Stingray Traffic Manager
View full article
Following is a library that I am working on that has one simple design goal: Make it easier to do authentication overlay with Stingray.   I want to have the ability to deploy a configuration that uses a single line to input an authentication element (basic auth or forms based) that takes the name of an authenticator, and uses a simple list to define what resources are protected and which groups can access them.   Below is the beginning of this library.  Once we have better code revision handling in splash (hint hint Owen Garrett!! ) I will move it to something more re-usable.  Until then, here it is.   As always, comments, suggestions, flames or gifts of mutton and mead most welcome...   The way I want to call it is like this:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import lib_auth_overlay as aaa;   # Here we challenge for user/pass $userpasswd = aaa.promptAuth401();   # extract the entered username / password into variables for clarity $username = $userpasswd [0]; $password = $userpasswd [1];   # Here we authenticate check that the user is a member of the listed group # We are using the "user_ldap" authenticator that I set up against my laptop.snrkl.org # AD domain controller. $authResult = aaa.doAuthAndCheckGroup( "user_ldap" , $username , $password , "CN=staff,CN=Users,DC=laptop,DC=snrkl,DC=org" );   # for convienience we will tell the user the result of their Auth in an http response aaa.doHtmlResponse.200( "Auth Result:" . $authResult );   here is the lib_auth_overlay that is referenced in the above element.  Please note the promptAuthHttpForm() sub routine is not yet finished...   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 sub doHtmlResponse.200 ( $message ){       http.sendResponse(          "200 OK" ,          "text/html" ,          $message ,          ""          ); }   sub challengeBasicAuth( $errorMessage , $realm ){       http.sendResponse(          "401 Access Denied" ,          "text/html" ,          $errorMessage ,          "WWW-Authenticate: Basic realm=\"" . $realm . "\"" );   }   sub basicAuthExtractUserPass( $ah ){ #// $ah is $authHeader,          $enc = string.skip( $ah , 6 );          $up = string. split (string.base64decode( $enc ), ":" );          return $up ;       }   sub doAuthAndGetGroups ( $authenticator , $u , $p ){       $auth = auth.query( $authenticator , $u , $p );       if ( $auth [ 'Error' ] ) {          log .error( "Error with authenticator " . $authenticator . ": " . $auth [ 'Error' ] );          return "Authentication Error" ;       } else if ( ! $auth [ 'OK' ] ) { #//Auth is not OK          # Unauthorised          log . warn ( "Access Denied - invalid username or password for user: \"" . $u . "\"" );          return "Access Denied - invalid username or password" ;       } else if ( $auth [ 'OK' ] ){          log .info( "Authenticated \"" . $u . "\" successfully at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));          return $auth [ 'memberOf' ];       }   }   sub doAuthAndCheckGroup ( $authenticator , $u , $p , $g ){       $auth = auth.query( $authenticator , $u , $p );       if ( $auth [ 'Error' ] ) {          log .error( "Error with authenticator \"" . $authenticator . "\": " . $auth [ 'Error' ] );          return "Authentication Error" ;       } else if ( ! $auth [ 'OK' ] ) { #//Auth is not OK          # Unauthorised          log . warn ( "Access Denied - invalid username or password for user: \"" . $u . "\"" );          return "Access Denied - invalid username or password" ;       } else if ( $auth [ 'OK' ] ){          log .info( "Authenticated \"" . $u . "\" successfully at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));          if ( lang.isArray( $auth [ 'memberOf' ])){ #//More than one group returned             foreach ( $group in $auth [ 'memberOf' ]){                if ( $group == $g ) {                   log .info( "User \"" . $u . "\" permitted access at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                   return "PASS" ;                   break;                } else {                   log . warn ( "User \"" . $u . "\" denied access - not a member of \"" . $g . "\" at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                }           }             #// If we get to here, we have exhausted list of groups with no match             return "FAIL" ;            } else { #// This means that only one group is returned             $group = $auth [ 'memberOf' ];                if ( $group == $g ) {                   log .info( "User \"" . $u . "\" permitted access " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                   return "PASS" ;                   break;                } else {                   log . warn ( "User \"" . $u . "\" denied access - not a member of \"" . $g . "\" at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                   return "FAIL" ;                }        }     } }   sub promptAuth401(){       if (!http.getHeader( "Authorization" )) { #// no Authorization header present, lets challenge for credentials          challengeBasicAuth( "Error Message" , "Realm" );       } else {          $authHeader = http.getHeader( "Authorization" );          $up = basicAuthExtractUserPass( $authHeader );            return $up ;     } }   sub promptAuthHttpForm(){       $response = "<html> <head>Authenticate me...</head> <form action=/login method=POST> <input name=user required> <input name=realm type=hidden value=stingray> <input name=pass type=password required> <button>Log In</button> </form> </html>" ;       doHtmlResponse.200( $response ); }  
View full article
There are many reasons why you may want to serve web content directly from Stingray Traffic Manager - simplification, performance, ease of administration and, perhaps most importantly, to host a 'Sorry Page' if your entire web infrastructure has failed and Stingray is all that is left.   The article Using Stingray Traffic Manager as a Webserver describes the rationale in more detail and presents a simple TrafficScript-based webserver.  However, we can do a lot more with a more complete programming language - mime types, index pages, more control over the location of the document root are all simple to implement with Python.   Get started with PyRunner.jar   Start with the procedure describe in the article PyRunner.jar: Running Python code in Stingray Traffic Manager.  The PyRunner extension lets you run Python code in Stingray, using the local JVM and the Jython implementation.   Note that this example does not work reliably with versions of Jython prior to 2.7 beta1 - I hit problems when a library attempted to import the Jython errno module (possibly related to https://github.com/int3/doppio/issues/177).   webserver.py   Once you've installed PyRunner (using an appropriate version of Jython), upload the following Python script to your Extra Files catalog.  Make sure to call the script 'webserver.py', and edit the location of the docroot to an appropriate value:   from javax.servlet.http import HttpServlet from urllib import url2pathname from os import listdir from os.path import normpath,isdir,isfile,getmtime,getsize import mimetypes import datetime docroot = '/tmp' def dirlist( uri, path, response ): files = '' for f in listdir( path ): if (f[0] == '.') and (f[1] != '.'): continue # hidden files if isdir( path+'/'+f ): size = '&lt;DIR&gt;' f += '/' # Add trailing slash for directories else: size = '{:,d} bytes'.format( getsize( path+'/'+f )) mtime = datetime.datetime.fromtimestamp( getmtime( path+'/'+f )) files += '<a href="{f}">{f:<30}</a> {t:14} {s:>17}\n'.format( f=f, t=mtime, s=size ) html = ''''' <html><head><title>{uri}</title></head><body> <h1>Directory listing for {uri}</h1> <pre>{files}<a href="../">Up</a></pre> </body></html>''' .format(uri=uri, files=files ) response.setContentType( 'text/html' ) toClient = response.getWriter() toClient.write( html ) class webserver(HttpServlet): def doGet(self, request, response): uri = request.getRequestURI() print "Processing "+uri path = normpath( docroot + url2pathname( uri ) ) # Never access files outside the docroot if path.startswith( docroot ) == False: path = docroot uri = '/' if isdir( path ): if uri.endswith( '/' ) == False: response.sendRedirect( uri+'/' ) if isfile( path + '/index.html' ): response.sendRedirect( uri + 'index.html' ) else: dirlist( uri, path, response ) return try: c = open( path, 'r' ).read() except Exception, e: response.sendError( 400, "Could not read "+path+": "+str( e ) ) return mtype = mimetypes.guess_type( path )[0] if mtype == None: mtype = 'application/octet-stream' response.setContentType( mtype ) toClient = response.getWriter() toClient.write( c )   If you want to edit the rule and try some changes, they you'll find the publish.py script in Deploying Python code to Stingray Traffic Manager useful, and you should follow the Stingray Event log ( tail -f /opt/zeus/zxtm/log/errors ) to catch any problems.   Performance Optimization   The article Using Stingray Traffic Manager as a Webserver describes how to front the web server virtual server with a second virtual server that caches responses and responds directly to web requests.   It also presents an alternative webserver implementation written in Java - take your pick!
View full article
Stingray Traffic Manager has a host of great, capable features to improve the performance and reliability of your web servers.  However, is there sometimes a case for putting the web content on Stingray directly, and using it as a webserver?   The role of a webserver in a modern application has shrunk over the years.  Now it's often just a front-end for a variety of application servers, performing authentication and serving simple static web content... not unlike the role of Stingray.  Often, its position in the application changes when Stingray is added:     Now, if you could move the static content on to Stingray Traffic Manager, wouldn't that help to simplify your application architecture still further? This article presents three such ways:   A simple webserver in TrafficScript   TrafficScript can send back web pages without difficulty, using the http.sendResponse() function.  It can load web content directly from Stingray's Resource Directory (the Extra Files catalog).   Here's a simple TrafficScript webserver that intercepts all requests for content under '/static' and attempts to satisfy them with files from the Resource directory:   # We will serve static web pages for all content under this directory $static = "/static/"; $page = http.getPath(); if( !string.startsWith( $page, $static )) break; # Look for the file in the resource directory $file = string.skip( $page, string.length( $static )); if( resource.exists( $file )) { # Page found! http.sendResponse( 200, "text/html", resource.get( $file ), "" ); } else { # Page not found, send an error back http.sendResponse( 404, "text/html", "Not found", "" ); }   Add this file (as a request rule) to your virtual server and upload some files to Catalog > Extra Files.  You can then browse them from Stingray, using URLs beginning /static.   This is a very basic example.  For example, it does not support mime types (it assumes everything is text/html) - you can check out the Sending custom error pages article for a more sophisticated TrafficScript example that shows you how to host an entire web page (images, css and all) that you can use as an error page if your webservers are down.   However, the example also does not support directory indices... in fact, because the docroot is the extra files catalog, there's no (easy) way to manage a hierarchy of web content.  Wouldn't it be better if you could serve your web content directly from a directory in disk?   A more sophisticated Web Server in Java   The article Serving Web Content from Stingray using Java presents a more sophisticated web server written in Java.  It runs as a Java Extension and can access files in a nominated docroot outside of the Stingray configuration.  It supports mime types and directory indices.   Another webserver - in Python   Stingray can also run application code in Python, by way of the PyRunner.jar: Running Python code in Stingray Traffic Manager implementation that runs Python code on Stingray's local JVM.  This article Serving Web Content from Stingray using Python presents an alternative webserver written in Python.   Optimizing for Performance   Perhaps the most widely used feature in Stingray Traffic Manager, after basic load balancing, health monitoring and the like, i s Content Caching .  Content Caching is a very easy and effective way to reduce the overhead of generating web content, whether the content is read from disk or generated by an application.  The content generated by our webserver implementations is fairly 'static' (does not change) so it's ripe for caching to reduce the load on our webservers.   There's one complication - you can't just turn content caching on and expect it to work in this situation.  That's because content caching hooks into two stages in the transaction lifecycle in Stingray:   Once all response rules have completed, Stingray will inspect the final response and decide whether it can be cached for future reuse Once all request rules have completed, Stingray will examine the current request and determine if there's a suitable response in the cache   In our webserver implementations, the content was generated during the request processing step and written back to the client using http.sendResponse (or equivalent).  We never run any response rules (so the content cannot be cached) and we never get to the end of the request rules (so we would not check the cache anyway).   The elegant solution is to create a virtual server in Stingray specifically to run the WebServe extension.  The primary virtual server can forward traffic to your application servers, or to the internal 'webserver' virtual server as appropriate.  It can then cache the responses (and respond directly to future requests) without any difficulty:   The primary Virtual Server decides whether to direct traffic to the back-end servers, or to the internal web server using an appropriate TrafficScript rule:   Create a new HTTP virtual server - this will be used to run the Webserver Extension. Configure the virtual server to listen on localhost only, as it need not be reachable from anywhere else.  You can chose an unused high port, such as 8080 perhaps.   Add the appropriate TrafficScript rule to this virtual server to make it run the WebServer Extension.   Create a pool called 'Internal Web Server' that will direct traffic to this new virtual server. The pool should contain the node localhost:[port].   Extend the TrafficScript rule on the original virtual server to:   # Use the internal web server for static content if( string.startsWith( http.getPath(), "/static" )) { pool.use( "Internal Web Server" ); }   Enable the web cache on the original virtual server.   Now, all traffic goes to the original virtual server as before. Static pages are directed to the internal web server, and the content from that will be cached. With this configuration, Stingray should be able to serve web pages as quickly as needed.   Don't forget that all the other Stingray features like content compression, logging, rate-shaping, SSL encryption and so on can all be used with the new internal web server. You can even use response rules to alter the static pages as they are sent out.   Now, time to throw your web servers away?  
View full article
The article Managing consistent caches across a Stingray Cluster describes in detail how to configure a pair of Stingray devices to operate together as a fully fault-tolerant cache.   The beauty of the configuration was that it minimized the load on the origin servers - content would only be requested from the origin servers when it had expired on both peers, and a maximum of one request per 15 seconds (configurable) per item of content would be sent to the origin servers:     The solution uses two Stingray Traffic Managers, and all incoming traffic is distributed to one single front-end traffic manager.   How could we extend this solution to support more than two traffic managers (for very high-availability requirements) with multiple active traffic managers?   Overview   The basic architecture of the solution is as follows:   We begin with a cluster of 3 Stingray Traffic Managers, named stm-1, stm-2 and stm-3, with a multi-hosted IP address distributing traffic across the three traffic managers Incoming traffic is looped through all three traffic managers before being forwarded to the origin servers; the return traffic can then be cached by each traffic manager   If any of the traffic managers have a cached version of the response, they respond directly   Configuration   Starting from a working cluster.  In this example, the names 'stm-1', 'stm-2' and 'stm-3' resolve to the permanent IP addresses of each traffic manager; replace these with the hostnames of the machines in your cluster.  The origin servers are webserver1, webserver2 and webserver3.   Step 1: Create the basic pool and virtual server   Create a pool named 'website0', containing the addresses of the origin servers. Create a virtual server that uses the 'discard' pool as its default pool.  Add a request rule to select 'website0':   pool.use( "website0" );   ... and verify that you can browse your website through this virtual server.   Step 2: Create the additional pools   You will need to create N * (N-1) additional pools if you have N traffic managers in your cluster.   Pools website10, website20 and website30 contain the origin servers and either node stm-1:80, stm-2:80 or stm-3:80.  Edit each pool and enable priority lists so that the stm node is used in favor to the origin servers:   Configuration for Pools website10 (left), website20 (middle) and website30 (right)   Pools website230, website310 and website120 contain the origin servers and two of nodes stm-1:80, stm-2:80 or stm-3:80.  Edit each pool and enable priority lists so that the stm nodes are each used in favor to the origin servers.   For example, pool website310 will contain nodes stm-3:80 and stm-1:80, and have the following priority list configuration:     Step 3: Add the TrafficScript rule to route traffic through the three Stingrays   Enable trafficscript!variable_pool_use (Global Settings > Other Settings), then add the following TrafficScript request rule:   # Consistent cache with multiple active traffic managers $tm = [ 'stm-1' => [ 'id' => '1', 'chain' => '123' ], 'stm-2' => [ 'id' => '2', 'chain' => '231' ], 'stm-3' => [ 'id' => '3', 'chain' => '312' ] ]; $me = sys.hostname(); $id = $tm[$me]['id']; $chain = http.getHeader( 'X-Chain' ); if( !$chain ) $chain = $tm[$me]['chain']; log.info( "Request " . http.getPath() . ": ".$me.", id ".$id.": chain: ".$chain ); do { $i = string.left( $chain, 1 ); $chain = string.skip( $chain, 1 ); } while( $chain && $i != $id ); log.info( "Request " . http.getPath() . ": New chain is ".$chain.", selecting pool 'website".$chain."0'"); http.setHeader( 'X-Chain', $chain ); pool.use( 'website'.$chain.'0' );   Leave the debugging 'log.info' statements in for the moment; you should comment them out when you deploy in production.   How does the rule work?   When traffic is received by a Traffic Manager (for example, the traffic manager with hostname stm-2), the rule selects the chain of traffic managers to process that request - traffic managers 2, 3 and 1.   It updates the chain by removing '2' from the start, and then selects pool 'website310'.   This pool selects stm-3 in preference, then stm-1 (if stm-3 has failed), and finally the origin servers if both devices have failed.   stm-3 will process the request, check the chain (which is now '31'), remove itself from the start of the chain and select pool 'website10'.   stm-1 will then select the origin servers.   This way, a route for the traffic is threaded through all of the working traffic managers in the cluster.   Testing the rule   You should test the configuration with a single request.  It can be very difficult to unravel multiple requests at the same time with this configuration.   Note that each traffic manager in the cluster will log its activity, but the merging of these logs is done at a per-second accuracy, so they will likely be misordered.  You could add a 'connection.sleep( 2000 )' in the rule for the purposes of testing to avoid this problem.   Enable caching   Once you are satisfied that the configuration is forwarding each request through every traffic manager, and that failures are appropriately handled, then you can configure caching.  The details of the configuration are explained in the Managing consistent caches across a Stingray Cluster article:     Test the configuration using a simple, repeated GET for a cacheable object:   $ while sleep 1 ; do wget http://192.168.35.41/zeus/kh/logo.png ; done   Just as in the Consistent Caches article, you'll see that all Stingrays have the content in their cache, and it's refreshed from one of the origin servers once every 15 seconds:   Notes   This configuration used a Multi-Hosted IP address to distribute traffic across the cluster.  It works just as well with single hosted addresses, and this can make testing somewhat easier as you can control which traffic manager receives the initial request.   You could construct a similar configuration using Failpools rather than priority lists.  The disadvantage of using failpools is that Stingray would treat the failure of a Stingray node as a serious error (because an entire pool has failed), whereas with priority lists, the failure of a node is reported as a warning.  A warning is more appropriate because the configuration can easily accommodate the failure of one or two Stingray nodes.   Performance should not be unduly affected by the need to thread requests through multiple traffic managers.  All cacheable requests are served directly by the traffic manager that received the request.  The only requests that traverse multiple traffic managers are those that are not in the cache, either because the response is not cacheable or because it has expired according to the 'one check every 15 seconds' policy.
View full article
To get started quickly with Python on Stingray Traffic Manager, go straight to PyRunner.jar: Running Python code in Stingray Traffic Manager.  This article dives deep into how that extension was developed.   As is well documented, we support the use of Java extensions to manipulate traffic. One of the great things about supporting "Java" is that this really means supporting the JVM platform... which, in turn, means we support any language that will run on the JVM and can access the Java libraries.   Java isn't my first choice of languages, especially when it comes to web development. My language of choice in this sphere is Python; and thanks to the Jython project we can write our extensions in Python!   Jython comes with a servlet named PyServlet which will work with Stingray out-of-the-box, this is a good place to start. First let us quickly set up our Jython environment, working Java support is a prerequisite of course. (If you're not already familiar with Stingray Java extensions I recommend reviewing the Java Development Guide available in the Stingray Product Documentation and the Splash guide Writing Java Extensions - an introduction )   Grab Jython 2.5 (if there is now a more recent version I expect it will work fine as well)   Jython comes in the form of a .jar installer, on Linux I recommend using the CLI install variant:   java -jar jython_installer-2.5.0.jar -c   Install to a path of your choosing, I've used: /space/jython   Upload /space/jython/jython.jar to your Java Extensions Catalog   In the Java Extensions Catalog you can now see a couple of extensions provided by jython.jar, including org.python.util.PyServlet. By default this servlet maps URLs ending in .py to local Python files which it will compile and cache. Set up a test HTTP Virtual Server (I've created a test one called "Jython" tied to the "discard" pool), and add the following request rule to it:   if (string.endsWith(http.getPath(), ".py")) { java.run( "org.python.util.PyServlet" ); }   The rule avoids errors by only invoking the extension for .py files. (Though if you invoke PyServlet for a .py file that doesn't exist you will get a nasty NullPointerException stack trace in your event log.)   Next, create a file called Hello.py containing the following code:   from javax.servlet.http import HttpServlet class Hello(HttpServlet): def doGet(self, request, response): toClient = response.getWriter() response.setContentType ("text/html") toClient.println("<html><head><title>Hello from Python</title>" + "<body><h1 style='color:green;'>Hello from Python!</h1></body></html>")   Upload Hello.py to your Java Extensions Catalog. Now if you visit the file Hello.py at the URL of your VirtualServer you should see the message "Hello from Python!" Hey, we didn't have to compile anything! Your event log will have some messages about processing .jar files, this only happens on first invocation. You will also get a warning in your event log every time you visit Hello.py:   WARN java/* servleterror Servlet org.python.util.PyServlet: Unknown attribute (pyservlet)   This is just because Stingray doesn't set up or use this particular attribute. We'll ignore the warning for now, and get rid of it when we tailor PyServlet to be more convenient for Stingray use. The servlet will also have created some some extra files in your Java Libraries & Data Catalog under a top-level directory WEB-INF. It is possible to change the location used for these files, we'll get back to that in a moment.   All quite neat so far, but the icing on the cake is yet to come. If you open up $ZEUSHOME/conf/jars/Hello.py in your favourite text editor and change the message to "Hello <em>again</em> from Python!" and refresh your browser you'll notice that the new message comes straight through. This is because the PyServlet code checks the .py file, if it is newer than the cached bytecode it will re-interpret and re-cache it. We now have somewhat-more-rapid application development for Stingray extensions. Bringing together the excellence of Python and the extensiveness of Java libraries.   However, what I really want to do is use TrafficScript at the top level to tell PyServlet which Python servlet to run. This will require a little tweaking of the code. We want to get rid of the code that resolves .py file from the request URI and replace it with code that uses an argument passed in by TrafficScript. While we're at it, we'll make it non-HTTP-specific and add some other small tweaks. The changes are documented in comments in the code, which is attached to this document.   To compile the Stingray version of the servlet and pop the two classes into a single convenient .jar file execute the following commands.   $ javac -cp $ZEUSHOME/zxtm/lib/servlet.jar:/space/jython/jython.jar ZeusPyServlet.java $ jar -cvf ZeusPyServlet.jar ZeusPyServlet.class ZeusPyServletCacheEntry.class   (Adjusting paths to suit your environment if necessary.)   Upload the ZeusPyServlet.jar file to your Java Extensions Catalog. You should now have a ZeusPyServlet extension available. Change your TrafficScript rule to load this new extension and provide an appropriate argument.   if (string.endsWith(http.getPath(), ".py")) { java.run( "ZeusPyServlet", "Hello.py" ); }   Now visiting Hello.py works just as before. In fact, visiting any URL that ends in .py will now generate the same result as visiting Hello.py. We have complete control over what Python code is executed from our TrafficScript rule, much more convenient.   If you continue hacking from this point you'll soon find that we're missing core parts of python with the setup described so far. For example adding import md5 to your servlet code will break the servlet, you'd see this in your Stingray Event Log:   WARN servlets/ZeusPyServlet Servlet threw exception javax.servlet.ServletException:            Exception during init of /opt/zeus/zws/zxtm/conf/jars/ServerStatus.py WARN  Java: Traceback (most recent call last): WARN  Java:    File "/opt/zeus/zws/zxtm/conf/jars/ServerStatus.py", line 2, in <module> WARN  Java:      import md5 WARN  Java: ImportError: No module named md5   This is because the class files for the core Python libraries are not included in jython.jar. To get a fully functioning Jython we need to tell ZeusPyServlet where Jython is installed. To do this you must have Jython installed on the same machine as the Stingray software, and then you just have to set a configuration parameter for the servlet, in summary:   Install Jython on your Stingray machine, I've installed mine to /space/jython In Catalogs > Java > ZeusPyServlet add some parameters: Parameter: python_home, Value: /space/jython (or wherever you have installed Jython) Parameter: debug, Value: none required (this is optional, it will turn on some potentially useful debug messages) Back in Catalogs > Java you can now delete all the WEB-INF files, now that Jython knows where it is installed it doesn't need this Go to System > Traffic Managers and click the 'Restart Java Runner ...' button, then confim the restart (this ensures no bad state is cached)   Now your Jython should be fully functional, here's a script for you to try that uses MD5 functionality from both Python and Java. Just replace the content of Hello.py with the following code.   from javax.servlet.http import HttpServlet from java.security import MessageDigest from md5 import md5 class Hello(HttpServlet): def doGet(self, request, response): toClient = response.getWriter() response.setContentType ("text/html") htmlOut = "<html><head><title>Hello from Python</title><body>" htmlOut += "<h1>Hello from Python!</h1>" # try a Python md5 htmlOut += "<h2>Python MD5 of 'foo': %s</h2>" % md5("foo").hexdigest() # try a Java md5 htmlOut += "<h2>Java MD5 of 'foo': " jmd5 = MessageDigest.getInstance("MD5") digest = jmd5.digest("foo") for byte in digest: htmlOut += "%02x" % (byte & 0xFF) htmlOut += "</h2>" # yes, the Stingray attributes are available htmlOut += "<h2>VS: %s</h2>" % request.getAttribute("virtualserver") htmlOut += "</body></html>" toClient.println(htmlOut)   An important point to realise about Jython is that beyond the usual core Python APIs you cannot expect all the 3rd party Python libraries out there to "just work". Non-core Python modules compiled from C (and any modules that depend on such modules) are the main issue here. For example the popular Numeric package will not work with Jython. Not to worry though, there are often pure-Python alternatives. Don't forget that you have all Java libraries available too; and even special Java libraries designed to extend Python-like APIs to Jyhon such as JNumeric, a Jython equivalent to Numeric. There's more information on the Jython wiki. I recommend reading through all the FAQs as a starting point. It is perhaps best to think of Jython as a language which gives you the neatness of Python syntax and the Python core with the utility of the massive collection of Java APIs out there.
View full article
For a comprehensive description of how this Stingray Java Extension operates, check out Yvan Seth's excellent article Making Stingray more RAD with Jython!   Overview   Stingray can invoke TrafficScript rules (see Feature Brief: TrafficScript) against requests and responses, and these rules run directly in the traffic manager kernel as high-performance bytecode.   A TrafficScript rule can also reach out to the local JVM to run servlets (Feature Brief: Java Extensions in Stingray Traffic Manager), and the PyRunner.jar library uses the JVM to run Python code against network traffic.  This is a great solution if you need to deploy complex traffic management policies and your development expertise lies with Python.   Requirements   Download and install Jython (http://www.jython.org/downloads.html).  This code was developed against Jython 2.5.3, but should run against other Jython versions.  For best compatibility across platforms, use the Jython installer from www.jython.org rather than the jython packages distributed by your OS vendor:   $ java -jar jython_installer-2.5.2.jar --console   Select installation option 1 (all components) or explicitly include the src part - this installs additional modules in extlibs that we will use later.   Locate the jython.jar file included in the install and upload this file to your Stingray Java Extensions catalog.   Download the PyRunner.jar file attached to this document and upload that to your Java Extensions catalog.  Alternatively, you can compile the Jar file from source:   $ javac -cp servlet.jar:zxtm-servlet.jar:jython.jar PyRunner.java $ jar -cvf PyRunner.jar PyRunner*.class   You can now run simple Python applications directly from TrafficScript!   A simple 'HelloWorld' example   Save the following Python code as Hello.py and upload the file to your Catalog > Extra Files catalog:     from javax.servlet.http import HttpServlet import time class Hello(HttpServlet): def doGet(self, request, response): toClient = response.getWriter() response.setContentType ("text/html") toClient.println("<html><head><title>Hello World</title>" + "<body><h1 style='color:red;'>Hello World</h1>" + "The current time is " + time.strftime('%X %x %Z') + "</body></html>")   Assign the following TrafficScript request rule to your Virtual Server:   java.run( "PyRunner", "Hello.py" );   Now, whenever the TrafficScript rule is called, it will run the Hello.py code.  The PyRunner extension loads and compiles the Python code, and caches the compiled bytecode to optimize performance.   More sophisticated Python examples   The PyRunner.jar/jython.jar combination is capable of running simple Python examples, but it does not have access to the full set of Python core libraries.  These are to be found in additional jar files in the extlibs part of the Jython installation.   If you install Jython on the same machine you are running the Stingray software on, then you can point PyRunner.jar at that location:   Install Jython in a known location, such as /usr/local/jython - make sure to install all components (option 1 in the installation types) or explicitly add the src part Navigate to Catalogs > Java > PyRunner and add a parameter named python_home , set to /usr/local/jython (or other location as appropriate) In Catalogs > Java, delete the WEB-INF files generated previously - they won't be required any more From the System > Traffic Managers page, restart your Java runner.   You can install jython in this way on the Stingray Virtual Appliance, but please take be aware that the installation will not be preserved during a major upgrade, and it will not form part of the supported configuration of the virtual appliance.   Here's an updated version of Hello.py that uses the Python and Java md5 implementations to compare md5s for the string 'foo' (they should give the same result!):   from javax.servlet.http import HttpServlet from java.security import MessageDigest from md5 import md5 import time class Hello(HttpServlet): def doGet(self, request, response): toClient = response.getWriter() response.setContentType ("text/html") htmlOut = "<html><head><title>Hello World</title><body>" htmlOut += "<h1>Hello World</h1>" htmlOut += "The current time is " + time.strftime('%X %x %Z') + "<br/>" # try a Python md5 htmlOut += "Python MD5 of 'foo': %s<br/>" % md5("foo").hexdigest() # try a Java md5 htmlOut += "Java MD5 of 'foo': " jmd5 = MessageDigest.getInstance("MD5") digest = jmd5.digest("foo") for byte in digest: htmlOut += "%02x" % (byte & 0xFF) htmlOut += "<br/>" # yes, the Stingray attributes are available htmlOut += "Virtual Server: %s<br/>" % request.getAttribute("virtualserver") # 'args' is the parameter list for java.run(), beginning with the script name htmlOut += "Args: %s<br/>" % ", ".join(request.getAttribute("args")) htmlOut += "</body></html>" toClient.println(htmlOut)   Upload this file to your Extra catalog to replace the existing Hello.py script and try it out.   Rapid test and development   Check out publish.py - a simple python script that automates the task of uploading your python code to the Extra Files catalog: Deploying Python code to Stingray Traffic Manager
View full article
Introduction Riverbed Stingray Traffic Manager is the only pure software ADC (Application Delivery Controller) available in the market today. As such it is the only ADC which can move with your business across Physical, Virtual, and Cloud platforms, and give you the exact same features and performance in each. Today Riverbed provides Stingray in the form of Virtual Appliances for many of the popular Hypervisors (including VMWare, Xen, and Hyper-V). The Virtual Appliance can also be provided in the standard OVA (Open Virtualization Archive) format for importing into a number of other Virtual platforms. For systems such as IBM PureFlex, where the Virtual Appliances are more closely integrated with the underlying platform it can be beneficial to build a completely custom appliance using the software edition of Stingray. IBM provides a tool precisely for this purpose: The Image Construction and Composition Tool (ICCT). This document will take you through the process of building a customised Stingray appliance for deployment on PureFlex using the ICCT. A very similar process can be followed to deploy an image into IBMs Smart Cloud Enterprise. IBM Image Construction and Composition Tool ICCT is a tool which allows a user on a PureFlex or Smart Cloud Enterprise platform to create custom Virtual Appliances. A full description of the ICCT application is beyond the scope of this document. We will assume that the ICCT has already been configured to communicate directly with your PureFlex or SCE environment and we will concentrate solely on the configuration elements required to build a custom appliance. These include: Managing ISO Resources, Managing Software Bundles, and Managing Images. ISO Resources An ISO resource is simply an ISO containing a compatible Operation System image. The ISO can be used to deploy new images from scratch. In terms of Stingray, the only PureFlex/SCE supported Operating Systems are Red Hat Enterprise Linux, SuSE Enterprise Linux. Software Bundles Along with this document, you should have been provided a prebuilt Stingray Software Bundle (a RAS file). The RAS file contains a number of scripts which are used by PureFlex or SCE in building and deploying custom appliances. A software Bundle includes four main configuration items: the installation script, the configuration script, a reset script, and some firewall configuration. Images An image is essentially a Virtual Machine. An image appears in ICCT either when it is created by deployment from an ISO, when it is imported from a connected SCE or PureFlex system, or when an existing image is extended. Build Process The process of building a custom appliance will be explained in detail in the section “Creating a custom Stingray appliance” below. As a quick overview the process is as follows: Take an image of a BaseOS and extend it. Add the software bundle to the extended image and configure the installer parameters. Deploy the new image (installer script is executed). Log on to running virtual machine and check installation succeeded. Capture the image. Export/deploy the image into PureFlex Manager/Smart Cloud Entry as an appliance Deploy an instance from the appliance image (the configure script is executed). The Stingray Software Bundle (RAS) The software bundle provided has everything you need to build a custom Stingray appliance for use in PureFlex or (with a little tweaking) Smart Cloud Enterprise. The RAS file includes a number of scripts which we discuss in turn. Installation Script The installation script “install.sh” is executed when the image is deployed for the first time. Its purpose is to download the Stingray software and Kernel modules from a HTTP repository and install them in to the image. The installer takes two arguments: stmUrl The URI for the Stingray software installer. Default version 9.1 from support.riverbed.com modsUrl The URI for the Stingray Kernel Modules installer Default: Version 2.5 from splash.riverbed.com The stingray installation needs nothing more than a Base OS install, however you will need to ensure that you have the development tools and kernel headers available in order to install the kernel modules. Note: It is recommended that you check the installer output in /var/log/provision.log after the initial deployment to ensure that everything worked. This is also a good opportunity to apply the latest security patches and bug fixes to the underlying OS. Configure Script The configuration script “configure.sh” is executed each time an instance is deployed from the appliance image. The configure script has the task of configuring the Stingray software in the appliance according to the parameters provided in the deployment form. The configure script in the provided RAS takes the following arguments: password The password for the Stingray admin user. Set password to “RANDOM” to have one generated for you. Default: “” accept_license Set to “Accept” if you agree to Stingray licensing terms. The configure script will not continue if this has not been set. Default: “No” license_key Should be “none” or a HTTP URI from where a license can be downloaded. Default: “none” join_cluster Should be “No” or “Yes”. Default: “No” cluster_host Should be “none” or an IP address or hostname of a Stingray to cluster with. Default: “none” cluster_tips Should be “No” or “Yes”. Default: “No” cluster_location The id of the location to join if we are joining a MSM cluster. Default: 0 cluster_external_ip If we are joining a cluster on the other side of a NAT device, then this should be our external IP Address. Default: “none” Reset Script The reset script “reset.sh” is executed whenever the appliance needs to be cleaned up and returned to an unconfigured state. I don’t believe it is ever actually used, but a reset script is provided. It takes no arguments. Firewall Configuration The final piece of configuration in a software bundle is the firewall configuration. This allows you to specify which ports should be open on the firewall. The Stingray bundle currently opens port 9090 and 9080 for cluster communications and administration. You will want to add all of the service ports you expect to use on your Stingray appliance. For example HTTP (80), HTTPS (443), etc. Miscellaneous All scripts write their output to /var/log/provision.log so you can always check that to see if there were any problems with the install, configure or reset steps. The software bundle includes AutoScaling drivers for Smart Cloud Enterprise, and also Smart Cloud Entry provisioning APIs. If you do not join a cluster during the deployment of an instance then the configure script will drop them into the extra files catalogue. However if you do join a cluster, then they will be copied into the root users home directory “/root”. Creating a custom Stingray Appliance Step 1: Upload your ISO The first step is to provide ICCT with an ISO containing the Operating System you wish to use. At the time of writing PureFlex and SmartCloud support RedHat Enterprise Linux and SuSE Enterprise Linux Operating Systems. Navigate to the “Manage ISO Resources” section and upload the ISO you wish to use as the base OS of your appliance. Step 2: Upload the Stingray RAS file The next step is to upload the Software Bundle (RAS file) to the ICCT server. Navigate to the “Build and Manage software Bundles” section and click on the import icon. The RAS file will need to be available either at a URL accessible by the ICCT server, or uploaded to the local file system of the ICCT server itself. Step 3: Create your new BaseOS Image At this point we have an ISO and our software bundle imported. However before we can use the software bundle we must have an image imported into ICCT which we can extend. We’re going to create a new image from the ISO; however you may chose to import a pre-existing Linux image if you have one. If you import a pre-exising image then you can skip to step 4. Navigate to “Build and Manage Images”. Click on the “New Image” icon and then select create from ISO On the next screen you will need to enter some information about the new image we are creating. You can give it a name such as “RHEL BaseOS”, A universal ID such as com.riverbed.rhel62.baseOS, a version (eg 6.2.0), and a description. You will next need to provide the ISO resource which we uploaded earlier, and a kickstart or AutoYAST file. You can download the default KickStart file from ICCT and then extend it. If you intend to make use of the Stingray kernel modules then I would recommend adding the following applications:  gcc, make, perl, kernel-devel. Next you can pick the hardware parameters for the new image. A summary is displayed on the final screen, click done to complete the image creation process. Step 4: Extend the BaseOS Image Now that we have a Base OS configuration, the next step is to add our software bundle and set the installer parameters. Select your new Base OS image in the left hand pane and then click the Extend icon Enter appropriate information for your Stingray Virtual Appliance: Name, Universal ID, Version and Description, and then click Create. Select the new Image in the left hand pane. Click the “Start Editing” Button. Scroll down to the “Software Bundles” and click the “Add” button. Add the Stingray software bundle. Once you have added the bundle you have the option of chosing a different version of the software to install. Expand the properties and change the locations of the installers to the desired versions. The Deploy options can be ignored as they are used at instance deployment not in the initial install phase. Once you are happy with your installation options, Click “Done Editing” and “Save”. Click on the “Synchronize” button to have ICCT deploy your new image and run through the installation process. Once the image is synchronized you will be able to see the details in the “Virtual System” section of the image details pane. At this point you can log into the Virtual System via SSH and confirm that the installer completed successfully by viewing the “/var/log/provision.log” file. This is also a good time to apply any OS patches and/or add additional software. Note:  On RHEL 6.2 the modules may fail to install because the shipped gcc does not match the compiler used to build the kernel (not on the original disc). Feel free to run a yum update at this point and rerun the Stingray modules installer. The provision log will tell you the temporary directory where they were downloaded. Once you are happy with the system, return to ICCT and click “Capture”. ICCT will shut down the VM and capture it as an appliance image, once this step completes you can export the image into PureFlex System Manager or Smart Cloud Entry. Your custom Stingray appliance is ready.
View full article
This Document provides step by step instructions on how to set up Brocade Virtual Traffic Manager for VMware Horizon View Servers.   This document has been updated from the original deployment guides written for Riverbed Stingray and SteelApp software.
View full article
As the content on websites grow, the structure of their URLs can change dramatically.  The addition of new applications and components can play havoc with the established URL space, and the development cost of supporting the old links that have been published in articles, referenced by search engines and bookmarked by users can be very high.   Stingray is in a great place to address this problem, and let your applications use the URL spaces most suited to them with little concern for backwards compatibility.  This article presents one technique you can employ to access this problem in a scalable and manageable manner.   A simple example   Let's start with a simple example; suppose you published content at the following URLs:   www.example.com/news.html www.example.com/about.html www.example.com/careers.html www.example.com/demos.html   As your company grew, offices were added, more products were developed and content began to be broken out by location.  The original URL structure was no longer sustainable, and a deeper layer of content was necessary:   www.example.com/corporate/news.html www.example.com/product/news.html www.example.com/product/demos.html www.example.com/about/cambridge.html www.example.com/about/honalulu.html www.example.com/careers/overview.html www.example.com/careers/honalulu/engineering.html www.example.com/careers/cambridge/research.html   The challenge is to serve out the best content to people who make requests to the old URLs.  You want a better solution than manually adding redirects to your webserver configuration file; ideally a solution that can be used by your web content team without the intervention from IT.   A solution   Ideally, we would like to issue an HTTP redirect to the most appropriate page. This is, of course, simple using TrafficScript.   1 http.redirect( "http://www.example.com/corporate/news.html" );   ... but you want to avoid having to build and maintain a rule that looks like this:   1 2 3 4 5 6 7 8 9 10 11 12 13 $url = http.getPath();   if ( $url == "/news.html" ){       http.redirect( "http://www.example.com/product/news.html" );   } else if ( $url == "/about.html" ) {       http.redirect( "http://www.example.com/about/cambridge.html" );   } else if ...   }   Wouldn't it be easier if you could maintain a file with a list of redirects, and Stingray could act on that?   /news.html http://www.example.com/product/news.html /about.html http://www.example.com/about/cambridge.html   etc...?   You can use the ResourceTable libraries from HowTo: Store tables of data in TrafficScript - part 1 to help you do exactly that:   1 2 3 4 5 6 7 import ResourceTableSmall as table;    $path = http.getPath();    $redirect = table.lookup( "redirects.txt" , $path );   if ( $redirect ) http.redirect( $redirect );   Managing the file of redirects   That leaves us with one problem - how best to manage the (albeit simple) file that contains the redirects?  The format is simple enough (space-separated key / value pairs, one per line) that anyone can edit it, but how do they get it into the Stingray configuration without using the complex and powerful Stingray Admin Interface?   There are a couple of simple approaches:   Use the Stingray Admin Interface, and give the user a permissions group that only gives access to the Extra Files catalog; Push the file in using one of Stingray's configuration APIs - Feature Brief: Stingray's RESTful Control API or Feature Brief: Stingray's SOAP Control API; Copy the file in using SSH, and then invoke ZEUSHOME/zxtm/bin/replicate-config to push the configuration across the cluster.   The REST approach is particularly attractive - you can use browser plugins like Chrome's REST Console to push configuration files into Stingray.   However, you may not want someone to use either approach directly because it would imply giving them a full administrative logon to the Stingray cluster.  In that case, you could consider a simple commandline tool that they can use to upload the updated configuration file, using the Collected Tech Tips: Using the RESTful Control API or  Collected Tech Tips: SOAP Control API examples APIs.
View full article
This Document provides step by step instructions on how to set up Brocade Virtual Traffic Manager for Oracle EBS 12.1.   This document has been updated from the original deployment guides written for Riverbed Stingray and SteelApp software.
View full article
Important note - this article illustrates an example of authenticating traffic using Java Extensions.  Stingray TrafficScript also includes LDAP/Active Directory primitives, in the form of auth.query() , and these are generally simpler and easier to use than a Java-based solution.   Overview   A very common requirement for intranet and extranet applications is the need to authenticate users against an Active Directory (or LDAP) database. The Java Extension in this article describes how to do exactly that.   This article describes two Java Extensions that manage the HTTP Basic Authentication process and validate the supplied username and password against an Active Directory database. It shows how to use Initialization Parameters to provide configuration to an extension, and how authentication results can be cached to reduce the load on the Active Directory server.   A basic Java Extension   The first Java Extension verifies that the supplied username and password can bind directly to the LDAP database.  It's appropriate for simple LDAP deployments, but enterprise AD deployments may not give end users permissions to bind directly to the entire database, so the second example may be more appropriate.   The Java Extension (version 1)   import java.io.IOException; import java.io.PrintWriter; import java.util.Hashtable; import javax.naming.Context; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.zeus.ZXTMServlet.ZXTMHttpServletRequest; public class LdapAuthenticate extends HttpServlet { private static final long serialVersionUID = 1L; private String dirServer; private String realm; public void init( ServletConfig config) throws ServletException { super.init( config ); dirServer = config.getInitParameter( "DB" ); realm = config.getInitParameter( "Realm" ); if( dirServer == null ) throw new ServletException( "No DB configured" ); if( realm == null ) realm = "Secure site"; } public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { try { ZXTMHttpServletRequest zreq = (ZXTMHttpServletRequest)req; String[] userPass = zreq.getRemoteUserAndPassword(); if( userPass == null ) throw new Exception( "No Authentication details" ); Hashtable<String, String> env = new Hashtable<String, String>(); env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put( Context.PROVIDER_URL, "LDAP://" + dirServer ); env.put( Context.SECURITY_AUTHENTICATION, "DIGEST-MD5" ); env.put( Context.SECURITY_PRINCIPAL, userPass[0] ); env.put( Context.SECURITY_CREDENTIALS, userPass[1] ); DirContext ctx = new InitialDirContext( env ); ctx.close(); // No exceptions thrown... must have been successful ;-) return; } catch( Exception e ) { res.setHeader( "WWW-Authenticate", "Basic realm=\"" + realm + "\"" ); res.setHeader( "Content-Type", "text/html" ); res.setStatus( 401 ); String message = "<html>" + "<head><title>Unauthorized</title></head>" + "<body>" + "<h2>Unauthorized - please log in</h2>" + "<p>Please log in with your system username and password</p>" + "<p>Error: " + e.toString() + "</p>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println( message ); } } public void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { doGet( req, res ); } }     Configuring the Java Extension   Upload the LdapAuthenticate Java Extension into the Java Catalog page. Click on the LdapAuthenticate link to edit the properties of the extension, and add two Initialization Parameters:   DB specifies the name of the LDAP or Active Directory database, and Realm specifies the authentication realm.   These parameters are read when the extension is initialized, which occurs the first time the extension is used by Stingray:   public void init( ServletConfig config) throws ServletException { super.init( config ); dirServer = config.getInitParameter( "DB" ); realm = config.getInitParameter( "Realm" ); if( dirServer == null ) throw new ServletException( "No DB configured" ); if( realm == null ) realm = "Secure site"; }   If you change the value of one of these parameters, use the 'Force Reload' option in the Stingray Admin Server to unload and reload this extension.   Either use the auto-generated rule, or create a new TrafficScript rule to call the extension on every request to an HTTP virtual server:   java.run( "LdapAuthenticate" );   Testing the Java Extension   When you try to access the web site through Stingray, you will be prompted for a username and password; the LdapAuthenticate extension checks that the username and password can bind to the configured Active Directory database, and refuses access if not: If you are unable to log in, cancel the prompt dialog box to see the error reported by the Java extension. In the following case, there was a networking problem; the extension could not contact the database server provided in the 'DB' parameter: Caching the Authentication Results   Caching the Authentication response from the Java extension will improve the performance of the web site and reduce the load on the database server.   You can modify the TrafficScript rule that calls the extension so that it records successful logins, caching them for a period of time. The following rule uses the data.set() TrafficScript function to record successful logins, caching this information for 10 minutes before attempting to reauthenticate the user against the database server.   $auth = http.getHeader( "Authorization" ); if( data.get( $auth ) < sys.time() ) { data.remove( $auth ); java.run( "LdapAuthenticate" ); # if we got here, we were authenticated. # Cache this information for 600 seconds data.set( $auth, sys.time()+600 ); }   A more sophisticated Java Extension implementation   In enterprise deployments, users often cannot bind to the LDAP or Active Directory database directly.  This example runs a custom search against the Active Directory database to locate the distinguishedName corresponding to the userid provided in the login attempt, then attempts to verify that the user can bind using their distinguishedName and the password they provided.   The Java Extension (version 2)   import java.io.IOException; import java.io.PrintWriter; import java.util.Hashtable; import javax.naming.NamingEnumeration; import javax.naming.Context; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.zeus.ZXTMServlet.ZXTMHttpServletRequest; public class LdapAuthenticate extends HttpServlet { private static final long serialVersionUID = 1L; private String dirServer; private String realm; private String authentication; private String bindDN; private String bindPassword; private String baseDN; private String filter; public void init( ServletConfig config) throws ServletException { super.init( config ); dirServer = config.getInitParameter( "DB" ); if( dirServer == null ) throw new ServletException( "No DB configured" ); realm = config.getInitParameter( "Realm" ); if( realm == null ) realm = "Secure site"; authentication = config.getInitParameter( "authentication" ); if( authentication == null ) authentication = "simple"; bindDN = config.getInitParameter( "bindDN" ); if( dirServer == null ) throw new ServletException( "No bindDN configured" ); bindPassword = config.getInitParameter( "bindPassword" ); if( dirServer == null ) throw new ServletException( "No bindPassword configured" ); baseDN = config.getInitParameter( "baseDN" ); if( dirServer == null ) throw new ServletException( "No baseDN configured" ); filter = config.getInitParameter( "filter" ); if( dirServer == null ) throw new ServletException( "No filter configured" ); } public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { try { ZXTMHttpServletRequest zreq = (ZXTMHttpServletRequest)req; String[] userPass = zreq.getRemoteUserAndPassword(); if( userPass == null ) throw new Exception( "No Authentication details" ); Hashtable<String, String> env = new Hashtable<String, String>(); env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put( Context.PROVIDER_URL, "LDAP://" + dirServer ); env.put( Context.SECURITY_AUTHENTICATION, authentication ); /* Bind with admin credentials */ NamingEnumeration<SearchResult> ne; String searchfilter = filter.replace( "%u", userPass[0] ); try { env.put( Context.SECURITY_PRINCIPAL, bindDN ); env.put( Context.SECURITY_CREDENTIALS, bindPassword ); DirContext ctx = new InitialDirContext( env ); String[] attrIDs = { "distinguishedName" }; SearchControls sc = new SearchControls(); sc.setReturningAttributes( attrIDs ); sc.setSearchScope(SearchControls.SUBTREE_SCOPE); ne = ctx.search(baseDN, searchfilter, sc); ctx.close(); } catch( Exception e ) { throw new Exception( "Failed to bind with master credentials: " + e.toString() ); } if( ne == null || !ne.hasMore() ) { throw new Exception( "No such user " + userPass[0] ); } SearchResult sr = (SearchResult) ne.next(); Attributes attrs = sr.getAttributes(); Attribute dnAttr = attrs.get("distinguishedName"); String dn = (String) dnAttr.get(); /* Now bind using dn with the user credentials */ try { env.put( Context.SECURITY_PRINCIPAL, dn ); env.put( Context.SECURITY_CREDENTIALS, userPass[1] ); DirContext ctx = new InitialDirContext( env ); ctx.close(); } catch( Exception e ) { throw new Exception( "Failed to bind with user credentials: " + e.toString() ); } // No exceptions thrown... must have been successful ;-) return; } catch( Exception e ) { res.setHeader( "WWW-Authenticate", "Basic realm=\"" + realm + "\"" ); res.setHeader( "Content-Type", "text/html" ); res.setStatus( 401 ); String message = "<html>" + "<head><title>Unauthorized</title></head>" + "<body>" + "<h2>Unauthorized - please log in</h2>" + "<p>Please log in with your system username and password</p>" + "<p>Error: " + e.toString() + "</p>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println( message ); } } public void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { doGet( req, res ); } }   This version of the Java Extension takes additional initialization parameters: It first searches the database for a distinguishedName using a query resembling:   $ ldapsearch -h DB -D bindDN -w bindPassword -b baseDN filter distinguishedName   Where the %u in the filter is replaced with the username in the login attempt.   It then attempts to bind to the database using a query resembling:   $ ldapsearch -h DB -D distinguishedName -w userpassword   ... and permits access if that bind is successful.
View full article
This article combines two Stingray technologies – Java Extensions and the Control API – and shows you how to query the status of Stingray with a simple, authenticated web request to generate an immediate status report.   Introduction   Stingray’s admin interface gives you plenty of useful information about the performance and health of Stingray, but sometimes you may want a lighter, quicker way of checking the status. Apache's ServerStatus page is a good model of a report that you can access from within a public website using a privileged URL.   This article presents a Java Extension that generates a similar report. The Java Extension is run when a particular URL is requested and appropriate authentication is given; the extension queries Stingray via the SOAP-based Control API and retrieves a range of status information and a list of the recently-processed connections.   Before you proceed   First, follow the instructions in the article Using Stingray's SOAP Control API with Java to create an appropriate Stingray-API.jar file. Upload this interface file to the Java Extensions catalog in the Stingray admin interface, and ensure that all of the the required dependencies are also installed in the Java catalog.   The Java Extension   The attached Java Extension source file (ServerStatus.java) is quite long; you can shortcut building it from source by using the attached ServerStatus.jar file and uploading it straight to the Java Extensions catalog in Stingray.   Compiling the extension   At compilation time, the extension will require the Stingray-API.jar and Apache Axis 1.4 jar files for type checking. For example, if you use the Eclipse IDE, you should add these files as 'External Jars' in the build path.  From the command line:   $ javac -cp Stingray-API.jar:zxtm-servlet.jar:servlet.jar:axis-1_4/lib/* ServerStatus.java   The extension compiles to several separate class files. You can upload each class file to the JAva catalog, or you can use the 'Export' command (in Eclipse) to create a Java jar file containing the compiled class files from the ServerStatus project. From the command line:   $ jar -cvf ServerStatus.jar ServerStatus*.class     Using the ServerStatus Java Extension   The ServerStatus java extension prompts for a username and password; provide the username and password for the 'admin' user in the Stingray Admin Interface.   Use the following RuleBuilder request rule to invoke the extension:     ... or the equivalent TrafficScript rule:   $path = http.getPath(); if( $path == "/serverstatus" ) java.run( "ServerStatus" );   Then, go to http://www.site.com/serverstatus to run the extension: Security   The extension first checks that the HTTP request that has invoked it contains a username and password. If the username and password is missing, the extension returns a '401 Authenticate' message to prompt the caller to provide them. The caller should provide the username and password for the 'admin' user in the Stingray Admin Interface.   The extension then attempts to connect to the local Stingray Control API interface using the supplied username/password pair. If the connection fails because the authentication details are invalid, the extension again prompts for a new username and password.   Use SSL!   When you provide the admin user and password, it’s very advisable to only do so over an SSL-enabled website. You can ensure that the user name and password is never requested by a non-SSL site by modifying the rule as follows:   $path = http.getPath(); if( $path == "/serverstatus" && ssl.isSSL() ) java.run( "ServerStatus" );   If necessary, configure a special SSL virtual server in Stingray to host the extension. The extension will return identical results no matter which virtual server it is invoked from.   Read more   Tech Tip: Reading Stingray's internal diagnosis report using Perl and SOAP Java Extensions - Overview Using Stingray's SOAP Control API with Java
View full article
Recent investigations have revealed an error in the PHP and Java floating point library that may be exploited to cause a denial of service against a web service. You can use Stingray Traffic Manager to filter incoming traffic and discard requests that may seek to exploit this fault.   Background   In January 2011, a bug was discovered in PHP's floating point library. Under certain circumstances, an attempt to convert the string '2.2250738585072011e-308' into a floating point value would hang the PHP runtime.   A similar problem was discovered in the Java runtime (and compiler). The two articles give a detailed description of the nature of the problem and its cause (relating to the parsing of a number close to DBL_MIN, the smallest non-zero number that can be represented as a floating point).   The implications   What are the implications to a web developer or security team? This fault can be exploited to mount a denial-of-service attack if an attacker can send a carefully-crafted request that causes the PHP or Java runtime to attempt to convert a string into the problematic floating-point value. Web developers are accustomed to treating user input with suspicion - for example, careful escaping to prevent SQL injection attacks - but who would have thought that an innocuous floating point number could pose a similar threat? Any application code that parses input into a floating point could be vulnerable; for example, a mapping API that takes coordinates as input may be vulnerable.   However, there's an even simpler potential problem that is inherent the HTTP protocol; the family of 'Accept' HTTP headers use floating point scores that may be exploitable in certain implementations.   Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, /;q=0.5 Accept-Charset: iso-8859-5, unicode-1-1;q=0.8 Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0 Accept-Language: da, en-gb;q=0.8, en;q=0.7   The wisest solution to protecting against this vulnerability would be to deploy a Web Application Firewall (see Stingray Application Firewall) and verify that the baseline protection rules detect attempts to exploit this attack. It's also possible to detect and drop these attacks using a TrafficScript rule, and this article presents a couple of solutions.   Floating Point: Solution 1   The following TrafficScript request rule checks all of the headers in each HTTP request. If the headers contain the sequence of digits that are the signature of this number, then the rule logs a warning and drops the request immediately.   $headers = http.getHeaders(); foreach( $header in hash.keys( $headers ) ) { $value = $headers[$header]; # remove any decimal points $value = string.replace( $value, '.', '' ); if( string.contains( $value, "2225073858507201" ) ) { log.warn( "suspect request - dropping" ); connection.discard(); } }   The result:     Floating Point: Solution 2   This more advanced solution checks both headers and form parameters, logs a more descriptive error message and illustrates the use of TrafficScript subroutines to minimise duplicated code:   # Checks the array of key-value (headers or form parameters) # If any value contains the suspect floating point value, return the # name of the header or form parameter sub check( $h ) { foreach( $k in hash.keys( $h ) ) { $v = $h[$k]; # remove any decimal points $v = string.replace( $v, '.', '' ); if( string.contains( $v, "2225073858507201" ) ) return $k; } } # Log the request and drop it immediately sub logAndDrop( $reason, $k, $v ) { $ip = request.getRemoteIP(); $country = geo.getCountry( $ip ); if( !$country ) $country = 'unknown'; $msg = 'Request from ' . $ip . ' (' . $country . ') ' . ' contained suspicious ' . $reason . ': ' . $k . ': ' . $v; log.warn( $msg ); # Optional - raise an event to trigger a configured event handler # event.emit( "FloatingPointAttack", $msg ); connection.discard(); } $headers = http.getHeaders(); if( $h = check( $headers ) ) logAndDrop( "header", $h, $headers[$h] ); $params = http.getFormParams(); if( $h = check( $params ) ) logAndDrop( "parameter", $h, $params[$h] );   The result, from an internal IP address (192.168.35.1) and using a querystring ?userid=2.2250738585072011e-308:     There is a very, very slim risk of false positives with these rules (dropping connections which would not have a malicious effect), but the probability of the string "2225073858507201" appearing is miniscule (except perhaps for blog posts about this very vulnerability...).
View full article
Here (attached) is a library that uses TrafficScript array and hashes to provide another new data structure, the set: an unordered collection in which an element occurs either once or not at all. An example use would be "words I have seen on the page". The trick to implementing this is to realise that TrafficScript already has an efficient set implementation: the hash (associative array). Specifically, you can put your data in the keys of the hash and use an arbitrary constant as the value. This means that inserting, deleting and checking membership of the set are all fast operations. While you could use that trick directly on an ad-hoc basis in individual rules, this library will improve readability and provide some type checking. If you're curious and use lang.dump($some_set) to inspect the data structure, you'll see something like this (note that the order of hash elements is arbitrary): [ "type" => "set", "values" => [ "foo" => 1, "123" => 1, "bar" => 1 ] ] One limitation of this structure is that only scalars can be members of sets, since only scalars can be hash keys. In this library, if you insert an array, each element will be inserted, and if you try to insert a hash, you'll get a warning and nothing will be inserted. The library includes the following functions: set.new() Returns a new (empty) set. set.destroy( $set ) Destroy a set. set.insert( $set, $value ) Insert a value (or another set or an array of values) into the set. set.remove( $set, $value ) Remove a value (or set or array of values) from the set. set.contains( $set, $value ) Check if the set contains a particular value. set.toarray( $set ) Return all the values in the set. set.empty( $set ) Empty a set. set.union( $a, $b ) Returns the set of elements that are in $a or $b. set.intersection( $a, $b ) Returns the set of elements that are in $a and $b. set.difference( $a, $b ) Returns the set of elements that are in $a and not in $b. set.count( $set ) Count the number of items in the set. set.subseteq( $a, $b ) Check if $a is a (non-strict) subset of $b. set.superseteq( $a, $b ) Check if $a is a (non-strict) superset of $b. To use it, add the library to your TrafficScript rules catlog, and then, in another rule, use: import libSet.rts as set; and all the 'set' functions above will be available. Here's an example of how you could use it. This rule will expect the words in $target to occur somewhere on the page, and write a log line if any of them are missing. import libSet.rts as set; $ctype = http.getResponseHeader( "Content-Type" ); if( ! string.startswith( $ctype, "text/html" ) ) break; $target = set.new(); set.insert( $target, ["riverbed","news","articles"] ); $used = set.new(); $words = string.split(string.lowercase(http.getResponseBody())); foreach( $w in $words ) {    set.insert( $used, $w ); } $unused = set.toarray( set.difference( $target, $used )); if( array.length( $unused ) ) {    log.info( http.getPath().": " . array.join(array.sort($unused),", ") );
View full article