Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • Pavel.Valach/warden
1 result
Select Git revision
Show changes
Showing
with 1938 additions and 0 deletions
#!/bin/bash
#
### BEGIN INIT INFO
# Provides: warden_filer_sender
# Required-Start: $local_fs $syslog
# Required-Stop: $local_fs $syslog
# Should-Start: $network $named
# Should-Stop: $network $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Warden Filer - sender
### END INIT INFO
DAEMON_NAME=warden_filer
FUNC=sender
DAEMON_PATH=/usr/local/bin/"$DAEMON_NAME".py
SERVICE_NAME="${DAEMON_NAME}_${FUNC}"
PID=/var/run/"$DAEMON_NAME"/"$FUNC".pid
CONFIG=/etc/"$DAEMON_NAME".cfg
# Try Debian & Fedora/RHEL/Suse sysconfig
for n in default sysconfig; do
[ -f /etc/$n/"$SERVICE_NAME" ] && . /etc/$n/"$SERVICE_NAME"
done
# Fallback
function log_daemon_msg () { echo -n "$@"; }
function log_end_msg () { [ $1 -eq 0 ] && echo " OK" || echo " Failed"; }
function status_of_proc () { [ -f "$PID" ] && ps u -p $(<"$PID") || echo "$PID not found."; }
function start_daemon () { shift; shift; $* ; }
function killproc () { kill $(cat $PID) ; }
[ -f /lib/lsb/init-functions ] && . /lib/lsb/init-functions
ACTION="$1"
case "$ACTION" in
start)
mkdir -p "${PID%/*}"
log_daemon_msg "Starting $SERVICE_NAME" "$SERVICE_NAME"
start_daemon -p "$PID" "$DAEMON_PATH" -c "$CONFIG" --pid_file "$PID" --daemon "$FUNC"
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping $SERVICE_NAME" "$SERVICE_NAME"
killproc -p "$PID" "$DAEMON_PATH"
log_end_msg $?
;;
restart|force-reload)
$0 stop && sleep 2 && exec $0 start
;;
status)
status_of_proc -p "$PID" "$DAEMON_PATH" "$SERVICE_NAME"
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 2
;;
esac
Copyright (c) 2011-2016 Cesnet z.s.p.o <warden-info@cesnet.cz>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file
Warden Registration Authority for Warden 3.X
============================================
Introduction
------------
Warden RA is a certificate registration authority for Warden_ server.
It is meant to support the client registration process and simplification
of the credential transport.
As Warden clients are authenticated by X509 certificate, the usual certificate
generation process can be used - local key and certificate request gets
generated, the request is submitted to registration authority, and after
review, certificate is issued and delivered back.
However in centralised Warden setup, it is useful to be able to preallow
certificate for particular client during negotiation phase (thus removing
another round-trip).
This is done by issuing 'token' by Warden registration officer during client
registration, which is just a oneshot password, allowing sending the request
and getting new certificate in one step through web API.
Password is meant only for completely new clients or unusual situations,
however RA also allows prolongation - generating new certificate by using
old certificate (which must be still valid, of course) instead of password.
The application script, which can be distributed to newly registered clients,
is also included.
Dependencies
------------
1. Platform
Python 2.7+
Apache 2.2+
mod_wsgi 3.3+
Registration process
--------------------
New client credentials
``````````````````````
After succesful negotiation of new Warden client parameters, the registration
officer enables new certificate generation by issuing (on the server side):
warden_ra.py applicant --name org.example.warden.client
The tool generates and sets one time password on the registration authority
side, and this password can be sent (preferably through the secured channel)
to the new client administrator along with other setup information.
The client administrator runs the application script with application
password:
warden_apply.sh https://warden.example.org/warden-ra/ org.example.warden.client P4SSW0RD
The script creates new X509 key, CSR certificate request and makes call to
the Warden RA web service, where it obtains the new complete certificate.
Prolonging existing client credentials
``````````````````````````````````````
The client administrator runs the application script with his existing valid
Warden credentials, which he needs to prolong:
warden_apply.sh https://warden.example.org/warden-ra/ org.example.warden.client cert.pem key.pem
The script creates new X509 key, CSR certificate request and makes call to
the Warden RA web service, where it obtains the new complete certificate.
Installation
------------
This depends heavily on your distribution and Apache configuration.
Basically you need to create and include apache.conf:
Include /opt/warden_ra/apache22.conf
or paste the contents into whichever Directory, Location or VirtualHost
you dedicate for Warden RA. Note that you have to use different host than
the one for Warden server, as Warden RA needs different Apache options
for SSL authentication.
You may need to change paths to certificate/key/ca material, path to
warden_server.wsgi and web path alias.
Note that warden_ra itself is NOT thread safe, so included configuration
expects Apache with mpm-prefork worker, or you will have to configure
mod_wsgi as separate process with threads=1.
Also, for warden_server.wsgi, you can use warden_server.wsgi.dist as
a template. You will possibly need to change at least configuration
file path.
* Now install and/or configure RA backend (see README.openssl or README.ejbca)
* Configure Warden RA (see next chapter)
* Reload Apache
Configuration
-------------
Configuration is JSON object in file (warden_server.cfg by default),
however, lines starting with "#" or "//" are allowed and will be ignored as
comments. File must contain valid JSON object, containing configuration. See
also warden_server.cfg.dist as example.
Top level JSON object contains subsections, which configure particular
aspect of the server. Each subsection can contain "type" key, which chooses
particular implementation object of the aspect, for example type of logger
(file, syslog), such as:
{
"Log": {
"type": "SysLogger",
...
},
"DB": { ... }
}
Sections and their "type" objects can be:
Log: FileLogger, SysLogger
Auth: OptionalAuthenticator
Registry: EjbcaRegistry, OpenSSLRegistry
Handler: CertHandler
"type" keyword is not mandatory, if not specified, first implementation
object from particular section list is used ("FileLogger" for example).
Object function and configuration keys are as follows:
FileLogger: logging into file on the filesystem
filename: name of the file, defaults to "warden_ra.log" at
installation directory
level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG)
SysLogger: logging into unix syslog
socket: path to syslog socket, defaults to "/dev/log"
facility: syslog facility, defaults to "daemon"
level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG)
OptionalAuthenticator: authenticate based on X509 certificate, or
signal the password auth for the registry
CertHandler: the main certificate requestor implementation
For OpenSSLRegistry or EJBCARegistry configuration please see
README.openssl or README.ejbca respectively.
Command line
------------
When run from the command line, RA allows for client and request management.
warden_ra.py [--help] [-c CONFIG] [-v]
{list,register,applicant,request,gencert} ...
Warden server certificate registry
arguments:
--help show this help message and exit
-c CONFIG, --config CONFIG
path to configuration file
-v, --verbose be more chatty
commands:
{list,register,applicant,request,gencert}
list list clients
register register client
applicant allow for certificate application
request generate CSR
gencert get new certificate
warden_ra.py list [--help] [--name NAME]
List registered clients.
arguments:
--help show this help message and exit
--name NAME client name
warden_ra.py register [--help] --name NAME --admins
[ADMINS [ADMINS ...]]
Add client registration entry.
arguments:
--help show this help message and exit
--name NAME client name
--admins [ADMINS [ADMINS ...]]
administrator list
warden_ra.py applicant [--help] --name NAME [--password PASSWORD]
Set client into certificate application mode and set its password
optional arguments:
--help show this help message and exit
--name NAME client name
--password PASSWORD password for application (will be autogenerated if not
set)
.. _Warden: https://warden.cesnet.cz/
------------------------------------------------------------------------------
Copyright (C) 2017 Cesnet z.s.p.o
EJBCA backend for Warden 3.# Registration Authority
===================================================
Introduction
------------
EJBCA_ is an open source CA management software. To use this backend
with Warden RA, you need to have it already installed and running.
Tested with EJBCA_ 3.9.
.. _EJBCA: https://www.ejbca.org/
Configuration
-------------
Options for "Registry: EjbcaRegistry" section.
url: EJBCA API URL, for example "https://ejbca.example.org/ejbca/ejbcaws/ejbcaws?wsdl"
cert: certificate for authentication to EJBCA, defaults to "warden_ra.cert.pem"
key: key for authentication to EJBCA, defaults to "warden_ra.key.pem"
ca_name: name of the CA, dedicated for Warden, defaults to "Example CA"
certificate_profile_name: name of the EJBCA certificate profile, defaults to "Example"
end_entity_profile_name: name of the EJBCA entity profile, defaults to "Example EE"
subject_dn_template: template for the DN generation, defaults to "DC=cz,DC=example-ca,DC=warden,CN=%s"
username_suffix: suffix, which will be added to EJBCA entities, defaults to "@warden"
------------------------------------------------------------------------------
Copyright (C) 2017 Cesnet z.s.p.o
OpenSSL local backed for Warden 3.# Registration Authority
==========================================================
Introduction
------------
This backend allows using basic `openssl ca`_ facility for certificate
emission. Client information is kept as plain config files within "clients"
subdirectory. Also, received CSRs and issued certificates are saved in "csr"
and "newcerts" subdirectories, respectively. File "lock" is used to conduct
concurrent access to running openssl binary.
.. _openssl ca: https://www.openssl.org/docs/manmaster/man1/openssl-ca.html
Installation
------------
Choose directory where OpenSSL CA structure will reside (for example
"ca").
# mkdir ca
# cd ca/
/ca# mkdir certs crl newcerts private clients csr
/ca# chmod 700 private
/ca# touch index.txt
/ca# echo 1024 > serial
Adjust permissions.
# s-bit, so newly created files receive permissions of parent
# directory, not of creator
ca# find . -type d | xargs chmod g+s
# owner - apache group (this is for Debian, adjust accordingly for
# different distribution)
ca# chgrp -R www-data .
Generate CA root certificate.
ca# openssl genrsa -out private/ca.key.pem 4096
ca# openssl req -config openssl.cnf \
-key private/ca.key.pem \
-new -x509 -days 7300 -sha256 -extensions v3_ca \
-out certs/ca.cert.pem
ca# chmod 444 private/ca.key.pem certs/ca.cert.pem
Create "openssl.cnf" in base directory. You can use "openssl.cnf.example" as
a basis.
Configuration
-------------
Options for "Registry: OpenSSLRegistry" section.
base_dir: Base directory where OpenSSL CA environment is managed
subject_dn_template: Template for DN of issued certs, defaults to "DC=cz,DC=example-ca,DC=warden,CN=%s"
openssl_sign: OpenSSL command and arguments to run for signing, defaults to "openssl ca -config %(cnf)s -batch -extensions server_cert -days 375 -notext -md sha256 -in %(csr)s -subj '%(dn)s'"
------------------------------------------------------------------------------
Copyright (C) 2017 Cesnet z.s.p.o
SSLEngine on
SSLVerifyClient optional
SSLOptions +StdEnvVars +ExportCertData
SSLCertificateFile /opt/warden_server/cert.pem
SSLCertificateKeyFile /opt/warden_server/key.pem
SSLCACertificateFile /opt/warden_server/chain_TERENA_SSL_CA_3.pem
WSGIScriptAlias /warden_ra /opt/warden-ra/warden_ra.wsgi
<Directory /opt/warden-ra/warden_ra.wsgi>
Order allow,deny
Allow from all
</Directory>
SSLEngine on
SSLVerifyClient optional
SSLOptions +StdEnvVars +ExportCertData
SSLCertificateFile /opt/warden_server/cert.pem
SSLCertificateKeyFile /opt/warden_server/key.pem
SSLCACertificateFile /opt/warden_server/chain_TERENA_SSL_CA_3.pem
WSGIScriptAlias /warden_ra /opt/warden-ra/warden_ra.wsgi
<Directory /opt/warden-ra/warden_ra.wsgi>
Require all granted
</Directory>
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016, CESNET, z. s. p. o.
# Use of this source is governed by an ISC license, see LICENSE file.
import sys
import socket
import base64
import suds.transport.http
import suds.client
import M2Crypto
if sys.version_info[0] >= 3:
import urllib.request, urllib.error, urllib.parse
import http.client
def get_https_handler():
return urllib.request.HTTPSHandler
else:
import urllib2
import httplib
def get_https_handler():
return urllib2.HTTPSHandler
STATUS_FAILED = 11
STATUS_GENERATED = 40
STATUS_HISTORICAL = 60
STATUS_INITIALIZED = 20
STATUS_INPROCESS = 30
STATUS_KEYRECOVERY = 70
STATUS_NEW = 10
STATUS_REVOKED = 50
MATCH_TYPE_BEGINSWITH = 1
MATCH_TYPE_CONTAINS = 2
MATCH_TYPE_EQUALS = 0
MATCH_WITH_CA = 5
MATCH_WITH_CERTIFICATEPROFILE = 4
MATCH_WITH_COMMONNAME = 101
MATCH_WITH_COUNTRY = 112
MATCH_WITH_DIRECTORYNAME = 204
MATCH_WITH_DN = 7
MATCH_WITH_DNSERIALNUMBER = 102
MATCH_WITH_DNSNAME = 201
MATCH_WITH_DOMAINCOMPONENT = 111
MATCH_WITH_EDIPARTNAME = 205
MATCH_WITH_EMAIL = 1
MATCH_WITH_ENDENTITYPROFILE = 3
MATCH_WITH_GIVENNAME = 103
MATCH_WITH_GUID = 209
MATCH_WITH_INITIALS = 104
MATCH_WITH_IPADDRESS = 202
MATCH_WITH_LOCALE = 109
MATCH_WITH_ORGANIZATION = 108
MATCH_WITH_ORGANIZATIONUNIT = 107
MATCH_WITH_REGISTEREDID = 207
MATCH_WITH_RFC822NAME = 200
MATCH_WITH_STATE = 110
MATCH_WITH_STATUS = 2
MATCH_WITH_SURNAME = 105
MATCH_WITH_TITLE = 106
MATCH_WITH_TOKEN = 6
MATCH_WITH_UID = 100
MATCH_WITH_UPN = 208
MATCH_WITH_URI = 206
MATCH_WITH_USERNAME = 0
MATCH_WITH_X400ADDRESS = 203
TOKEN_TYPE_JKS = "JKS"
TOKEN_TYPE_P12 = "P12"
TOKEN_TYPE_PEM = "PEM"
TOKEN_TYPE_USERGENERATED = "USERGENERATED"
VIEW_RIGHTS = "/view_end_entity"
EDIT_RIGHTS = "/edit_end_entity"
CREATE_RIGHTS = "/create_end_entity"
DELETE_RIGHTS = "/delete_end_entity"
REVOKE_RIGHTS = "/revoke_end_entity"
HISTORY_RIGHTS = "/view_end_entity_history"
APPROVAL_RIGHTS = "/approve_end_entity"
HARDTOKEN_RIGHTS = "/view_hardtoken"
HARDTOKEN_PUKDATA_RIGHTS = "/view_hardtoken/puk_data"
KEYRECOVERY_RIGHTS = "/keyrecovery"
ENDENTITYPROFILEBASE = "/endentityprofilesrules"
ENDENTITYPROFILEPREFIX = "/endentityprofilesrules/"
USERDATASOURCEBASE = "/userdatasourcesrules"
USERDATASOURCEPREFIX = "/userdatasourcesrules/"
UDS_FETCH_RIGHTS = "/fetch_userdata"
UDS_REMOVE_RIGHTS = "/remove_userdata"
CABASE = "/ca"
CAPREFIX = "/ca/"
ROLE_PUBLICWEBUSER = "/public_web_user"
ROLE_ADMINISTRATOR = "/administrator"
ROLE_SUPERADMINISTRATOR = "/super_administrator"
REGULAR_CAFUNCTIONALTY = "/ca_functionality"
REGULAR_CABASICFUNCTIONS = "/ca_functionality/basic_functions"
REGULAR_ACTIVATECA = "/ca_functionality/basic_functions/activate_ca"
REGULAR_RENEWCA = "/ca_functionality/renew_ca"
REGULAR_VIEWCERTIFICATE = "/ca_functionality/view_certificate"
REGULAR_APPROVECAACTION = "/ca_functionality/approve_caaction"
REGULAR_CREATECRL = "/ca_functionality/create_crl"
REGULAR_EDITCERTIFICATEPROFILES = "/ca_functionality/edit_certificate_profiles"
REGULAR_CREATECERTIFICATE = "/ca_functionality/create_certificate"
REGULAR_STORECERTIFICATE = "/ca_functionality/store_certificate"
REGULAR_RAFUNCTIONALITY = "/ra_functionality"
REGULAR_EDITENDENTITYPROFILES = "/ra_functionality/edit_end_entity_profiles"
REGULAR_EDITUSERDATASOURCES = "/ra_functionality/edit_user_data_sources"
REGULAR_VIEWENDENTITY = "/ra_functionality/view_end_entity"
REGULAR_CREATEENDENTITY = "/ra_functionality/create_end_entity"
REGULAR_EDITENDENTITY = "/ra_functionality/edit_end_entity"
REGULAR_DELETEENDENTITY = "/ra_functionality/delete_end_entity"
REGULAR_REVOKEENDENTITY = "/ra_functionality/revoke_end_entity"
REGULAR_VIEWENDENTITYHISTORY = "/ra_functionality/view_end_entity_history"
REGULAR_APPROVEENDENTITY = "/ra_functionality/approve_end_entity"
REGULAR_LOGFUNCTIONALITY = "/log_functionality"
REGULAR_VIEWLOG = "/log_functionality/view_log"
REGULAR_LOGCONFIGURATION = "/log_functionality/edit_log_configuration"
REGULAR_LOG_CUSTOM_EVENTS = "/log_functionality/log_custom_events"
REGULAR_SYSTEMFUNCTIONALITY = "/system_functionality"
REGULAR_EDITADMINISTRATORPRIVILEDGES = "/system_functionality/edit_administrator_privileges"
REGULAR_EDITSYSTEMCONFIGURATION = "/system_functionality/edit_systemconfiguration"
REGULAR_VIEWHARDTOKENS = "/ra_functionality/view_hardtoken"
REGULAR_VIEWPUKS = "/ra_functionality/view_hardtoken/puk_data"
REGULAR_KEYRECOVERY = "/ra_functionality/keyrecovery"
HARDTOKEN_HARDTOKENFUNCTIONALITY = "/hardtoken_functionality"
HARDTOKEN_EDITHARDTOKENISSUERS = "/hardtoken_functionality/edit_hardtoken_issuers"
HARDTOKEN_EDITHARDTOKENPROFILES = "/hardtoken_functionality/edit_hardtoken_profiles"
HARDTOKEN_ISSUEHARDTOKENS = "/hardtoken_functionality/issue_hardtokens"
HARDTOKEN_ISSUEHARDTOKENADMINISTRATORS = "/hardtoken_functionality/issue_hardtoken_administrators"
RESPONSETYPE_CERTIFICATE = "CERTIFICATE"
RESPONSETYPE_PKCS7 = "PKCS7"
RESPONSETYPE_PKCS7WITHCHAIN = "PKCS7WITHCHAIN"
NOT_REVOKED = -1
REVOKATION_REASON_UNSPECIFIED = 0
REVOKATION_REASON_KEYCOMPROMISE = 1
REVOKATION_REASON_CACOMPROMISE = 2
REVOKATION_REASON_AFFILIATIONCHANGED = 3
REVOKATION_REASON_SUPERSEDED = 4
REVOKATION_REASON_CESSATIONOFOPERATION = 5
REVOKATION_REASON_CERTIFICATEHOLD = 6
REVOKATION_REASON_REMOVEFROMCRL = 8
REVOKATION_REASON_PRIVILEGESWITHDRAWN = 9
REVOKATION_REASON_AACOMPROMISE = 10
class HTTPSClientAuthHandler(get_https_handler()):
def __init__(self, key, cert):
get_https_handler().__init__(self)
self.key = key
self.cert = cert
def https_open(self, req):
return self.do_open(self.get_connection, req)
def get_connection(self, host, timeout=5):
if sys.version_info[0] >= 3:
return http.client.HTTPSConnection(host, key_file=self.key, cert_file=self.cert, timeout=timeout)
else:
return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert, timeout=timeout)
class HTTPSClientCertTransport(suds.transport.http.HttpTransport):
def __init__(self, key, cert, *args, **kwargs):
suds.transport.http.HttpTransport.__init__(self, *args, **kwargs)
self.key = key
self.cert = cert
def u2open(self, u2request, timeout=None):
tm = timeout or self.options.timeout
if sys.version_info[0] >= 3:
url = urllib.request.build_opener(HTTPSClientAuthHandler(self.key, self.cert))
else:
url = urllib2.build_opener(HTTPSClientAuthHandler(self.key, self.cert))
if self.u2ver() < 2.6:
socket.setdefaulttimeout(tm)
return url.open(u2request)
else:
return url.open(u2request, timeout=tm)
class Ejbca(object):
def __init__(self, url, cert=None, key=None):
self.url = url
self.cert = cert
self.key = key
self.transport = HTTPSClientCertTransport(self.key, self.cert) if self.cert else None
self.wsclient = suds.client.Client(self.url, transport=self.transport)
def get_version(self):
return self.wsclient.service.getEjbcaVersion()
def get_users(self):
return self.find_user(MATCH_WITH_DN, MATCH_TYPE_CONTAINS, "=")
def find_user(self, matchwith, matchtype, matchvalue):
usermatch = self.wsclient.factory.create('userMatch')
usermatch.matchwith = matchwith
usermatch.matchtype = matchtype
usermatch.matchvalue = matchvalue
return self.wsclient.service.findUser(usermatch)
def edit_user(self, user):
return self.wsclient.service.editUser(user)
def _decode_ejbca_cert(self, double_mess):
single_mess = base64.b64decode(double_mess)
cert_data = base64.b64decode(single_mess)
cert = M2Crypto.X509.load_cert_string(cert_data, M2Crypto.X509.FORMAT_DER)
return cert
def pkcs10_request(self, username, password, pkcs10, hardTokenSN, responseType):
res = self.wsclient.service.pkcs10Request(
arg0=username,
arg1=password,
arg2=pkcs10,
arg3=hardTokenSN,
arg4=responseType)
return self._decode_ejbca_cert(res["data"])
def find_certs(self, loginName, validOnly=False):
reslist = self.wsclient.service.findCerts(
arg0=loginName,
arg1=validOnly)
certs = []
for res in reslist:
double_mess = res["certificateData"]
if double_mess is not None:
cert = self._decode_ejbca_cert(double_mess)
cert.ejbca_status = res["type"]
certs.append(cert)
return certs
# OpenSSL root CA configuration file.
# Copy to `/root/ca/openssl.cnf`.
[ ca ]
# `man ca`
default_ca = CA_default
[ CA_default ]
# Directory and file locations.
dir = /var/spool/example-ca
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
RANDFILE = $dir/private/.rand
unique_subject = no
# The root key and root certificate.
private_key = $dir/private/ca.key.pem
certificate = $dir/certs/ca.cert.pem
# For certificate revocation lists.
crlnumber = $dir/crlnumber
crl = $dir/crl/ca.crl.pem
crl_extensions = crl_ext
default_crl_days = 30
# SHA-1 is deprecated, so use SHA-2 instead.
default_md = sha256
name_opt = ca_default
cert_opt = ca_default
default_days = 375
preserve = no
policy = policy_loose
[ policy_loose ]
# Allow the CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
# Options for the `req` tool (`man req`).
default_bits = 2048
distinguished_name = req_distinguished_name
string_mask = utf8only
# SHA-1 is deprecated, so use SHA-2 instead.
default_md = sha256
# Extension to add when the -x509 option is used.
x509_extensions = v3_ca
[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
# Optionally, specify some defaults.
countryName_default = CZ
stateOrProvinceName_default = Czech Republic
localityName_default =
0.organizationName_default = Example
organizationalUnitName_default =
emailAddress_default =
[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client
nsComment = "OpenSSL Generate Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
#!/bin/bash
key=key.pem
csr=csr.pem
cert=cert.pem
result=${TMPDIR:-${TMP:-/tmp}}/cert.$$.$RANDOM
config=${TMPDIR:-${TMP:-/tmp}}/conf.$$.$RANDOM
if [ "$1" == "--cacert" ]; then
cacert="--cacert $2"
shift
shift
fi
url="$1"
client="$2"
password="$3"
incert="$3"
inkey="$4"
trap 'rm -f "$config $result"' INT TERM HUP EXIT
function flee { echo -e "$1"; exit $2; }
[ -z "$client" -o -z "$password" ] && flee "Usage: ${0%.*} [--cacert CERT] url client.name password\n ${0%.*} [--cacert CERT] url client.name cert_file key_file" 255
url="${url%/}/getCert"
for n in openssl curl; do
command -v "$n" 2>&1 >/dev/null || flee "Haven't found $n binary." 251
done
for n in "$csr" "$key" "$cert"; do
[ -e "$n" ] && flee "$n already exists, I won't overwrite, move them away first, please." 254
done
for n in "$result" "$config"; do
touch "$n" || flee "Error creating temporary file ($n)." 253
done
echo -e "default_bits=2048\ndistinguished_name=rdn\nprompt=no\n[rdn]\ncommonName=dummy" > "$config"
openssl req -new -nodes -batch -keyout "$key" -out "$csr" -config "$config" || flee "Error generating key/certificate request." 252
if [ -z "$inkey" ]; then
curl --progress-bar $cacert --request POST --data-binary '@-' "$url?name=$client&password=$password" < "$csr" > "$result"
else
# local cert file name may be interpreted as a "nickname", add "./" to force interpretation as a file
if [[ ! "$incert" =~ "/" ]]; then
incert="./$incert"
fi
curl --progress-bar $cacert --request POST --data-binary '@-' --cert "$incert" --key "$inkey" "$url?name=$client" < "$csr" > "$result"
fi
case $(<$result) in '-----BEGIN CERTIFICATE-----'*)
mv "$result" "$cert"
flee "Succesfully generated key ($key) and obtained certificate ($cert)." 0
esac
flee "$(<$result)\n\nCertificate request failed. Please save all error messages for communication with registration authority representative." 252
{
"Log": {
"filename": "/var/log/warden_ra.log",
"level": "info"
},
"Registry": {
// Example configuration for OpenSSL CA backend
// "type": "OpenSSLRegistry",
// "base_dir": "/var/spool/example-ca",
// "subject_dn_template": "DC=cz,DC=example-ca,DC=warden,CN=%s"
// Example configuration for EJBCA backend
// "type": "EjbcaRegistry",
// "url": "https://ejbca.example.org/ejbca/ejbcaws/ejbcaws?wsdl",
// "cert": "warden_ra.cert.pem",
// "key": "warden_ra.key.pem",
// "ca_name": "Example CA",
// "certificate_profile_name": "Example",
// "end_entity_profile_name": "Example EE",
// "subject_dn_template": "DC=cz,DC=example-ca,DC=warden,CN=%s",
// "username_suffix": "@warden"
}
}
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016, CESNET, z. s. p. o.
# Use of this source is governed by an ISC license, see LICENSE file.
from __future__ import print_function
import sys
import os
import time
import fcntl
import errno
import string
import random
import struct
import operator
import argparse
import json
import logging
import os.path as pth
import subprocess
import shlex
import tempfile
import M2Crypto
import ejbcaws
if sys.version_info[0] >= 3:
import configparser as ConfigParser
else:
import ConfigParser
# usual path to warden server
sys.path.append(pth.join(pth.dirname(__file__), "..", "warden_server"))
import warden_server
from warden_server import Request, ObjectBase, FileLogger, SysLogger, Server, expose, read_cfg
VERSION = "3.0-beta3"
class ClientDisabledError(Exception): pass
class ClientNotIssuableError(Exception): pass
class AuthenticationError(Exception): pass
class PopenError(Exception): pass
class Client(object):
def __init__(self, name, admins=None, status=None, pwd=None, opaque=None):
self.name = name
self.admins = admins or []
self.status = status or "New"
self.pwd = pwd
self.opaque = opaque or {}
def update(self, admins=None, status=None, pwd=None):
if admins is not None:
self.admins = admins
if status:
if self.status == "Disabled" and status not in ("Passive", "Disabled"):
raise ClientDisabledError("This client is disabled")
self.status = status
self.pwd = pwd if status=="Issuable" and pwd else None
def __str__(self):
return (
"Client: %s\n"
"Admins: %s\n"
"Status: %s\n"
) % (self.name, ", ".join(self.admins), self.status)
def str(self, verbose=False):
return str(self) + (str(self.opaque) if self.opaque and verbose else "")
class OpenSSLRegistry(object):
def __init__(self, log, base_dir,
subject_dn_template, openssl_sign, lock_timeout):
self.base_dir = base_dir
self.cnf_file = pth.join(base_dir, "openssl.cnf")
self.client_dir = pth.join(base_dir, "clients")
self.serial_file = pth.join(base_dir, "serial")
self.newcerts_dir = pth.join(base_dir, "newcerts")
self.csr_dir = pth.join(base_dir, "csr")
self.lock_file = pth.join(base_dir, "lock")
self.lock_timeout = lock_timeout
self.log = log
self.subject_dn_template = subject_dn_template
self.openssl_sign = openssl_sign
os.umask(0o0002) # read privilege for usual apache group
def get_clients(self):
return [self.get_client(c) for c in os.listdir(self.client_dir) if pth.isdir(pth.join(self.client_dir, c))]
def get_client(self, name):
config = ConfigParser.RawConfigParser()
try:
with open(pth.join(self.client_dir, name, "state")) as cf:
config.readfp(cf)
except IOError as e:
if e.errno == errno.ENOENT:
return None
raise
datum = dict(config.items("Client"))
return Client(name, admins=datum["admins"].split(","), status=datum["status"], pwd=datum.get("password"))
def new_client(self, name, admins=None):
user = self.get_client(name)
if user:
raise LookupError("Client %s already exists" % name)
return Client(name, admins)
def save_client(self, client):
config = ConfigParser.RawConfigParser()
config.add_section("Client")
config.set("Client", "admins", ",".join(client.admins))
config.set("Client", "status", client.status)
if client.pwd:
config.set("Client", "password", client.pwd)
client_path = pth.join(self.client_dir, client.name)
try:
os.makedirs(client_path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
with tempfile.NamedTemporaryFile(dir=client_path, delete=False, mode="w") as cf:
config.write(cf)
os.chmod(cf.name, 0o660) # read privilege for usual apache group
os.rename(cf.name, pth.join(client_path, "state")) # atomic + rewrite, so no need for locking
def get_certs(self, client):
files = [fname for fname in os.listdir(pth.join(self.client_dir, client.name)) if not fname.startswith(".") and fname.endswith(".pem")]
certs = [M2Crypto.X509.load_cert(pth.join(self.client_dir, client.name, fname)) for fname in files]
return certs
def __enter__(self):
self._lockfd = os.open(self.lock_file, os.O_CREAT)
start = time.time()
while True:
try:
fcntl.flock(self._lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
return
except (OSError, IOError) as e:
if e.errno != errno.EAGAIN or time.time() > start + self.lock_timeout:
raise
time.sleep(0.5)
def __exit__(self, type_, value, traceback):
fcntl.flock(self._lockfd, fcntl.LOCK_UN)
os.close(self._lockfd)
try:
os.unlink(self.lock_file)
except:
pass
def run_openssl(self, command, **kwargs):
cmdline = shlex.split(command % kwargs)
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = process.communicate()
if process.returncode:
raise PopenError("Popen returned nonzero code", process.returncode, ' '.join(cmdline), res[0], res[1])
return res
def new_cert(self, client, csr, pwd):
if client.status != "Issuable" or not client.pwd:
raise ClientNotIssuableError("Client not allowed to issue request or password not set")
if client.pwd != pwd:
raise AuthenticationError("Wrong credentials")
dn = self.subject_dn_template.replace("/", "//").replace(",", "/") % client.name
if not dn.startswith("/"):
dn = "/" + dn
with tempfile.NamedTemporaryFile(dir=self.csr_dir, delete=False) as csr_file:
csr_file.write(csr)
with self: # lock dance
with open(self.serial_file) as f:
serial = f.read().strip()
output = self.run_openssl(self.openssl_sign, cnf = self.cnf_file, csr = csr_file.name, dn = dn)
self.log.debug(output)
os.rename(csr_file.name, pth.join(self.csr_dir, serial + ".csr.pem"))
client_pem_name = pth.join(self.client_dir, client.name, serial + ".cert.pem")
os.symlink(pth.join(self.newcerts_dir, serial + ".pem"), client_pem_name)
with open(client_pem_name) as pem:
cert = M2Crypto.X509.load_cert_string(pem.read(), M2Crypto.X509.FORMAT_PEM)
client.update(status="Passive", pwd=None)
self.save_client(client)
return cert
def __str__(self):
return "%s<%s>" % (type(self).__name__, self.base_dir)
class EjbcaRegistry(OpenSSLRegistry):
status_ejbca_to_str = {
ejbcaws.STATUS_NEW: "Issuable",
ejbcaws.STATUS_GENERATED: "Passive",
ejbcaws.STATUS_INITIALIZED: "New",
ejbcaws.STATUS_HISTORICAL: "Disabled"
}
status_str_to_ejbca = dict((v, k) for k, v in status_ejbca_to_str.items())
def __init__(self, log, url, cert=None, key=None,
ca_name="", certificate_profile_name="", end_entity_profile_name="",
subject_dn_template="%s", username_suffix=""):
self.log = log
self.ejbca = ejbcaws.Ejbca(url, cert, key)
self.ca_name = ca_name
self.certificate_profile_name = certificate_profile_name
self.end_entity_profile_name = end_entity_profile_name
self.subject_dn_template = subject_dn_template
self.username_suffix = username_suffix
def client_data(self, ejbca_data):
ejbca_username = ejbca_data["username"]
username = ejbca_username[:-len(self.username_suffix)] if ejbca_username.endswith(self.username_suffix) else ejbca_username
try:
alt_name = ejbca_data["subjectAltName"]
except KeyError:
alt_name = None
if alt_name:
admins = [u if not u.startswith("RFC822NAME") else u[11:] for u in alt_name.split(",")]
else:
admins = []
status = self.status_ejbca_to_str.get(ejbca_data["status"], "Other")
return username, admins, status, None, ejbca_data
def get_clients(self):
return [Client(*self.client_data(u)) for u in self.ejbca.get_users()]
def get_client(self, name):
users = self.ejbca.find_user(ejbcaws.MATCH_WITH_USERNAME, ejbcaws.MATCH_TYPE_EQUALS, name + self.username_suffix)
if len(users) > 1:
raise LookupError("%d users %s found (more than one?!)" % (len(users), name))
if not users:
return None
return Client(*self.client_data(users[0]))
def save_client(self, client):
edata = client.opaque or dict(
caName=self.ca_name,
certificateProfileName=self.certificate_profile_name,
endEntityProfileName=self.end_entity_profile_name,
keyRecoverable=False,
sendNotification=False,
tokenType=ejbcaws.TOKEN_TYPE_USERGENERATED,
password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16))),
clearPwd = True,
username = client.name + self.username_suffix,
subjectDN = self.subject_dn_template % client.name
)
edata["subjectAltName"] = ",".join(("RFC822NAME=%s" % a for a in client.admins))
try:
edata["status"] = self.status_str_to_ejbca.get(client.status)
except KeyError:
# Unknown status - either came from EJBCA and translated to
# "Other", or something wrong came in later. Let's just
# keep original EJBCA status unchanged.
pass
if client.pwd:
edata["password"] = client.pwd
edata["clearPwd"] = True
self.ejbca.edit_user(edata)
def get_certs(self, client):
return self.ejbca.find_certs(client.opaque["username"], validOnly=False)
def new_cert(self, client, csr, pwd):
cert = self.ejbca.pkcs10_request(
client.opaque["username"],
pwd, csr, 0, ejbcaws.RESPONSETYPE_CERTIFICATE)
return cert
def __str__(self):
return self.ejbca.get_version()
def relaxed_ord(c):
# Compatibility wrapper for py2/py3
try:
return ord(c)
except TypeError:
return c
def format_cert(cert):
return (
"Subject: %s\n"
"Validity: %s - %s\n"
"Serial: %s\n"
"Fingerprint: md5:%s, sha1:%s\n"
"Issuer: %s\n"
) % (
cert.get_subject().as_text(),
cert.get_not_before().get_datetime().isoformat(),
cert.get_not_after().get_datetime().isoformat(),
":".join(["%02x" % relaxed_ord(c) for c in struct.pack('!Q', cert.get_serial_number())]),
cert.get_fingerprint("md5"),
cert.get_fingerprint("sha1"),
cert.get_issuer().as_text()
)
# Server side
class OptionalAuthenticator(ObjectBase):
def __init__(self, req, log):
ObjectBase.__init__(self, req, log)
def __str__(self):
return "%s(req=%s)" % (type(self).__name__, type(self.req).__name__)
def authenticate(self, env, args):
cert_name = env.get("SSL_CLIENT_S_DN_CN")
if cert_name:
if cert_name != args.setdefault("name", [cert_name])[0]:
exception = self.req.error(message="authenticate: client name does not correspond with certificate", error=403, cn = cert_name, args = args)
exception.log(self.log)
return None
verify = env.get("SSL_CLIENT_VERIFY")
if verify != "SUCCESS":
exception = self.req.error(message="authenticate: certificate present but verification failed", error=403, cn = cert_name, args = args, verify=verify)
exception.log(self.log)
return None
return "cert" # Ok, client authorized by valid certificate
else:
try:
args["password"][0]
return "pwd" # Ok, pass on, but getCert will have to rely on certificate registry password
except (KeyError, IndexError):
exception = self.req.error(message="authenticate: no certificate nor password present", error=403, cn = cert_name, args = args)
exception.log(self.log)
return None
def authorize(self, env, client, path, method):
return True
class CertHandler(ObjectBase):
def __init__(self, req, log, registry):
ObjectBase.__init__(self, req, log)
self.registry = registry
@expose(read=1, debug=1)
def getCert(self, csr_data=None, name=None, password=None):
if not (name and csr_data):
raise self.req.error(message="Wrong or missing arguments", error=400, name=name, password=password)
client = self.registry.get_client(name[0])
if not client:
raise self.req.error(message="Unknown client", error=403, name=name, password=password)
self.log.info("Client %s" % client)
if self.req.client == "cert":
# Correctly authenticated by cert, most probably not preactivated with password,
# so generate oneshot password and allow now
password = ["".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16)))]
self.log.debug("Authorized by X509, enabling cert generation with password %s" % password)
try:
client.update(status="Issuable", pwd=password[0])
self.registry.save_client(client)
except ClientDisabledError as e:
raise self.req.error(message="Error enabling cert generation", error=403, exc=sys.exc_info())
if not password:
raise self.req.error(message="Missing password and certificate validation failed", error=403, name=name, password=password)
try:
newcert = self.registry.new_cert(client, csr_data.decode('latin1'), password[0])
except Exception as e:
raise self.req.error(message="Processing error", error=403, exc=sys.exc_info())
self.log.info("Generated.")
return [("Content-Type", "application/x-pem-file")], newcert.as_pem()
# Order in which the base objects must get initialized
section_order = ("log", "auth", "registry", "handler", "server")
# List of sections and objects, configured by them
# First object in each object list is the default one, otherwise
# "type" keyword in section may be used to choose other
section_def = {
"log": [FileLogger, SysLogger],
"auth": [OptionalAuthenticator],
"registry": [OpenSSLRegistry, EjbcaRegistry],
"handler": [CertHandler],
"server": [Server]
}
# Object parameter conversions and defaults
param_def = {
FileLogger: warden_server.param_def[FileLogger],
SysLogger: warden_server.param_def[SysLogger],
Server: warden_server.param_def[Server],
OptionalAuthenticator: {
"req": {"type": "obj", "default": "req"},
"log": {"type": "obj", "default": "log"}
},
OpenSSLRegistry: {
"log": {"type": "obj", "default": "log"},
"base_dir": {"type": "str", "default": pth.join(pth.dirname(__file__), "ca")},
"subject_dn_template": {"type": "str", "default": "DC=cz,DC=example-ca,DC=warden,CN=%s"},
"openssl_sign": {"type": "str", "default": "openssl ca -config %(cnf)s -batch -extensions server_cert -days 375 -notext -md sha256 -in %(csr)s -subj '%(dn)s'"},
"lock_timeout": {"type": "natural", "default": "3"}
},
EjbcaRegistry: {
"log": {"type": "obj", "default": "log"},
"url": {"type": "str", "default": "https://ejbca.example.org/ejbca/ejbcaws/ejbcaws?wsdl"},
"cert": {"type": "filepath", "default": pth.join(pth.dirname(__file__), "warden_ra.cert.pem")},
"key": {"type": "filepath", "default": pth.join(pth.dirname(__file__), "warden_ra.key.pem")},
"ca_name": {"type": "str", "default": "Example CA"},
"certificate_profile_name": {"type": "str", "default": "Example"},
"end_entity_profile_name": {"type": "str", "default": "Example EE"},
"subject_dn_template": {"type": "str", "default": "DC=cz,DC=example-ca,DC=warden,CN=%s"},
"username_suffix": {"type": "str", "default": "@warden"}
},
CertHandler: {
"req": {"type": "obj", "default": "req"},
"log": {"type": "obj", "default": "log"},
"registry": {"type": "obj", "default": "registry"}
}
}
param_def[FileLogger]["filename"] = {"type": "filepath", "default": pth.join(pth.dirname(__file__), pth.splitext(pth.split(__file__)[1])[0] + ".log")}
def build_server(conf):
return warden_server.build_server(conf, section_order, section_def, param_def)
# Command line
def list_clients(registry, name=None, verbose=False, show_cert=True):
if name is not None:
client = registry.get_client(name)
if client is None:
print("No such client.")
return
else:
print(client.str(verbose))
if show_cert:
for cert in sorted(registry.get_certs(client), key=lambda c: c.get_not_after().get_datetime()):
print(format_cert(cert))
if verbose:
print(cert.as_text())
else:
clients = registry.get_clients()
for client in sorted(clients, key=operator.attrgetter("name")):
print(client.str(verbose))
def register_client(registry, name, admins=None, verbose=False):
try:
client = registry.new_client(name, admins)
except LookupError as e:
print(e)
return
registry.save_client(client)
list_clients(registry, name, verbose, show_cert=False)
def applicant(registry, name, password=None, verbose=False):
client = registry.get_client(name)
if not client:
print("No such client.")
return
if password is None:
password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16)))
try:
client.update(status="Issuable", pwd=password)
except ClientDisabledError:
print("This client is disabled. Use 'enable' first.")
return
registry.save_client(client)
list_clients(registry, name, verbose, show_cert=False)
print("Application password is: %s\n" % password)
def enable(registry, name, verbose=False):
client = registry.get_client(name)
if not client:
print("No such client.")
return
client.update(status="Passive")
registry.save_client(client)
list_clients(registry, name, verbose, show_cert=False)
def disable(registry, name, verbose=False):
client = registry.get_client(name)
if not client:
print("No such client.")
return
client.update(status="Disabled")
registry.save_client(client)
list_clients(registry, name, verbose, show_cert=False)
def request(registry, key, csr, verbose=False):
openssl = subprocess.Popen(
[
"openssl", "req", "-new", "-nodes", "-batch",
"-keyout", key,
"-out", csr,
"-config", "/dev/stdin"
], stdin=subprocess.PIPE
)
openssl.stdin.write(
"distinguished_name=req_distinguished_name\n"
"prompt=no\n"
"\n"
"[req_distinguished_name]\n"
"commonName=dummy".encode("ascii")
)
openssl.stdin.close()
openssl.wait()
if verbose:
with open(csr, "r") as f:
print(f.read())
def gen_cert(registry, name, csr, cert, password, verbose=False):
with open(csr, "r") as f:
csr_data = f.read()
client = registry.get_client(name)
newcert = registry.new_cert(client, csr_data, password)
print(format_cert(newcert))
if verbose:
print(newcert.as_text())
print(newcert.as_pem())
with open(cert, "w") as f:
f.write(newcert.as_text())
f.write(newcert.as_pem().decode("ascii"))
def get_args():
argp = argparse.ArgumentParser(
description="Warden server certificate registry", add_help=False)
argp.add_argument("--help", action="help",
help="show this help message and exit")
argp.add_argument("-c", "--config",
help="path to configuration file")
argp.add_argument("-v", "--verbose", action="store_true", default=False,
help="be more chatty")
subargp = argp.add_subparsers(title="commands")
subargp_list = subargp.add_parser("list", add_help=False,
description="List registered clients.",
help="list clients")
subargp_list.set_defaults(command=list_clients)
subargp_list.add_argument("--help", action="help",
help="show this help message and exit")
subargp_list.add_argument("--name", action="store", type=str,
help="client name")
subargp_reg = subargp.add_parser("register", add_help=False,
description="Add client registration entry.",
help="register client")
subargp_reg.set_defaults(command=register_client)
subargp_reg.add_argument("--help", action="help",
help="show this help message and exit")
subargp_reg.add_argument("--name", action="store", type=str,
required=True, help="client name")
subargp_reg.add_argument("--admins", action="store", type=str,
required=True, nargs="*", help="administrator list")
subargp_apply = subargp.add_parser("applicant", add_help=False,
description="Set client into certificate application mode and set its password",
help="allow for certificate application")
subargp_apply.set_defaults(command=applicant)
subargp_apply.add_argument("--help", action="help",
help="show this help message and exit")
subargp_apply.add_argument("--name", action="store", type=str,
required=True, help="client name")
subargp_apply.add_argument("--password", action="store", type=str,
help="password for application (will be autogenerated if not set)")
subargp_enable = subargp.add_parser("enable", add_help=False,
description="Enable this client",
help="enable this client")
subargp_enable.set_defaults(command=enable)
subargp_enable.add_argument("--help", action="help",
help="show this help message and exit")
subargp_enable.add_argument("--name", action="store", type=str,
required=True, help="client name")
subargp_disable = subargp.add_parser("disable", add_help=False,
description="Disable this client",
help="disable this client (no more applications until enabled again)")
subargp_disable.set_defaults(command=disable)
subargp_disable.add_argument("--help", action="help",
help="show this help message and exit")
subargp_disable.add_argument("--name", action="store", type=str,
required=True, help="client name")
subargp_req = subargp.add_parser("request", add_help=False,
description="Generate certificate request",
help="generate CSR")
subargp_req.set_defaults(command=request)
subargp_req.add_argument("--help", action="help",
help="show this help message and exit")
subargp_req.add_argument("--key", action="store", type=str,
required=True, help="file for saving the key")
subargp_req.add_argument("--csr", action="store", type=str,
required=True, help="file for saving the request")
subargp_cert = subargp.add_parser("gencert", add_help=False,
description="Request new certificate from registry",
help="get new certificate")
subargp_cert.set_defaults(command=gen_cert)
subargp_cert.add_argument("--help", action="help",
help="show this help message and exit")
subargp_cert.add_argument("--name", action="store", type=str,
required=True, help="client name")
subargp_cert.add_argument("--csr", action="store", type=str,
required=True, help="file for saving the request")
subargp_cert.add_argument("--cert", action="store", type=str,
required=True, help="file for saving the new certificate")
subargp_cert.add_argument("--password", action="store", type=str,
required=True, help="password for application")
return argp.parse_args()
if __name__ == "__main__":
args = get_args()
config = pth.join(pth.dirname(__file__), args.config or "warden_ra.cfg")
server = build_server(read_cfg(config))
registry = server.handler.registry
if args.verbose:
print(registry)
command = args.command
subargs = vars(args)
del subargs["command"]
del subargs["config"]
sys.exit(command(registry, **subargs))
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sys import path
from os.path import dirname, join
path.append(dirname(__file__))
from warden_ra import build_server
## JSON configuration with line comments (trailing #)
from warden_ra import read_cfg
application = build_server(read_cfg(join(dirname(__file__), "warden_ra.cfg")))
BSD License
Copyright © 2011-2015 Cesnet z.s.p.o
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Cesnet z.s.p.o nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE Cesnet z.s.p.o BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------+
| Warden Server 3.0-beta3 |
+-------------------------+
Content
A. Introduction
B. Dependencies
C. Installation
D. Configuration
E. Command line
------------------------------------------------------------------------------
A. Introduction
Warden is a system for efficient sharing of information about detected
events (threats). Warden Server is server-side part of the software, the
communication hub, allowing to publish detected events and download yet
unprocessed ones.
The events are exchanged in Idea, flexible and descriptive event JSON
serialized format. Warden 3 protocol is based on plain HTTPS queries with
help of JSON, so the clients can be thin and simple.
For description of API see Warden Client, this document describes basics
of Warden Server administration.
Warden Server is Python/WSGI based, written primarily with Apache mod_wsgi
in mind. Other WSGI servers/frameworks are not yet tested, so your mileage
may vary. Authentication is X509 certificate (for machine or client
identification) + shared secret (for client identification, where
certificate does not suffice).
------------------------------------------------------------------------------
B. Dependencies
1. Platform
Python 2.7+
Apache 2.2/2.4
mod_wsgi 3.3+
2. Python modules
python-mysqldb 5.3.3+
python-m2crypto 0.20+
jsonschema 2.4+
3. Database
MySQL | MariaDB >= 5.5
------------------------------------------------------------------------------
C. Installation
* Untar distribution archive into required destination
(We're using /opt as an example.)
# cd /opt
# tar xjf warden_server_3.0.tar.bz2
# mv warden_server_3.0 warden_server
* Create database and desired database users
(We're using db "warden3" and user "warden@localhost" as an example.)
# mysql -p
> CREATE DATABASE warden3;
> CREATE USER 'warden'@'localhost' IDENTIFIED BY 'example';
> GRANT ALL ON warden3.* TO `warden`@`localhost`;
> FLUSH PRIVILEGES;
* Create necessary table structure
mysql -p -u warden warden3 < warden_3.0.sql
* Get up to date Idea schema
wget -O warden_server/idea.schema https://idea.cesnet.cz/_media/en/idea0.schema
* Enable mod_wsgi, mod_ssl, include Warden configuration
This depends heavily on your distribution and Apache configuration.
Basically you need to create and include apache.conf:
Include /opt/warden_server/apache.conf
or paste the contents into whichever Directory, Location or VirtualHost
you dedicate for Warden. You can use apache22.conf.dist or
apache24.conf.dist (for Apache version 2.2 or 2.4, respectively) as an
example.
You may need to change paths to certificate/key/ca material, path to
warden_server.wsgi and web path alias.
Also note that warden_server itself is NOT thread safe, so included
configuration expects Apache with mpm-prefork worker, or you will have
to configure mod_wsgi as separate process with threads=1.
Also, for warden_server.wsgi, you can use warden_server.wsgi.dist as
a template. You will possibly need to change at least configuration
file path.
* Configure Warden server (see next chapter)
* Reload Apache
------------------------------------------------------------------------------
D. Configuration
Configuration is JSON object in file (warden_server.cfg by default),
however, lines starting with "#" or "//" are allowed and will be ignored as
comments. File must contain valid JSON object, containing configuration. See
also warden_server.cfg.dist as example.
Top level JSON object contains subsections, which configure particular
aspect of the server. Each subsection can contain "type" key, which chooses
particular implementation object of the aspect, for example type of logger
(file, syslog), such as:
{
"Log": {
"type": "SysLogger",
...
},
"DB": { ... }
}
Sections and their "type" objects can be:
Log: FileLogger, SysLogger
DB: MySQL
Auth: X509Authenticator, X509NameAuthenticator,
X509MixMatchAuthenticator,PlainAuthenticator
Validator: JSONSchemaValidator, NoValidator
Handler: WardenHandler
"type" keyword is not mandatory, if not specified, first implementation
object from particular section list is used ("FileLogger" for example).
Object function and configuration keys are as follows:
FileLogger: logging into file on the filesystem
filename: name of the file, defaults to "warden_server.log" at
installation directory
level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG)
SysLogger: logging into unix syslog
socket: path to syslog socket, defaults to "/dev/log"
facility: syslog facility, defaults to "daemon"
level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG)
X509Authenticator: authenticate based on certificate chain validation,
hostname corresponding with certificate CN or SubjectAltName and
optionally shared secret (note that more clients on one machine
will have to have the certificate with the same hostname, clients
than can be differentiated by separate secrets).
This method is OBSOLETE.
X509NameAuthenticator: authenticate based on certificate chain validation,
certificate CN must correspond with client _name_, NOT hostname.
X509MixMatchAuthenticator: automatically choose X509Authenticator or
X509NameAuthenticator based on existence of 'secret' in query. Allows
for seamless transition of clients between two authentication methods.
PlainAuthenticator: authenticate based on client name or shared secret, usable
over plain HTTP connection or HTTPS without client certificate - note that
this pretty much spoils security, and is meant only for testing and
debugging purposes, NOT for production servers
NoValidator: forego event JSON validation, for debug purposes
JSONSchemaValidator: validate incoming events based on JSON schema file
filename: path to schema file, defaults to "idea.schema" at
installation directory, for information on obtaining current
IDEA schema file, refer to https://idea.cesnet.cz/en/schema
MySQL: database storage backend
host: database server host, default "localhost"
user: database user, default "warden"
password: database password
dbname: database name, default "warden3"
port: database server port, default 3306
retry_pause: retry in case of database errors, in seconds, defaults to 5
retry_count: number of retries, defaults to 3
event_size_limit: max size of serialized event, defaults to 5 MB
catmap_filename: IDEA category mapping to database ids, defaults to
"catmap_mysql.json" at installation directory
tagmap_filename": IDEA node type mapping to database ids, defaults to
"tagmap_mysql.json" at installation directory
WardenHandler: Main Warden RPC worker
send_events_limit: max events sent in one bunch, defaults to 10000
get_events_limit: max events received in one bunch, defaults to 10000
description: human readable description, sent in server info
------------------------------------------------------------------------------
E. Command line
When run from command line, server offers set of commands and options for
runtime and database management. You can also use --help option for each
command and for server itself.
warden_server.py [--help] [-c CONFIG] <command>
optional arguments:
-c CONFIG, --config CONFIG
configuration file path
commands:
{check,register,modify,list,loadmaps}
check check configuration
register register new client
modify modify client registration
list list registered clients
loadmaps load catmap and tagmap into db
warden_server.py register [--help] -n NAME -h HOSTNAME -r REQUESTOR
[-s SECRET] [--note NOTE]
[--valid | --novalid] [--read | --noread]
[--nowrite | --write] [--nodebug | --debug]
[--test | --notest]
Add new client registration entry.
optional arguments:
-n NAME, --name NAME client name (in dotted reverse path notation)
-h HOSTNAME, --hostname HOSTNAME
client FQDN hostname
-r REQUESTOR, --requestor REQUESTOR
requestor email
-s SECRET, --secret SECRET
authentication token (use explicit empty string to
disable)
--note NOTE client freetext description
--valid valid client (default)
--novalid
--read client is allowed to read (default)
--noread
--nowrite client is allowed to send (default - no)
--write
--nodebug client is allowed receive debug output (default - no)
--debug
--test client is yet in testing phase (default - yes)
--notest
warden_server.py modify [--help] -i ID [-n NAME] [-h HOSTNAME]
[-r REQUESTOR] [-s SECRET] [--note NOTE]
[--valid | --novalid] [--read | --noread]
[--nowrite | --write] [--nodebug | --debug]
[--test | --notest]
Modify details of client registration entry.
optional arguments:
-i ID, --id ID client id
Rest of the arguments is same as in "register" command.
warden_server.py list [--help] [--id ID]
List details of client registration entries.
optional arguments:
--id ID client id
warden_server.py purge [--help] [-l] [-e] [-d DAYS]
Purge old events or lastlog records. Note that lastlog purge retains at
least one newest record for each client, even if it is more than number of
'days' old.
optional arguments:
--help show this help message and exit
-l, --lastlog purge lastlog records
-e, --events purge events
-d DAYS, --days DAYS records older than 'days' back from today will get
purged
warden_server.py loadmaps [--help]
Load 'categories' and 'tags' table from 'catmap_mysql.json' and
'tagmap_mysql.json'. Note that this is NOT needed for server at all, load
them into db at will, should you need to run your own specific SQL queries
on data directly. Note also that previous content of both tables will be
lost.
------------------------------------------------------------------------------
Copyright (C) 2011-2015 Cesnet z.s.p.o
+----------------------------------+
| Warden3 Server Test Suite README |
+----------------------------------+
Content
A. Introduction
B. Compatibility
C. Dependencies
D. Usage
-------------------------------------------------------------------------------
A. Introduction
The Warden Server Test Suite is a collection of high-level functional tests
(black-box testing), covering the most important interfaces of the Warden
Server.
-------------------------------------------------------------------------------
B. Compatibility
* The test suite, just like the Warden Server, is compatible with both Python2
(tested on 2.7) and Python3 (tested on 3.6).
* Just like Warden Server, the test suite requires a local MySQL installation.
* It is safe to run the test suite on a production system. For testing,
a database distinct from the default production one is used. Also, the user
account used for accessing the testing database is set for local login only.
To be extra safe, make sure not to name the production database `w3test`.
-------------------------------------------------------------------------------
C. Dependencies
In addition to the regular Warden Server dependencies, package `unittest2` is
required to run the test suite. It can be installed by running:
pip install unittest2
or `pip3 install unittest2` for Python3 version
or on Debian:
apt-get install python-unittest2
or alternatively:
apt-get install python3-unittest2
for Python3 version.
An optional dependency is a code coverage measurement tool `Coverage.py`,
which can be installed by:
pip install coverage
or `pip3 install coverage` for Python3 version
or on Debian:
apt-get install python-coverage
or alternatively:
apt-get install python3-coverage
for Python3 version.
-------------------------------------------------------------------------------
D. Usage
Before running the tests (for the first time), a DB user with required rights
must be created. An easy way to do it is:
./test_warden_server.py --init
This will prompt for MySQL root password.
Standard usage for testing:
./test_warden_server.py
Advanced usage:
./test_warden_server.py --help
usage: test_warden_server.py [-h] [-i] [-n]
Warden3 Server Test Suite
optional arguments:
-h, --help show this help message and exit
-i, --init Set up an user with rights to CREATE/DROP the
test database
-n, --nopurge Skip the database purge after running the tests
Option -n (--nopurge) is meant for debugging purposes and test development, it
keeps the test database around for inspection after running the tests.
-------------------------------------------------------------------------------
SSLEngine on
SSLVerifyClient require
SSLVerifyClient optional
SSLVerifyDepth 4
SSLOptions +StdEnvVars +ExportCertData
#SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile /opt/warden_server_3/etc/cert.pem
SSLCertificateKeyFile /opt/warden_server_3/etc/key.pem
SSLCACertificateFile /opt/warden_server_3/etc/tcs-ca-bundle.pem
SSLCertificateFile /opt/warden_server/etc/cert.pem
SSLCertificateKeyFile /opt/warden_server/etc/key.pem
SSLCACertificateFile /opt/warden_server/etc/tcs-ca-bundle.pem
WSGIScriptAlias /warden3 /opt/warden_server_3/warden_server.wsgi
WSGIScriptAlias /warden3 /opt/warden_server/warden_server.wsgi
<Directory /opt/warden_server_3/warden_server.wsgi>
<Directory /opt/warden_server/warden_server.wsgi>
Order allow,deny
Allow from all
</Directory>
SSLEngine on
SSLVerifyClient optional
SSLVerifyDepth 4
SSLOptions +StdEnvVars +ExportCertData
#SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile /opt/warden_server/etc/cert.pem
SSLCertificateKeyFile /opt/warden_server/etc/key.pem
SSLCACertificateFile /opt/warden_server/etc/tcs-ca-bundle.pem
WSGIScriptAlias /warden3 /opt/warden_server/warden_server.wsgi
<DirectoryMatch /opt/warden_server/warden_server.wsgi>
Require all granted
</DirectoryMatch>
......@@ -42,6 +42,7 @@
"Fraud.Scam" : 805,
"Vulnerable" : 900,
"Vulnerable.Open" : 901,
"Vulnerable.Config" : 902,
"Anomaly" : 1000,
"Anomaly.Traffic" : 1001,
"Anomaly.Connection" : 1002,
......
......@@ -12,6 +12,8 @@
"Correlation": 11,
"External": 12,
"Reporting": 13,
"Relay": 14,
"Auth": 15,
"Blackhole": 30,
"Signature": 31,
"Statistical": 32,
......