The following sections contain more information:
Novell ZENworks Network Access Control is an efficient, flexible and extensible testing platform. All tests are implemented in the object oriented programming language called Python. Python is a well- respected, clean, and efficient scripting language. Because the language is object oriented and the Novell ZENworks Network Access Control test platform is extensible, new tests can be developed easily.
Existing tests can also be extended using inheritance—a programming language’s ability to derive one class/script from another class and override and extend methods of that class.
You need some programming experience to extend and add tests. If you have previously used Perl to complete these tasks, you might find that Python is a better choice as a programming language for the tasks described in the following sections.
IMPORTANT:You should familiarize yourself with Python and with the rest of the Novell ZENworks Network Access Control product before attempting to create custom test scripts.
This version of Novell ZENworks Network Access Control uses Python v2.4.1:
Python home: http://www.python.org/
Python 2.4.1 tutorial: http://www.python.org/doc/2.4.1/tut/tut.html
Python language reference: http://www.python.org/doc/2.4.1/
Sample test scripts are on the Novell ZENworks Network Access Control CD in the /sampleScripts folder.
Using Python, try changing the error messages in an existing test script. This task can help you to familiarize yourself with the Novell ZENworks Network Access Control scripting API. Each Novell ZENworks Network Access Control test script defines a test class. To change an error message, create a new script that derives a new test class from an existing test class and modify the return hash of the runTest method.
Log in as root to the Novell ZENworks Network Access Control server using SSH.
Open the /sampleScripts/myCheckSoftwareNotAllowed.py file on the Novell ZENworks Network Access Control CD in a text editor.
Examine the code. The comments explain each section of code. The following example shows the contents of the file.
Example 16-1 Test Script Code
#!/usr/bin/python from checkSoftwareNotAllowed import CheckSoftwareNotAllowed # # This allows a script to be tested from the command line. # if __name__ == '__main__': import myCheckSoftwareNotAllowed t = myCheckSoftwareNotAllowed.MyCheckSoftwareNotAllowed() t.processCommandLine() # # The class definition. MyCheckSofwareNotAllowed is derived # from the existing test CheckSoftwareNotAllowed and inherits # all the existing tests functionality. # class MyCheckSoftwareNotAllowed(CheckSoftwareNotAllowed): # _____________________________________________________________________________ # Override the testId to be unique from all other test ids # testId = "MyCheckSoftwareNotAllowed" # # Rename your derived test # testName = "My check software not allowed" _____________________________________________________________________________ # # All test classes must define the runTest method with the self and debug # parameters # def runTest(self,debug=0): # # Get the result hash from the CheckSoftwareNotAllowed test # and modify the result message based on the result code. # result = CheckSoftwareNotAllowed.runTest(self,debug) if result["result_code"] == "fail": result["result_message"] = "The MyCheckSoftwareNotAllowed test failed." elif result["result_code"] == "pass": result["result_message"] = "The MyCheckSoftwareNotAllowed test passed." return result
You can change the result["result_message"] to whatever text you want. This message is what the end-user sees in the access windows. This text also appears in the management user interface when you run reports.
Every test must return a hash with the following keys:
status_code – 0 test did not run, error occurred, 1 test ran result_code – pass, fail result_message – the text to display to the user
NOTE:Do not change the status_code or the result_code for this example.
Once you have completed your edits and saved the myCheckSoftwareNotAllowed.py file, copy it to the following directory on the Novell ZENworks Network Access Control MS:
/usr/local/nac/scripts/Custom/Tests
If you have created new base classes, copy them to the following directory on the Novell ZENworks Network Access Control MS:
/usr/local/nac/scripts/Custom/BaseClasses
IMPORTANT:When updating or modifying files, use the Custom directory tree (Custom/BaseClasses, Custom/Tests). The Custom directory tree is a mirror (with symbolic links) to the live test tree (scripts/BaseClasses and scripts/Tests). The live tree is not modified directly, but is modified with the installCustomTests script and the RPM mechanism.
Once your custom test script is complete, and you are ready to push it out to all of the ESs, verify that the scripts and base classes are under the Custom directory tree as specified above, and enter the following on the command line of the Novell ZENworks Network Access Control MS:
installCustomTests
This command compiles the Python source files, builds an RPM, updates the policy groups, and sends these changes to all ESs. An example of the output from the installCustomTests command is shown as follows:
NOTE:This command affects all ESs, even those that are not currently up and running. Once a stopped ES comes back up, the ES is updated.
Example 16-2 Example InstallCustomTests Output
# installCustomTests
Creating custom test script RPM version 5.0-51
Found 5 python files
+ Compiling python scripts
+ Generating test script XML files
If you continue, this will generate an RPM file containing your custom scripts
and will send the new custom script RPM to the Management Server and all
Enforcement Servers.
_____________________________________________________________________________
--> Press Enter to proceed or Ctrl-C to abort <--
+ Generating RPM spec file
+ Creating RPM file 'NAC-custom-testscripts-5.0-51.i386.rpm'
+ Creating update package file (/tmp/customUpdatePkg.29285.tar.gz)
+ Creating XML file to send custom scripts to the MS (/tmp installCustomTest.29285.xml)
+ Sending XML message to MS to install and distribute custom scripts
00:22:34 INFO channel status changed: Channel: TcpTransportChannel: Socket[addr=localhost/127.0.0.1,port=61616,localport=44041] has connected
_____________________________________________________________________________
00:22:34 DEBUG TCP consumer thread starting
00:22:34 DEBUG Created temporary queue: TemporaryQueue-{TD{ID:perf-ms1-40612-1162365754580-1:0}TD}ID:perf-ms1-40612-1162365754580-6:0
00:22:34 DEBUG Sending request:
<UpdateRequest>
<requestParameters>
<entry>
<string>UPDATE_DATA</string>
<string>/tmp/customUpdatePkg.29285.tar.gz</string>
</entry>
</requestParameters>
</UpdateRequest>
_____________________________________________________________________________
00:22:34 DEBUG Sending message: ACTIVEMQ_TEXT_MESSAGE: id = 0 ActiveMQMessage{ , jmsMessageID = ID:perf-ms1-40612-1162365754580-7:0, bodyAsBytes = org.activemq.io.util.ByteArray@1112783, readOnlyMessage = false, jmsClientID = 'ID:perf-ms1-40612-1162365754580-1:0' , jmsCorrelationID = 'null' , jmsDestination = nac.requests, jmsReplyTo = TemporaryQueue-{TD{ID:perf-ms1-40612-1162365754580-1:0}TD}ID:perf-ms1-40612-1162365754580-6:0, jmsDeliveryMode = 2, jmsRedelivered = false, jmsType = 'null' , jmsExpiration = 1162365784872, jmsPriority = 4, jmsTimestamp = 1162365754872, properties = null, readOnlyProperties = false, entryBrokerName = 'null' , entryClusterName = 'null' , consumerNos = null, transactionId = 'null' , xaTransacted = false, consumerIdentifer = 'null' , messageConsumed = false, transientConsumed = false, sequenceNumber = 0, deliveryCount = 1, dispatchedFromDLQ = false, messageAcknowledge = null, jmsMessageIdentity = null, producerKey = ID:perf-ms1-40612-1162365754580-7: }, text = <UpdateRequest>
_____________________________________________________________________________
<requestParameters>
<entry>
<string>UPDATE_DATA</string>
<string>/tmp/customUpdatePkg.29285.tar.gz</string>
</entry>
</requestParameters>
</UpdateRequest>
_____________________________________________________________________________
00:22:34 DEBUG Waiting for a response on :TemporaryQueue-{TD{ID:perf-ms1-40612-1162365754580-1:0}TD}ID:perf-ms1-40612-1162365754580-6:0
_____________________________________________________________________________
00:22:36 DEBUG Message received: ACTIVEMQ_TEXT_MESSAGE: id = 0 ActiveMQMessage{ , jmsMessageID = ID:perf-ms1-51331-1162363440379-15:3, bodyAsBytes = org.activemq.io.util.ByteArray@1362012, readOnlyMessage = true, jmsClientID = '93baaf5a-b0ed-4fc2-a3ae-ec6460caedc0' , jmsCorrelationID = 'null' , jmsDestination = TemporaryQueue-{TD{ID:perf-ms1-40612-1162365754580-1:0}TD}ID:perf-ms1-40612-1162365754580-6:0, jmsReplyTo = null, jmsDeliveryMode = 2, jmsRedelivered = false, jmsType = 'null' , jmsExpiration = 1162365766750, jmsPriority = 4, jmsTimestamp = 1162365756750, properties = null, readOnlyProperties = true, entryBrokerName = '172.30.1.50' , entryClusterName = 'default' , consumerNos = [0], transactionId = 'null' , xaTransacted = false, consumerIdentifer = 'ID:perf-ms1-40612-1162365754580-1:0.1.1' , messageConsumed = false, transientConsumed = false, sequenceNumber = 3, deliveryCount = 1, dispatchedFromDLQ = false, messageAcknowledge = org.activemq.ActiveMQSession@73a34b, jmsMessageIdentity = null, producerKey = ID:perf-ms1-51331-1162363440379-15: }, text = <NACResponse><resultStatus>true</resultStatus><response class="string">9X</response><ip>172.30.1.50</ip><id>MNM</id><originalTimeStamp>1162365756707</originalTimeStamp></NACResponse>
00:22:36 DEBUG Received: <NACResponse><resultStatus>true</resultStatus><response class="string">9X</response><ip>172.30.1.50</ip><id>MNM</id><originalTimeStamp>1162365756707</originalTimeStamp></NACResponse>
Done
NOTE:The output between the “+ Sending XML message to MS to install and distribute custom scripts” message and the “Done” message in Example InstallCustomTests Output is output from the command that installed the custom scripts and shows the status of the sending the XML JMS request to the MS.
Creating a custom test script is similar to the previous error message example; however, you must define a few more things and then add your own test functionality. Examine the test script template shown in testTemplate.py. The comments explain each section of code. Once you are comfortable with the template, the following section contains an example that shows how to create a checkOpenPorts.py test script, which tests an endpoint for specified open ports.
HINT:This template file is included on the CD at /sampleScripts/testTemplate.py, so you can edit it instead of retyping it.
Example 16-3 testTemplate.py
#!/usr/bin/python
from BaseClasses.SABase import SABase as SABase
#
# This allows a script to be tested from the command line.
#
if __name__ == '__main__':
import testTemplate
t = testTemplate.TestTemplate()
t.processCommandLine()
_____________________________________________________________________________
#
# The class definition. All classes must be derived from the SABase class.
#
class TestTemplate(SABase):
#
# Make up a test id. Just make sure it doesn't match any existing test ids.
#
testId = "TestId"
#
# Make up test name. Just make sure it doesn't match any existing test names.
#
testName = "Test Name"
_____________________________________________________________________________
#
# Assign the test to an existing group or create a new group.
# Groups are configured and created in the policies.xml file <group>
# section (See the Adding new groups section).
#
testGroupId = "TestGroup"
#
# This is the HTML that will be displayed in the test properties page
# in the policy editor.
#
testConfig = \
"""
<HTML>Test Config HTML</HTML>
"""
#
# These are any default values you want to assign to the input parameters
# in the testConfig HTML.
#
defaultConfigValues = {}
_____________________________________________________________________________
#
# A short summary for the test. This will show up in the description field
# when editing NAC policies in the management UI.
#
testSummary = \
"""
My short description
"""
_____________________________________________________________________________
#
#
# This is field is unused at the moment.
# field in the policy editor.
#
testDescription = ''
_____________________________________________________________________________
#
# These are the arguments to run the test. This is displayed in the command
# line help.
#
testArguments = \
"""
My test arguments
"""
_____________________________________________________________________________
#
# All tests must define the runTest method with the self and the debug
# parameters.
#
def runTest(self,debug=0):
#
# All tests must call the initialize routine
#
self.initTest()
_____________________________________________________________________________
#
# Create a hash to store the return results.
# All tests must fill return a hash with the following keys:
#
# status_code - 0 if an unexpected error occurred, 1 if successful
# result_code - pass, fail or some error
# result_message - the message to display to the end-user
#
returnHash = {}
returnHash["status_code"] = 1
returnHash["result_code"] = "pass"
returnHash["result_message"] = "Some nice text that a user can read here."
_____________________________________________________________________________
try:
#
# Replace 'pass' with your test here. Modify the returnHash accordingly.
#
pass
_____________________________________________________________________________
except:
#
# Set the return status when exception occurs
#
import sys
returnHash['status_code'] = 0
returnHash['result_code'] = "unknown_error"
returnHash['result_message'] = sys.exc_type, sys.exc_value
return(returnHash)
_____________________________________________________________________________
#
# Always use the doReturn function; this allows superclass to add or modify
# any items in the returnHash as necessary.
#
return(self.doReturn(returnHash))
Use the template, as shown in testTemplate.py, to create a new test script. As an example, the new test script is called checkOpenPorts.py, and it fails if any of the specified ports are open on the target host being tested. Before examining the code, consider the following information about the test scripts:
All test scripts contain a self.inputParams hash table that has all input parameters configured through the policy properties HTML. For example, if the testConfig variable for the test is set to:
<input id="myparam" name="myparam" value="">
Then, the self inputParams contains a myparams key that is set to the value of the HTML input element set in the policy editor.
All test scripts contain a self.session member variable that is set by Novell ZENworks Network Access Control when the test class is instantiated. It contains a reference to a Session object, which is a built-in Python class defined by Novell ZENworks Network Access Control and is used internally by the BasicTests class described later in this section. However, to retrieve the host name or IP address, use host() method:
self.session.host()
when developing scripts.
All tests contain a reference to the BasicTests class called self.bt. The self.bt class gives you access to commonly used functions for testing endpoints including registry operations and service operations. See Section 16.10.5, BasicTests API for more information on the BasicTests API. This example does not use this API.
checkOpenPorts.py script shows the code for the new checkOpenPorts.py test. The file is included on the Novell ZENworks Network Access Control CD as /sampleScripts/checkOpenPorts.py. Review the code. The comments explain each section of the code.
Example 16-4 checkOpenPorts.py script
#!/usr/bin/python
from BaseClasses.SABase import SABase as SABase
#
# This allows a script to be tested from the command line.
#
if __name__ == '__main__':
import checkOpenPorts
t = checkOpenPorts.CheckOpenPorts()
t.processCommandLine()
_____________________________________________________________________________
#
# The class definition. All classes must be derived from the SABase class.
#
class CheckOpenPorts(SABase):
#
# Make up a test id. Just make sure it doesn't match any existing test ids
#
testId = "CheckOpenPorts"
#
# Make up test name. Just make sure it doesn't match any existing test names.
#
testName = "Open ports"
_____________________________________________________________________________
#
# Assign the test to an existing group or create a new group.
# Groups are configured and created in the policies.xml file <group>
# section (See the Adding new groups section).
#
testGroupId = "MyCustomTests"
#
# This is the HTML that will be displayed in the test properties page
# in the policy editor. All this HTML isn't REALLY necessary, but we
# to keep the Novell ZENworks Network Access Control Web UI pretty.
#
_____________________________________________________________________________
testConfig = \
"""
<div id="test_parameters">
<table height="100%" width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td colspan="2" style="padding: 5px 3px 5px 3px;">
Enter a list of ports that are not allowed to be open on the
endpoint. Add ports separated by a comma. For example, 23,80.
</td>
</tr>
<tr>
<td style="padding: 3px 0px 3px 3px;">
<textarea name="ports_not_allowed" rows="5" cols="30" wrap="on" style="border: 1px solid #A894D1;
font-family: Arial, Helvetica, sans-serif; font-size: 8pt; padding: 1px 2px 1px 2px;"></textarea>
</td>
</tr>
</tbody>
</table>
</div>
"""
_____________________________________________________________________________
#
# These are any default values you want to assign to the input parameters
# in the testConfig HTML. The first time this test is configured for a
# policy or if the test is never configured for a policy, this will be
# the default. Notice the key in this hash corresponds to the input element
# above in the testConfig.
#
defaultConfigValues = { "ports_not_allowed" : "23,80" }
_____________________________________________________________________________
#
# Make up a detailed description for the test.
#
testDescription = \
"""
This test takes a list of ports that should NOT be found open on
the remote host. If any port is found open, this test will
fail. This script will only succeed if none of the undesired ports
are found open.
"""
_____________________________________________________________________________
#
# Make up a summary for the test. This will show up in the description
# field in the policy editor.
#
testSummary = "This test takes a list of ports that should NOT be found open on the remote host. If any port is found open, this test will fail. This script will only succeed if none of the undesired ports are found open."
_____________________________________________________________________________
#
# These are the arguments to run the test. This is displayed in the command
# line help.
#
testArguments = \
"""
--host=<hostname, IP, or NETBIOS>
--input ports_not_allowed=<comma delimited list of ports>
_____________________________________________________________________________
Example: <this script> --host=somehost --input "ports_not_allowed=23,80"
"""
#
# All tests must define the runTest method with the self and the debug
# parameters.
#
def runTest(self,debug=0):
#
# All tests must call the initialize routine
#
self.initTest()
if debug:
print "Starting checkOpenPorts(host="+self.session.host()+", session="+self.session.id()+")"
_____________________________________________________________________________
#
# Create a hash to store the return results.
# All tests must fill return a hash with the following keys:
#
# status_code - 0 if an unexpected error occurred, 1 if successful
# result_code - pass, fail or some error
# result_message - the message to display to the end-user
#
returnHash = {}
returnHash["status_code"] = 1
returnHash["result_code"] = "pass"
returnHash["result_message"] = "The ports were not open."
_____________________________________________________________________________
try:
ports = []
if self.inputParams.has_key("ports_not_allowed"):
ports = self.inputParams["ports_not_allowed"].split(",")
else:
# No ports not allowed, pass
return(self.doReturn(returnHash))
_____________________________________________________________________________
if debug:
print "Checking ports " + str(ports) + " on host " + self.session.host()
#
# Do your test here. Modify the returnHash accordingly.
#
portsOpen = ""
#
# Use a Python socket to connect directly to the target host
#
import socket
_____________________________________________________________________________
for p in ports:
hp = self.session.host()+":"+str(p)
s = None
try:
if debug:
print "Connecting to " + hp
_____________________________________________________________________________
#
# Try to open the port. Throws an exception if connection
# is refused or times out (set timeout to 5 seconds).
#
# Note that Novell ZENworks Network Access Control uses a restricted Python socket
# library that doesn't allow connections to arbitrary
# hosts. Normally, the first element of the tuple passed
# to socket.connect() is the IP or hostname; in SA, you
# must pass the Session object form which the socket
# object will get the target host IP/name.
#
s = socket.socket()
s.settimeout(5)
s.connect((self.session, int(p)))
# Uh oh, no exception. The port was open
_____________________________________________________________________________
s.close()
if debug:
print "Connected to "+hp+". Port open!"
#
# Add the port to our list of open ports for use later
#
portsOpen += str(p) + ","
except:
if s is not None:
try:
s.close()
except:
pass
_____________________________________________________________________________
import sys
print "checkOpenPorts(host="+self.session.host()+", session="+self.session.id()+"): ", sys.exc_type, sys.exc_value
if debug:
print "Could not connect to "+hp+". Port not open."
# Good, it wasn't open
#
# There are ports open, so set the returnHash values
# to indicate that the endpoint failed the test.
#
if portsOpen != "":
returnHash["status_code"] = 1
returnHash["result_code"] = "fail"
returnHash["result_message"] = "The following ports that are not allowed open were open: " + portsOpen.rstrip(", ")
_____________________________________________________________________________
except:
#
# Set the return status when exception occurs
#
import sys
returnHash['status_code'] = 0
returnHash['result_code'] = "unknown_error"
returnHash['result_message'] = sys.exc_type, sys.exc_value
return(returnHash)
#
# Always use the doReturn function. This will record test timings as well as
# encode the result_message into a format compatible with Novell ZENworks Network Access Control
#
return(self.doReturn(returnHash))
Once you have completed your test script modifications, save the script as described in Step 6.
Save any new classes as described in Step 7.
Push the new test out to all ESs as described in Step 8.
For the final test, connect to:
http://<Novell ZENworks Network Access Control ip>:88
and test your Windows endpoint. If you have ports open that are not allowed, this test fails.
Every Novell ZENworks Network Access Control test has a base functionality described as follows:
…
try:
self.bt.getregKeyExists( “HKEY_LOCAL_MACHINE\\Software\\America Online\\AIM”)
except:
import sys
returnHash["status_code"] = 0
returnHash["result_code"] = "unknown_error"
returnHash["result_message"] = sys.exc_type, sys.exc_value
…
The following table describes the BasicTests API.
Table 16-4 BasicTests API
NOTE:Service Name
The serviceName parameters can be the registry name or the display name. For example, TlntSvr or Telnet can be used to identify the Telnet service.
For performance reasons, it is important to use the same case when specifying the same service name in multiple calls. Even though the windows process table is not case-sensitive, the test result cache is case-sensitive.
NOTE:Registry key parameters use HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, HKEY_USER to specify the subtree of the registry. For example, HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion.
Registry Key
For performance reasons, it is important to use the same case when specifying the same registry key in multiple calls. Even though the windows registry is not case-sensitive, the test result cache is case-sensitive.
NOTE:Environment variable templates can be used in filenames. For example, %AppData%\\Adobe.
File Name
For performance reasons, it is important to use the same case when specifying the same file name in multiple calls. Even though the windows file system is not case-sensitive, the test result cache is case-sensitive.