Skip to content
Snippets Groups Projects
Commit 9841ddd6 authored by Pavel Kácha's avatar Pavel Kácha
Browse files

Added certificate prolongation, added README

parent 0d1c62d8
No related branches found
No related tags found
No related merge requests found
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+
EJBCA_ 3.9+
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 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 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
------------
As for now, correctly configured and running EJBCA_ PKI is necessary. PKI part
of the RA is however pluggable, so simple openssl backend is also planned.
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.
* 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
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
EjbcaRegistry: EJBCA connector configuration
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"
Command line
------------
Whe 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/
.. _EJBCA: https://www.ejbca.org/
------------------------------------------------------------------------------
Copyright (C) 2017 Cesnet z.s.p.o
SSLEngine on
SSLVerifyClient none
SSLVerifyClient optional
SSLOptions +StdEnvVars +ExportCertData
SSLCertificateFile /opt/warden_server_3/cert.pem
......
......@@ -8,12 +8,14 @@ result=${TMPDIR:-${TMP:-/tmp}}/cert.$$.$RANDOM
config=${TMPDIR:-${TMP:-/tmp}}/conf.$$.$RANDOM
client="$1"
password="$2"
incert="$2"
inkey="$3"
trap 'rm -f "$config $result"' INT TERM HUP EXIT
function flee { echo -e "$1"; exit $2; }
[ -z "$client" -o -z "$password" ] && flee "Usage: ${0%.*} client.name password" 255
[ -z "$client" -o -z "$password" ] && flee "Usage: ${0%.*} client.name password\n ${0%.*} client.name cert_file key_file" 255
for n in openssl curl; do
command -v "$n" 2>&1 >/dev/null || flee "Haven't found $n binary." 251
......@@ -29,7 +31,11 @@ echo -e "default_bits=2048\ndistinguished_name=rdn\nprompt=no\n[rdn]\ncommonName
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 --request POST --data-binary '@-' "$url?name=$client&password=$password" < "$csr" > "$result"
else
curl --progress-bar --request POST --data-binary '@-' --cert "$incert" --key "$inkey" "$url?name=$client" < "$csr" > "$result"
fi
case $(<$result) in '-----BEGIN CERTIFICATE-----'*)
mv "$result" "$cert"
......
......@@ -167,7 +167,7 @@ def format_cert(cert):
# Server side
class NullAuthenticator(ObjectBase):
class OptionalAuthenticator(ObjectBase):
def __init__(self, req, log):
ObjectBase.__init__(self, req, log)
......@@ -178,7 +178,30 @@ class NullAuthenticator(ObjectBase):
def authenticate(self, env, args):
return True
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):
......@@ -193,12 +216,21 @@ class CertHandler(ObjectBase):
@expose(read=1, debug=1)
def getCert(self, csr_data=None, name=None, password=None):
if not (name and password and csr_data):
raise self.req.error(message="Wrong or missing arguments", error=400, client=name[0], password=password[0])
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, client=name[0], password=password[0])
self.log.info("Client: %s" % client.name)
raise self.req.error(message="Unknown client", error=403, name=name, password=password)
self.log.info("Client %s" % client.name)
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)
client.allow_new_cert(pwd=password)
client.save()
if not password:
raise self.req.error(message="Missing password and certificate validation failed", error=403, name=name, password=password)
try:
newcert = client.new_cert(csr_data, password)
except Exception as e:
......@@ -215,7 +247,7 @@ section_order = ("log", "auth", "registry", "handler", "server")
# "type" keyword in section may be used to choose other
section_def = {
"log": [FileLogger, SysLogger],
"auth": [NullAuthenticator],
"auth": [OptionalAuthenticator],
"registry": [EjbcaRegistry],
"handler": [CertHandler],
"server": [Server]
......@@ -226,7 +258,7 @@ param_def = {
FileLogger: warden_server.param_def[FileLogger],
SysLogger: warden_server.param_def[SysLogger],
Server: warden_server.param_def[Server],
NullAuthenticator: {
OptionalAuthenticator: {
"req": {"type": "obj", "default": "req"},
"log": {"type": "obj", "default": "log"}
},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment