diff options
| author | Rob Crittenden <rcritten@redhat.com> | 2014-02-26 16:37:51 -0500 |
|---|---|---|
| committer | Rob Crittenden <rcritten@redhat.com> | 2014-02-26 16:50:55 -0500 |
| commit | 07c27296c2c940cb119386304ebffb4ab41f0fb9 (patch) | |
| tree | 201d87fb7f87d734bcec06aef66d8f20d8fb4706 /test | |
| parent | c2ac0d128e776f3edb8aeb8920bf41b99742e74c (diff) | |
| download | mod_nss-07c27296c2c940cb119386304ebffb4ab41f0fb9.tar.gz mod_nss-07c27296c2c940cb119386304ebffb4ab41f0fb9.tar.xz mod_nss-07c27296c2c940cb119386304ebffb4ab41f0fb9.zip | |
Add some basic functional tests.
This tests in an in-tree Apache instance using the local libmodnss.so
shared library, so no pre-installation is necessary.
The tests use python-nose and a hacked python-requests library. It is
hacked so I can obtain the negotiated cipher and protocol as well as
pass a few other things into it.
Tests right now are limited to GET requests.
A new user certificate for 'beta' was added to gencert to do pass/fail
access control testing.
The basic process of the tests are:
- run setup.sh which sets up a new instance with createinstance.sh
and does some variable substitution.
- nosetests -v
I picture multiple test "suites" of different configurations. Right now
there is only one. A template file is provided for each suite.
Tested only on Fedora 20 right now.
Diffstat (limited to 'test')
| -rwxr-xr-x | test/createinstance.sh | 59 | ||||
| -rw-r--r-- | test/httpd.conf.tmpl | 999 | ||||
| -rwxr-xr-x | test/setup.sh | 55 | ||||
| -rw-r--r-- | test/suite1.tmpl | 65 | ||||
| -rw-r--r-- | test/test.py | 138 | ||||
| -rw-r--r-- | test/test_config.py | 186 | ||||
| -rw-r--r-- | test/test_request.py | 190 | ||||
| -rw-r--r-- | test/test_util.py | 52 |
8 files changed, 1744 insertions, 0 deletions
diff --git a/test/createinstance.sh b/test/createinstance.sh new file mode 100755 index 0000000..1eaa644 --- /dev/null +++ b/test/createinstance.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Make a temporary Apache instance for testing. + +if [ " $#" -eq 0 ]; then + echo "Usage: $0 /path/to/instance" + exit 1 +fi +target=$1 + +echo "Creating instance in $target" +mkdir -p $target + +cd $target +mkdir alias +mkdir conf +mkdir conf.d +mkdir logs +mkdir run +mkdir content +mkdir cgi-bin +mkdir lib + +# Create the content +mkdir content/rc4_cipher +mkdir content/acl + +cat > content/index.html << EOF +<html> +Basic index page +</html +EOF +cp content/index.html content/acl/aclS01.html +cp content/index.html content/acl/aclS02.html +cp content/index.html content/acl/aclS03.html +cp content/index.html content/secret-test.html + +ln -s /etc/httpd/modules modules + +dn="E=alpha@`hostname`,CN=Frank Alpha,UID=alpha,OU=People,O=example.com,C=US" +cat > conf/htpasswd << EOF +/${dn}:xxj31ZMTZzkVA +EOF + +# Create start/stop scripts + +cat << EOF > start +#!/bin/sh +HTTPD=/usr/sbin/httpd +\$HTTPD -k start -d . -f ./conf/httpd.conf +EOF + +cat << EOF > stop +#!/bin/sh +HTTPD=/usr/sbin/httpd +\$HTTPD -k stop -d . -f ./conf/httpd.conf +EOF + +chmod 0755 start stop diff --git a/test/httpd.conf.tmpl b/test/httpd.conf.tmpl new file mode 100644 index 0000000..36af91d --- /dev/null +++ b/test/httpd.conf.tmpl @@ -0,0 +1,999 @@ +# +# This is the main Apache HTTP server configuration file. It contains the +# configuration directives that give the server its instructions. +# See <URL:http://httpd.apache.org/docs/2.2> for detailed information. +# In particular, see +# <URL:http://httpd.apache.org/docs/2.2/mod/directives.html> +# for a discussion of each configuration directive. +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# The configuration directives are grouped into three basic sections: +# 1. Directives that control the operation of the Apache server process as a +# whole (the 'global environment'). +# 2. Directives that define the parameters of the 'main' or 'default' server, +# which responds to requests that aren't handled by a virtual host. +# These directives also provide default values for the settings +# of all virtual hosts. +# 3. Settings for virtual hosts, which allow Web requests to be sent to +# different IP addresses or hostnames and have them handled by the +# same Apache server process. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "logs/foo.log" +# with ServerRoot set to "/etc/httpd" will be interpreted by the +# server as "/etc/httpd/logs/foo.log". +# + +### Section 1: Global Environment +# +# The directives in this section affect the overall operation of Apache, +# such as the number of concurrent requests it can handle or where it +# can find its configuration files. +# + +# +# Don't give away too much information about all the subcomponents +# we are running. Comment out this line if you don't mind remote sites +# finding out what major optional modules you are running +ServerTokens ProductOnly + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the LockFile documentation +# (available at <URL:http://httpd.apache.org/docs/2.2/mod/mpm_common.html#lockfile>); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +ServerRoot "::SERVER_ROOT::" + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. Note the PIDFILE variable in +# /etc/sysconfig/httpd must be set appropriately if this location is +# changed. +# +PidFile run/httpd.pid + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 60 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive Off + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + +## +## Server-Pool Size Regulation (MPM specific) +## + +# prefork MPM +# StartServers: number of server processes to start +# MinSpareServers: minimum number of server processes which are kept spare +# MaxSpareServers: maximum number of server processes which are kept spare +# ServerLimit: maximum value for MaxClients for the lifetime of the server +# MaxClients: maximum number of server processes allowed to start +# MaxRequestsPerChild: maximum number of requests a server process serves +<IfModule prefork.c> +StartServers 8 +MinSpareServers 5 +MaxSpareServers 20 +ServerLimit 256 +MaxClients 256 +MaxRequestsPerChild 4000 +</IfModule> + +# worker MPM +# StartServers: initial number of server processes to start +# MaxClients: maximum number of simultaneous client connections +# MinSpareThreads: minimum number of worker threads which are kept spare +# MaxSpareThreads: maximum number of worker threads which are kept spare +# ThreadsPerChild: constant number of worker threads in each server process +# MaxRequestsPerChild: maximum number of requests a server process serves +<IfModule worker.c> +StartServers 4 +MaxClients 300 +MinSpareThreads 25 +MaxSpareThreads 75 +ThreadsPerChild 25 +MaxRequestsPerChild 0 +</IfModule> + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, instead of the default. See also the <VirtualHost> +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses. +# +#Listen 12.34.56.78:80 +#Listen 80 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# + +LoadModule access_compat_module modules/mod_access_compat.so +LoadModule actions_module modules/mod_actions.so +LoadModule alias_module modules/mod_alias.so +LoadModule allowmethods_module modules/mod_allowmethods.so +LoadModule auth_basic_module modules/mod_auth_basic.so +#LoadModule auth_digest_module modules/mod_auth_digest.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authn_dbd_module modules/mod_authn_dbd.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_socache_module modules/mod_authn_socache.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule authz_dbd_module modules/mod_authz_dbd.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule cache_module modules/mod_cache.so +LoadModule cache_disk_module modules/mod_cache_disk.so +LoadModule data_module modules/mod_data.so +LoadModule dbd_module modules/mod_dbd.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule dir_module modules/mod_dir.so +LoadModule dumpio_module modules/mod_dumpio.so +LoadModule echo_module modules/mod_echo.so +LoadModule env_module modules/mod_env.so +LoadModule expires_module modules/mod_expires.so +LoadModule ext_filter_module modules/mod_ext_filter.so +LoadModule filter_module modules/mod_filter.so +LoadModule headers_module modules/mod_headers.so +LoadModule include_module modules/mod_include.so +LoadModule info_module modules/mod_info.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule macro_module modules/mod_macro.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule mime_module modules/mod_mime.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule remoteip_module modules/mod_remoteip.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule slotmem_plain_module modules/mod_slotmem_plain.so +LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +LoadModule socache_dbm_module modules/mod_socache_dbm.so +LoadModule socache_memcache_module modules/mod_socache_memcache.so +LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +LoadModule status_module modules/mod_status.so +LoadModule substitute_module modules/mod_substitute.so +LoadModule suexec_module modules/mod_suexec.so +LoadModule unique_id_module modules/mod_unique_id.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule userdir_module modules/mod_userdir.so +LoadModule version_module modules/mod_version.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so + +# Include the locally-built NSS module +LoadModule nss_module lib/libmodnss.so + +# Include the configuration we care about +Include /etc/httpd/conf.modules.d/00-mpm.conf +Include /etc/httpd/conf.modules.d/01-cgi.conf + +# +# Load config files from the config directory "/etc/httpd/conf.d". +# +#Include conf.d/*.conf + +# +# ExtendedStatus controls whether Apache will generate "full" status +# information (ExtendedStatus On) or just basic information (ExtendedStatus +# Off) when the "server-status" handler is called. The default is Off. +# +#ExtendedStatus On + +# +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# . On SCO (ODT 3) use "User nouser" and "Group nogroup". +# . On HPUX you may not be able to use shared memory as nobody, and the +# suggested workaround is to create a user www and use that user. +# NOTE that some kernels refuse to setgid(Group) or semctl(IPC_SET) +# when the value of (unsigned)Group is above 60000; +# don't use Group #-1 on these systems! +# +User ::SERVER_UID:: +Group ::SERVER_GID:: + +### Section 2: 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# <VirtualHost> definition. These values also provide defaults for +# any <VirtualHost> containers you may define later in the file. +# +# All of these directives may appear inside <VirtualHost> containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# +ServerAdmin root@localhost + +# +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# +# If this is not set to valid DNS name for your host, server-generated +# redirections will not work. See also the UseCanonicalName directive. +# +# If your host doesn't have a registered DNS name, enter its IP address here. +# You will have to access it by its address anyway, and this will make +# redirections work in a sensible way. +# +#ServerName www.example.com:80 +ServerName localhost:::SERVER_PORT:: + +# +# UseCanonicalName: Determines how Apache constructs self-referencing +# URLs and the SERVER_NAME and SERVER_PORT variables. +# When set "Off", Apache will use the Hostname and Port supplied +# by the client. When set "On", Apache will use the value of the +# ServerName directive. +# +UseCanonicalName On + +# +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +DocumentRoot "::TEST_ROOT::/content" + +# +# Each directory to which Apache has access can be configured with respect +# to which services and features are allowed and/or disabled in that +# directory (and its subdirectories). +# +# First, we configure the "default" to be a very restrictive set of +# features. +# +<Directory /> + Options FollowSymLinks + AllowOverride None +</Directory> + +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. +# + +# +# This should be changed to whatever you set DocumentRoot to. +# +<Directory "::TEST_ROOT::/content"> + + # + # Possible values for the Options directive are "None", "All", + # or any combination of: + # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews + # + # Note that "MultiViews" must be named *explicitly* --- "Options All" + # doesn't give it to you. + # + # The Options directive is both complicated and important. Please see + # http://httpd.apache.org/docs/2.2/mod/core.html#options + # for more information. + # + Options Indexes FollowSymLinks + + # + # AllowOverride controls what directives may be placed in .htaccess files. + # It can be "All", "None", or any combination of the keywords: + # Options FileInfo AuthConfig Limit + # + AllowOverride All + + # + # Controls who can get stuff from this server. + # + Order allow,deny + Allow from all + +</Directory> + +# +# UserDir: The name of the directory that is appended onto a user's home +# directory if a ~user request is received. +# +# The path to the end user account 'public_html' directory must be +# accessible to the webserver userid. This usually means that ~userid +# must have permissions of 711, ~userid/public_html must have permissions +# of 755, and documents contained therein must be world-readable. +# Otherwise, the client will only receive a "403 Forbidden" message. +# +# See also: http://httpd.apache.org/docs/misc/FAQ.html#forbidden +# +<IfModule mod_userdir.c> + # + # UserDir is disabled by default since it can confirm the presence + # of a username on the system (depending on home directory + # permissions). + # + UserDir disabled + + # + # To enable requests to /~user/ to serve the user's public_html + # directory, remove the "UserDir disabled" line above, and uncomment + # the following line instead: + # + #UserDir public_html + +</IfModule> + +# +# Control access to UserDir directories. The following is an example +# for a site where these directories are restricted to read-only. +# +#<Directory /home/*/public_html> +# AllowOverride FileInfo AuthConfig Limit +# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec +# <Limit GET POST OPTIONS> +# Order allow,deny +# Allow from all +# </Limit> +# <LimitExcept GET POST OPTIONS> +# Order deny,allow +# Deny from all +# </LimitExcept> +#</Directory> + +# +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# +# The index.html.var file (a type-map) is used to deliver content- +# negotiated documents. The MultiViews Option can be used for the +# same purpose, but it is much slower. +# +DirectoryIndex index.html index.html.var + +# +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# +<FilesMatch "^\.ht"> + Order allow,deny + Deny from all + Satisfy All +</FilesMatch> + +# +# TypesConfig describes where the mime.types file (or equivalent) is +# to be found. +# +TypesConfig /etc/mime.types + +# +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. +# +<IfModule mod_mime_magic.c> +# MIMEMagicFile /usr/share/magic.mime + MIMEMagicFile conf/magic +</IfModule> + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# +# EnableMMAP: Control whether memory-mapping is used to deliver +# files (assuming that the underlying OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. On some systems, turning it off (regardless of +# filesystem) can improve performance; for details, please see +# http://httpd.apache.org/docs/2.2/mod/core.html#enablemmap +# +#EnableMMAP off + +# +# EnableSendfile: Control whether the sendfile kernel support is +# used to deliver files (assuming that the OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. Please see +# http://httpd.apache.org/docs/2.2/mod/core.html#enablesendfile +# +#EnableSendfile off + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a <VirtualHost> +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a <VirtualHost> +# container, that host's errors will be logged there and not here. +# +ErrorLog logs/error_log + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel debug + +# +# The following directives define some format nicknames for use with +# a CustomLog directive (see below). +# +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %b" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# "combinedio" includes actual counts of actual bytes received (%I) and sent (%O); this +# requires the mod_logio module to be loaded. +#LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + +# +# The location and format of the access logfile (Common Logfile Format). +# If you do not define any access logfiles within a <VirtualHost> +# container, they will be logged here. Contrariwise, if you *do* +# define per-<VirtualHost> access logfiles, transactions will be +# logged therein and *not* in this file. +# +#CustomLog logs/access_log common + +# +# If you would like to have separate agent and referer logfiles, uncomment +# the following directives. +# +#CustomLog logs/referer_log referer +#CustomLog logs/agent_log agent + +# +# For a single logfile with access, agent, and referer information +# (Combined Logfile Format), use the following directive: +# +CustomLog logs/access_log combined + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +# +ServerSignature Off + +# +# Aliases: Add here as many aliases as you need (with no limit). The format is +# Alias fakename realname +# +# Note that if you include a trailing / on fakename then the server will +# require it to be present in the URL. So "/icons" isn't aliased in this +# example, only "/icons/". If the fakename is slash-terminated, then the +# realname must also be slash terminated, and if the fakename omits the +# trailing slash, the realname must also omit it. +# +# We include the /icons/ alias for FancyIndexed directory listings. If you +# do not use FancyIndexing, you may comment this out. +# +Alias /icons/ "/var/www/icons/" + +<Directory "/var/www/icons"> + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all +</Directory> + +# +# WebDAV module configuration section. +# +<IfModule mod_dav_fs.c> + # Location of the WebDAV lock database. + DAVLockDB /var/lib/dav/lockdb +</IfModule> + +# +# ScriptAlias: This controls which directories contain server scripts. +# ScriptAliases are essentially the same as Aliases, except that +# documents in the realname directory are treated as applications and +# run by the server when requested rather than as documents sent to the client. +# The same rules about trailing "/" apply to ScriptAlias directives as to +# Alias. +# +ScriptAlias /cgi-bin/ "::TEST_ROOT::/cgi-bin/" + +# +# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. +# +<Directory "::TEST_ROOT::/cgi-bin"> + AllowOverride None + Options None + Order allow,deny + Allow from all +</Directory> + +# +# Redirect allows you to tell clients about documents which used to exist in +# your server's namespace, but do not anymore. This allows you to tell the +# clients where to look for the relocated document. +# Example: +# Redirect permanent /foo http://www.example.com/bar + +# +# Directives controlling the display of server-generated directory listings. +# + +# +# IndexOptions: Controls the appearance of server-generated directory +# listings. +# +IndexOptions FancyIndexing VersionSort NameWidth=* HTMLTable Charset=UTF-8 + +# +# AddIcon* directives tell the server which icon to show for different +# files or filename extensions. These are only displayed for +# FancyIndexed directories. +# +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip + +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (VID,/icons/movie.gif) video/* + +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/tar.gif .tar +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/text.gif .txt +AddIcon /icons/c.gif .c +AddIcon /icons/p.gif .pl .py +AddIcon /icons/f.gif .for +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tex.gif .tex +AddIcon /icons/bomb.gif core + +AddIcon /icons/back.gif .. +AddIcon /icons/hand.right.gif README +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/blank.gif ^^BLANKICON^^ + +# +# DefaultIcon is which icon to show for files which do not have an icon +# explicitly set. +# +DefaultIcon /icons/unknown.gif + +# +# AddDescription allows you to place a short description after a file in +# server-generated indexes. These are only displayed for FancyIndexed +# directories. +# Format: AddDescription "description" filename +# +#AddDescription "GZIP compressed document" .gz +#AddDescription "tar archive" .tar +#AddDescription "GZIP compressed tar archive" .tgz + +# +# ReadmeName is the name of the README file the server will look for by +# default, and append to directory listings. +# +# HeaderName is the name of a file which should be prepended to +# directory indexes. +ReadmeName README.html +HeaderName HEADER.html + +# +# IndexIgnore is a set of filenames which directory indexing should ignore +# and not include in the listing. Shell-style wildcarding is permitted. +# +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t + +# +# DefaultLanguage and AddLanguage allows you to specify the language of +# a document. You can then use content negotiation to give a browser a +# file in a language the user can understand. +# +# Specify a default language. This means that all data +# going out without a specific language tag (see below) will +# be marked with this one. You probably do NOT want to set +# this unless you are sure it is correct for all cases. +# +# * It is generally better to not mark a page as +# * being a certain language than marking it with the wrong +# * language! +# +# DefaultLanguage nl +# +# Note 1: The suffix does not have to be the same as the language +# keyword --- those with documents in Polish (whose net-standard +# language code is pl) may wish to use "AddLanguage pl .po" to +# avoid the ambiguity with the common suffix for perl scripts. +# +# Note 2: The example entries below illustrate that in some cases +# the two character 'Language' abbreviation is not identical to +# the two character 'Country' code for its country, +# E.g. 'Danmark/dk' versus 'Danish/da'. +# +# Note 3: In the case of 'ltz' we violate the RFC by using a three char +# specifier. There is 'work in progress' to fix this and get +# the reference data for rfc1766 cleaned up. +# +# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl) +# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) +# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) +# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) +# Norwegian (no) - Polish (pl) - Portugese (pt) +# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) +# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) +# +AddLanguage ca .ca +AddLanguage cs .cz .cs +AddLanguage da .dk +AddLanguage de .de +AddLanguage el .el +AddLanguage en .en +AddLanguage eo .eo +AddLanguage es .es +AddLanguage et .et +AddLanguage fr .fr +AddLanguage he .he +AddLanguage hr .hr +AddLanguage it .it +AddLanguage ja .ja +AddLanguage ko .ko +AddLanguage ltz .ltz +AddLanguage nl .nl +AddLanguage nn .nn +AddLanguage no .no +AddLanguage pl .po +AddLanguage pt .pt +AddLanguage pt-BR .pt-br +AddLanguage ru .ru +AddLanguage sv .sv +AddLanguage zh-CN .zh-cn +AddLanguage zh-TW .zh-tw + +# +# LanguagePriority allows you to give precedence to some languages +# in case of a tie during content negotiation. +# +# Just list the languages in decreasing order of preference. We have +# more or less alphabetized them here. You probably want to change this. +# +LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW + +# +# ForceLanguagePriority allows you to serve a result page rather than +# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) +# [in case no accepted languages matched the available variants] +# +ForceLanguagePriority Prefer Fallback + +# +# Specify a default charset for all content served; this enables +# interpretation of all content as UTF-8 by default. To use the +# default browser choice (ISO-8859-1), or to allow the META tags +# in HTML content to override this choice, comment out this +# directive: +# +AddDefaultCharset UTF-8 + +# +# AddType allows you to add to or override the MIME configuration +# file mime.types for specific file types. +# +#AddType application/x-tar .tgz + +# +# AddEncoding allows you to have certain browsers uncompress +# information on the fly. Note: Not all browsers support this. +# Despite the name similarity, the following Add* directives have nothing +# to do with the FancyIndexing customization directives above. +# +#AddEncoding x-compress .Z +#AddEncoding x-gzip .gz .tgz .svgz + +# If the AddEncoding directives above are commented-out, then you +# probably should define those extensions to indicate media types: +# +AddType application/x-compress .Z +AddType application/x-gzip .gz .tgz + +# +# MIME-types for downloading Certificates and CRLs +# +AddType application/x-x509-ca-cert .crt +AddType application/x-pkcs7-crl .crl + +# +# AddHandler allows you to map certain file extensions to "handlers": +# actions unrelated to filetype. These can be either built into the server +# or added with the Action directive (see below) +# +# To use CGI scripts outside of ScriptAliased directories: +# (You will also need to add "ExecCGI" to the "Options" directive.) +# +#AddHandler cgi-script .cgi + +# +# For files that include their own HTTP headers: +# +#AddHandler send-as-is asis + +# +# For type maps (negotiated resources): +# (This is enabled by default to allow the Apache "It Worked" page +# to be distributed in multiple languages.) +# +AddHandler type-map var + +# +# Filters allow you to process content before it is sent to the client. +# +# To parse .shtml files for server-side includes (SSI): +# (You will also need to add "Includes" to the "Options" directive.) +# +AddType text/html .shtml +AddOutputFilter INCLUDES .shtml + +# +# Action lets you define media types that will execute a script whenever +# a matching file is called. This eliminates the need for repeated URL +# pathnames for oft-used CGI file processors. +# Format: Action media/type /cgi-script/location +# Format: Action handler-name /cgi-script/location +# + +# +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html +# + +# +# Putting this all together, we can internationalize error responses. +# +# We use Alias to redirect any /error/HTTP_<error>.html.var response to +# our collection of by-error message multi-language collections. We use +# includes to substitute the appropriate text. +# +# You can modify the messages' appearance without changing any of the +# default HTTP_<error>.html.var files by adding the line: +# +# Alias /error/include/ "/your/include/path/" +# +# which allows you to create your own set of files by starting with the +# /var/www/error/include/ files and +# copying them to /your/include/path/, even on a per-VirtualHost basis. +# + +Alias /error/ "/var/www/error/" + +<IfModule mod_negotiation.c> +<IfModule mod_include.c> + <Directory "/var/www/error"> + AllowOverride None + Options IncludesNoExec + AddOutputFilter Includes html + AddHandler type-map var + Order allow,deny + Allow from all + LanguagePriority en es de fr + ForceLanguagePriority Prefer Fallback + </Directory> + +# ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var +# ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var +# ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var +# ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var +# ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var +# ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var +# ErrorDocument 410 /error/HTTP_GONE.html.var +# ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var +# ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var +# ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var +# ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var +# ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var +# ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var +# ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var +# ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var +# ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var +# ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var + +</IfModule> +</IfModule> + +# +# The following directives modify normal HTTP response behavior to +# handle known problems with browser implementations. +# +BrowserMatch "Mozilla/2" nokeepalive +BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 +BrowserMatch "RealPlayer 4\.0" force-response-1.0 +BrowserMatch "Java/1\.0" force-response-1.0 +BrowserMatch "JDK/1\.0" force-response-1.0 + +# +# The following directive disables redirects on non-GET requests for +# a directory that does not include the trailing slash. This fixes a +# problem with Microsoft WebFolders which does not appropriately handle +# redirects for folders with DAV methods. +# Same deal with Apple's DAV filesystem and Gnome VFS support for DAV. +# +BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully +BrowserMatch "MS FrontPage" redirect-carefully +BrowserMatch "^WebDrive" redirect-carefully +BrowserMatch "^WebDAVFS/1.[0123]" redirect-carefully +BrowserMatch "^gnome-vfs/1.0" redirect-carefully +BrowserMatch "^XML Spy" redirect-carefully +BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully + +# +# Allow server status reports generated by mod_status, +# with the URL of http://servername/server-status +# Change the ".example.com" to match your domain to enable. +# +#<Location /server-status> +# SetHandler server-status +# Order deny,allow +# Deny from all +# Allow from .example.com +#</Location> + +# +# Allow remote server configuration reports, with the URL of +# http://servername/server-info (requires that mod_info.c be loaded). +# Change the ".example.com" to match your domain to enable. +# +#<Location /server-info> +# SetHandler server-info +# Order deny,allow +# Deny from all +# Allow from .example.com +#</Location> + +# +# Proxy Server directives. Uncomment the following lines to +# enable the proxy server: +# +#<IfModule mod_proxy.c> +#ProxyRequests On +# +#<Proxy *> +# Order deny,allow +# Deny from all +# Allow from .example.com +#</Proxy> + +# +# Enable/disable the handling of HTTP/1.1 "Via:" headers. +# ("Full" adds the server version; "Block" removes all outgoing Via: headers) +# Set to one of: Off | On | Full | Block +# +#ProxyVia On + +# +# To enable a cache of proxied content, uncomment the following lines. +# See http://httpd.apache.org/docs/2.2/mod/mod_cache.html for more details. +# +#<IfModule mod_disk_cache.c> +# CacheEnable disk / +# CacheRoot "/var/cache/mod_proxy" +#</IfModule> +# + +#</IfModule> +# End of proxy directives. + +### Section 3: Virtual Hosts +# +# VirtualHost: If you want to maintain multiple domains/hostnames on your +# machine you can setup VirtualHost containers for them. Most configurations +# use only name-based virtual hosts so the server doesn't need to worry about +# IP addresses. This is indicated by the asterisks in the directives below. +# +# Please see the documentation at +# <URL:http://httpd.apache.org/docs/2.2/vhosts/> +# for further details before you try to setup virtual hosts. +# +# You may use the command line option '-S' to verify your virtual host +# configuration. + +# +# Use name-based virtual hosting. +# +#NameVirtualHost *:80 +# +# NOTE: NameVirtualHost cannot be used without a port specifier +# (e.g. :80) if mod_ssl is being used, due to the nature of the +# SSL protocol. +# + +# +# VirtualHost example: +# Almost any Apache directive may go into a VirtualHost container. +# The first VirtualHost section is used for requests without a known +# server name. +# +#<VirtualHost *:80> +# ServerAdmin webmaster@dummy-host.example.com +# DocumentRoot /www/docs/dummy-host.example.com +# ServerName dummy-host.example.com +# ErrorLog logs/dummy-host.example.com-error_log +# CustomLog logs/dummy-host.example.com-access_log common +#</VirtualHost> + +Include conf/test.conf diff --git a/test/setup.sh b/test/setup.sh new file mode 100755 index 0000000..693d603 --- /dev/null +++ b/test/setup.sh @@ -0,0 +1,55 @@ +#!/bin/sh +currentpath=`pwd` +server_uid=$USER +server_gid=$USER +server_port=8000 +server_name=`hostname` + +test_root=$currentpath/work/httpd +test_root_esc=`echo ${test_root} | sed -e 's/\\//\\\\\\//g'` + +if [ -e $test_root ]; then + if [ $# -gt 0 -a "$1X" = "forceX" ]; then + rm -rf work + else + echo "Test directory already exists" + exit 1 + fi +fi + +./createinstance.sh ${test_root} + +cp ../.libs/libmodnss.so ${test_root}/lib + +../gencert ${test_root}/alias +echo internal:httptest > ${test_root}/conf/password.conf + +# Export the CA cert +certutil -L -d ${test_root}/alias -n cacert -a > ${test_root}/alias/ca.pem + +# Export the client cert +cd ${test_root} +echo password > pw +echo httptest > dbpw +pk12util -o alpha.p12 -d alias -n alpha -w pw -k dbpw +openssl pkcs12 -in alpha.p12 -clcerts -nokeys -out alpha.crt -passin pass:`cat pw` +openssl pkcs12 -in alpha.p12 -nocerts -nodes -out alpha.key -passin pass:`cat pw` +pk12util -o beta.p12 -d alias -n beta -w pw -k dbpw +openssl pkcs12 -in beta.p12 -clcerts -nokeys -out beta.crt -passin pass:`cat pw` +openssl pkcs12 -in beta.p12 -nocerts -nodes -out beta.key -passin pass:`cat pw` +/bin/rm -f pw dbpw +cd - + +if [ -f ${test_root}/sedfile ] +then + rm ${test_root}/sedfile +fi + +echo "s/::TEST_ROOT::/${test_root_esc}/g" >> ${test_root}/sedfile +echo "s/::SERVER_ROOT::/${test_root_esc}/g" >> ${test_root}/sedfile +echo "s/::SERVER_PORT::/${server_port}/g" >> ${test_root}/sedfile +echo "s/::SERVER_NAME::/${server_name}/g" >> ${test_root}/sedfile +echo "s/::SERVER_UID::/${server_uid}/g" >> ${test_root}/sedfile +echo "s/::SERVER_GID::/${server_gid}/g" >> ${test_root}/sedfile + +cat httpd.conf.tmpl | sed -f ${test_root}/sedfile > ${test_root}/conf/httpd.conf diff --git a/test/suite1.tmpl b/test/suite1.tmpl new file mode 100644 index 0000000..999c4d7 --- /dev/null +++ b/test/suite1.tmpl @@ -0,0 +1,65 @@ +<VirtualHost *:$SERVER_PORT> + +NSSEngine on +NSSFIPS off +NSSOCSP off +NSSRenegotiation on + +NSSCipherSuite +rc4,+rc4export,+rc2,+rc2export,+des,+desede3,-fortezza,-fortezza_rc4_128_sha,-fortezza_null,+rsa_rc4_128_md5,+rsa_3des_sha,+rsa_des_sha,+rsa_rc4_40_md5,+rsa_rc2_40_md5,+rsa_null_md5,+rsa_des_56_sha,+rsa_rc4_56_sha,+rsa_aes_128_sha,+rsa_aes_256_sha,+fips_des_sha,+fips_3des_sha + +NSSProtocol SSLv3,TLSv1.0 + +NSSNickname Server-Cert + +NSSCertificateDatabase $SERVER_ROOT/alias + +NSSVerifyClient none + +NSSUserName SSL_CLIENT_S_DN_UID + +<Location "/rc4_cipher"> + NSSCipherSuite -rc4,-rc4export,-rc2,-rc2export,-des,-desede3,-fortezza,-fortezza_rc4_128_sha,-fortezza_null,+rsa_rc4_128_md5,-rsa_3des_sha,-rsa_des_sha,-rsa_rc4_40_md5,-rsa_rc2_40_md5,-rsa_null_md5,-rsa_des_56_sha,-rsa_rc4_56_sha,-rsa_aes_128_sha,-rsa_aes_256_sha,-fips_des_sha,-fips_3des_sha,-ecdhe_rsa_rc4_128_sha,-ecdhe_rsa_aes_128_sha,-rsa_rc4_128_sha +</Location> + +<Location "/acl/aclS01.html"> + NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData + NSSVerifyClient require +</Location> + +<Location "/acl/aclS02.html"> + NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData + NSSVerifyClient require + NSSRequire ( %{SSL_CLIENT_S_DN_UID} eq "alpha" \ + or %{SSL_CLIENT_S_DN_UID} eq "gamma" ) \ + and %{SSL_CLIENT_S_DN_O} eq "example.com" \ + and %{SSL_CLIENT_S_DN_OU} eq "People" +</Location> + +<Location "/acl/aclS03.html"> + NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData +FakeBasicAuth + NSSVerifyClient require + AuthType Basic + AuthName Cert + AuthUserFile conf/htpasswd + Require valid-user +</Location> + +<Location "/secret-test.html"> + NSSRequire %{SSL_CIPHER_USEKEYSIZE} > 40 +</Location> + +<Location "/secret-test-impossible.html"> + NSSRequire %{SSL_CIPHER_USEKEYSIZE} > 4000 +</Location> +</VirtualHost> + +# SSL configuration +NSSPassPhraseDialog file:$SERVER_ROOT/conf/password.conf + +NSSPassPhraseHelper /usr/sbin/nss_pcache + +NSSSessionCacheSize 10000 +NSSSessionCacheTimeout 100 +NSSSession3CacheTimeout 86400 + +Listen 0.0.0.0:$SERVER_PORT diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..e7136e6 --- /dev/null +++ b/test/test.py @@ -0,0 +1,138 @@ +from test_config import Declarative, write_template_file, restart_apache +from test_config import stop_apache +import requests.exceptions + +class test_suite1(Declarative): + @classmethod + def setUpClass(cls): + write_template_file('suite1.tmpl', 'work/httpd/conf/test.conf', {}) + restart_apache() + + @classmethod + def tearDownClass(cls): + stop_apache() + + tests = [ + + dict( + desc='Basic SSL connection', + request=('/', {}), + expected=200, + ), + + dict( + desc='Basic SSL connection, 404', + request=('/notfound', {}), + expected=404, + ), + + dict( + desc='SSL connection, fail to verify', + request=('/', {'verify': True}), + expected=requests.exceptions.SSLError(), + ), + + dict( + desc='SSL AES128-SHA cipher check', + request=('/index.html', {}), + expected=200, + cipher='AES128-SHA', + ), + + dict( + desc='Default protocol check', + request=('/', {}), + expected=200, + protocol='TLSv1/SSLv3', + ), + + dict( + desc='server-side RC4 cipher check', + request=('/rc4_cipher/', {'ciphers': 'ALL'}), + expected=200, + cipher='RC4-MD5', + ), + + dict( + desc='client-side RC4 cipher check', + request=('/', {'ciphers': 'RC4-MD5'}), + expected=200, + cipher='RC4-MD5', + ), + + dict( + desc='Basic client auth, no certificate', + request=('/acl/aclS01.html', {}), + expected=requests.exceptions.SSLError(), + ), + + dict( + desc='Basic client auth, valid certificate', + request=('/acl/aclS01.html', { + 'key_file': 'work/httpd/alpha.key', + 'cert_file': 'work/httpd/alpha.crt',} + ), + expected=200, + ), + + dict( + desc='NSSRequire auth, no certificate', + request=('/acl/aclS02.html', {}), + expected=requests.exceptions.SSLError(), + ), + + dict( + desc='NSSRequire auth, valid certificate', + request=('/acl/aclS02.html', { + 'key_file': 'work/httpd/alpha.key', + 'cert_file': 'work/httpd/alpha.crt',} + ), + expected=200, + ), + + dict( + desc='NSSRequire auth, not allowed certificate', + request=('/acl/aclS02.html', { + 'key_file': 'work/httpd/beta.key', + 'cert_file': 'work/httpd/beta.crt',} + ), + expected=403, + ), + + dict( + desc='FakeBasicAuth, no certificate', + request=('/acl/aclS03.html', {}), + expected=requests.exceptions.SSLError(), + ), + + dict( + desc='FakeBasicAuth, valid certificate', + request=('/acl/aclS03.html', { + 'key_file': 'work/httpd/alpha.key', + 'cert_file': 'work/httpd/alpha.crt',} + ), + expected=200, + ), + + dict( + desc='FakeBasicAuth, not allowed user', + request=('/acl/aclS03.html', { + 'key_file': 'work/httpd/beta.key', + 'cert_file': 'work/httpd/beta.crt',} + ), + expected=401, + ), + + dict( + desc='Secret key size', + request=('/secret-test.html', {}), + expected=200, + ), + + dict( + desc='Impossible secret key size', + request=('/secret-test-impossible.html', {}), + expected=403, + ), + + ] diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..9990a92 --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,186 @@ +# Copyright (C) 2013 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import re +import ssl +import time +import string +import requests +import socket +import subprocess +import test_util +import test_request + +# Utility functions to assist in creating Apache configuration based +# on test suite + +PORT=8000 +FQDN = socket.gethostname() + +default_vars = dict( + SERVER_PORT = PORT, + SERVER_NAME = FQDN, + TEST_ROOT = '%s/work/httpd' % os.getcwd(), + SERVER_ROOT = '%s/work/httpd' % os.getcwd(), +) + +def template_str(txt, vars): + val = string.Template(txt).substitute(vars) + + # eval() is a special string one can insert into a template to have the + # Python interpreter evaluate the string. This is intended to allow + # math to be performed in templates. + pattern = re.compile('(eval\s*\(([^()]*)\))') + val = pattern.sub(lambda x: str(eval(x.group(2))), val) + + return val + +def template_file(infilename, vars): + """Read a file and perform template substitutions""" + with open(infilename) as f: + return template_str(f.read(), vars) + +def write_template_file(infilename, outfilename, vars): + """Read a file and perform template substitutions""" + replacevars = dict(default_vars.items() + vars.items()) + with open(outfilename, 'w') as f: + f.write('%s\n' % template_file(infilename, replacevars)) + +def stop_apache(): + """Stop the Apache process""" + cwd = os.getcwd() + # no try/except, just let it fail + os.chdir('work/httpd') + + p = subprocess.Popen(['./stop'], + close_fds=True) + +def restart_apache(): + """Restart the Apache process""" + cwd = os.getcwd() + # no try/except, just let it fail + os.chdir('work/httpd') + + p = subprocess.Popen(['./stop'], + close_fds=True) + time.sleep(5) + p = subprocess.Popen(['./start'], + close_fds=True) + os.chdir(cwd) + test_util.wait_for_open_ports(FQDN, PORT) + +EXPECTED = """Expected %r to raise %s. + options = %r + output = %r""" + +UNEXPECTED = """Expected %r to raise %s, but caught different. + options = %r + %s: %s""" + +class Declarative(object): + """A declarative-style test suite + + A Declarative test suite is controlled by the ``tests`` + class variable. + + The ``tests`` is a list of dictionaries with the following keys: + + ``desc`` + A name/description of the test + ``request`` + A (uri, options) triple specifying the uri to run + ``expected`` + Can be either an ``errors.PublicError`` instance, in which case + the command must fail with the given error; or the + expected result. + The result is checked with ``tests.util.assert_deepequal``. + """ + + tests = tuple() + + def test_generator(self): + """ + Iterate through tests. + + nose reports each one as a separate test. + """ + + # Iterate through the tests: + name = self.__class__.__name__ + for (i, test) in enumerate(self.tests): + nice = '%s[%d]: %s: %s' % ( + name, i, test['request'][0], test.get('desc', '') + ) + func = lambda: self.check(nice, **test) + func.description = nice + yield (func,) + + def make_request(self, uri, options): + session = requests.Session() + session.mount('https://', test_request.MyAdapter()) + verify = dict(verify = options) + request = session.get('https://%s:%d%s' % (FQDN, PORT, uri), **verify) + + return request + + def check(self, nice, desc, request, expected, cipher=None, protocol=None): + # TODO: need way to set auth, etc. + (uri, options) = request + if not 'verify' in options: + options['verify'] = 'work/httpd/alias/ca.pem' + if isinstance(expected, Exception): + self.check_exception(nice, uri, options, expected) + else: + self.check_result(nice, uri, options, expected, cipher, protocol) + + def check_exception(self, nice, uri, options, expected): + klass = expected.__class__ + name = klass.__name__ + try: + output = self.make_request(uri, options) + except StandardError, e: + pass + else: + raise AssertionError( + EXPECTED % (uri, name, options, output) + ) + if not isinstance(e, klass): + raise AssertionError( + UNEXPECTED % (uri, name, options, e.__class__.__name__, e) + ) + + + def check_result(self, nice, uri, options, expected, cipher=None, protocol=None): + name = expected.__class__.__name__ + request = self.make_request(uri, options) + if cipher: + client_cipher = request.raw._pool._get_conn().client_cipher + if cipher != client_cipher[0]: + raise AssertionError( + 'Expected cipher %s, got %s' % (cipher, client_cipher[0]) + ) + if protocol: + client_cipher = request.raw._pool._get_conn().client_cipher + if protocol != client_cipher[1]: + raise AssertionError( + 'Expected cipher %s, got %s' % (cipher, client_cipher[1]) + ) + if expected != request.status_code: + raise AssertionError( + 'Expected status %s, got %s' % (expected, request.status_code) + ) diff --git a/test/test_request.py b/test/test_request.py new file mode 100644 index 0000000..40d8024 --- /dev/null +++ b/test/test_request.py @@ -0,0 +1,190 @@ +# +# Override a slew of methods to have more control over SSL + +import socket +import requests +import urlparse +from urllib3.util import get_host +from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool +import logging + +# Don't bend over backwards for ssl support, assume it is there. +import ssl +try: # Python 3 + from http.client import HTTPConnection, HTTPException + from http.client import HTTP_PORT, HTTPS_PORT + from http.client import HTTPSConnection +except ImportError: + from httplib import HTTPConnection, HTTPException + from httplib import HTTP_PORT, HTTPS_PORT + from httplib import HTTPSConnection + +try: + # python3.2+ + from ssl import match_hostname, CertificateError +except ImportError: + try: + # Older python where the backport from pypi is installed + from backports.ssl_match_hostname import match_hostname, CertificateError + except ImportError: + # Other older python we use the urllib3 bundled copy + from urllib3.packages.ssl_match_hostname import match_hostname, CertificateError + +log = logging.getLogger(__name__) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example: :: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + if scheme == 'https': + return MyHTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + +class MyHTTPSConnectionPool(HTTPSConnectionPool): + def __init__(self, host, port=None, + strict=False, timeout=None, maxsize=1, + block=False, headers=None, + key_file=None, cert_file=None, + cert_reqs='CERT_REQUIRED', ca_certs='/etc/ssl/certs/ca-certificates.crt', ssl_version=ssl.PROTOCOL_SSLv23, ciphers=None): + + super(HTTPSConnectionPool, self).__init__(host, port, + strict, timeout, maxsize, + block, headers) + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.ca_certs = ca_certs + self.ssl_version = ssl_version + self.ciphers = ciphers + + def _new_conn(self): + """ + Return a fresh :class:`httplib.HTTPSConnection`. + """ + self.num_connections += 1 + log.info("Starting new HTTPS connection (%d): %s" + % (self.num_connections, self.host)) + + #if not ssl: # Platform-specific: Python compiled without +ssl + # if not HTTPSConnection or HTTPSConnection is object: + # raise SSLError("Can't connect to HTTPS URL because the SSL " + # "module is not available.") + + # return HTTPSConnection(host=self.host, port=self.port) + + connection = MyVerifiedHTTPSConnection(host=self.host, port=self.port) + connection.set_cert(key_file=self.key_file, cert_file=self.cert_file, + cert_reqs=self.cert_reqs, ca_certs=self.ca_certs) + connection.set_ssl_version(self.ssl_version) + connection.set_ciphers(self.ciphers) + return connection + +class MyVerifiedHTTPSConnection(HTTPSConnection): + """ + Based on httplib.HTTPSConnection but wraps the socket with + SSL certification. + """ + cert_reqs = None + ca_certs = None + client_cipher = None + + def set_cert(self, key_file=None, cert_file=None, + cert_reqs='CERT_NONE', ca_certs=None): + ssl_req_scheme = { + 'CERT_NONE': ssl.CERT_NONE, + 'CERT_OPTIONAL': ssl.CERT_OPTIONAL, + 'CERT_REQUIRED': ssl.CERT_REQUIRED + } + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = ssl_req_scheme.get(cert_reqs) or ssl.CERT_NONE + self.ca_certs = ca_certs + + def set_ssl_version(self, ssl_version=ssl.PROTOCOL_SSLv23): + self.ssl_version = ssl_version + + def set_ciphers(self, ciphers=None): + self.ciphers = ciphers + + def connect(self): + # Add certificate verification + sock = socket.create_connection((self.host, self.port), self.timeout) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ssl_version=self.ssl_version, + ciphers=self.ciphers) + if self.ca_certs: + match_hostname(self.sock.getpeercert(), self.host) + + def close(self): + self.client_cipher = self.sock.cipher() + HTTPSConnection.close(self) + +class MyAdapter(requests.adapters.HTTPAdapter): + + def get_connection(self, url, proxies=None): + """Returns a connection for the given URL.""" + + # proxies are not supported + return connection_from_url(url) + + def cert_verify(self, conn, url, verify, cert): + # I'm overloading the content of verify since this API is so + # braindead. If verify is a dict then key 'verify' represents the + # original meaning, the other keys are my own. + if isinstance(verify, bool): + super(MyAdapter, self).cert_verify(conn, url, verify, cert) + elif isinstance(verify, dict): + if 'verify' in verify: + super(MyAdapter, self).cert_verify(conn, url, + verify['verify'], cert) + if 'ssl_version' in verify: + conn.ssl_version = verify['ssl_version'] + if 'ciphers' in verify: + conn.ciphers = verify['ciphers'] + if 'cert_file' in verify: + conn.cert_file = verify['cert_file'] + if 'key_file' in verify: + conn.key_file = verify['key_file'] + else: # huh? Do nothing + pass + +""" +s = requests.Session() +s.mount('https://', MyAdapter()) +try: + r = s.get('https://darlene.greyoak.com:8000/', verify={'verify': False, 'ssl_version': ssl.PROTOCOL_SSLv23, 'ciphers': 'HIGH'}) + cipher = r.raw._pool._get_conn().client_cipher +except requests.exceptions.SSLError, e: + print e.message +else: + print r.status_code + print cipher + +#request = requests.get('https://darlene.greyoak.com:8000/', verify=False) +#print request.status_code +""" diff --git a/test/test_util.py b/test/test_util.py new file mode 100644 index 0000000..f01d43a --- /dev/null +++ b/test/test_util.py @@ -0,0 +1,52 @@ +import socket +import time + +def host_port_open(host, port, socket_type=socket.SOCK_STREAM, socket_timeout=None): + for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket_type): + af, socktype, proto, canonname, sa = res + try: + try: + s = socket.socket(af, socktype, proto) + except socket.error: + s = None + continue + + if socket_timeout is not None: + s.settimeout(socket_timeout) + + s.connect(sa) + + if socket_type == socket.SOCK_DGRAM: + s.send('') + s.recv(512) + + return True + except socket.error, e: + pass + finally: + if s: + s.close() + + return False + +def wait_for_open_ports(host, ports, timeout=0): + """ + Wait until the specified port(s) on the remote host are open. Timeout + in seconds may be specified to limit the wait. If the timeout is + exceeded, socket.timeout exception is raised. + """ + if not isinstance(ports, (tuple, list)): + ports = [ports] + + op_timeout = time.time() + timeout + + for port in ports: + while True: + port_open = host_port_open(host, port) + + if port_open: + break + if timeout and time.time() > op_timeout: # timeout exceeded + raise socket.timeout() + time.sleep(1) + |
