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

Target

Select target project
  • Pavel.Valach/warden
1 result
Show changes
Showing
with 991 additions and 119 deletions
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
...@@ -3,9 +3,9 @@ SSLEngine on ...@@ -3,9 +3,9 @@ SSLEngine on
SSLVerifyClient optional SSLVerifyClient optional
SSLOptions +StdEnvVars +ExportCertData SSLOptions +StdEnvVars +ExportCertData
SSLCertificateFile /opt/warden_server_3/cert.pem SSLCertificateFile /opt/warden_server/cert.pem
SSLCertificateKeyFile /opt/warden_server_3/key.pem SSLCertificateKeyFile /opt/warden_server/key.pem
SSLCACertificateFile /opt/warden_server_3/chain_TERENA_SSL_CA_3.pem SSLCACertificateFile /opt/warden_server/chain_TERENA_SSL_CA_3.pem
WSGIScriptAlias /warden_ra /opt/warden-ra/warden_ra.wsgi WSGIScriptAlias /warden_ra /opt/warden-ra/warden_ra.wsgi
......
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>
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
# Copyright (c) 2016, CESNET, z. s. p. o. # Copyright (c) 2016, CESNET, z. s. p. o.
# Use of this source is governed by an ISC license, see LICENSE file. # Use of this source is governed by an ISC license, see LICENSE file.
import urllib2 import sys
import httplib
import socket import socket
import base64 import base64
import suds.transport.http import suds.transport.http
...@@ -13,6 +12,20 @@ import suds.client ...@@ -13,6 +12,20 @@ import suds.client
import M2Crypto 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_FAILED = 11
STATUS_GENERATED = 40 STATUS_GENERATED = 40
STATUS_HISTORICAL = 60 STATUS_HISTORICAL = 60
...@@ -139,10 +152,10 @@ REVOKATION_REASON_PRIVILEGESWITHDRAWN = 9 ...@@ -139,10 +152,10 @@ REVOKATION_REASON_PRIVILEGESWITHDRAWN = 9
REVOKATION_REASON_AACOMPROMISE = 10 REVOKATION_REASON_AACOMPROMISE = 10
class HTTPSClientAuthHandler(urllib2.HTTPSHandler): class HTTPSClientAuthHandler(get_https_handler()):
def __init__(self, key, cert): def __init__(self, key, cert):
urllib2.HTTPSHandler.__init__(self) get_https_handler().__init__(self)
self.key = key self.key = key
self.cert = cert self.cert = cert
...@@ -150,7 +163,10 @@ class HTTPSClientAuthHandler(urllib2.HTTPSHandler): ...@@ -150,7 +163,10 @@ class HTTPSClientAuthHandler(urllib2.HTTPSHandler):
return self.do_open(self.get_connection, req) return self.do_open(self.get_connection, req)
def get_connection(self, host, timeout=5): def get_connection(self, host, timeout=5):
return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert, timeout=timeout) 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): class HTTPSClientCertTransport(suds.transport.http.HttpTransport):
...@@ -160,9 +176,12 @@ class HTTPSClientCertTransport(suds.transport.http.HttpTransport): ...@@ -160,9 +176,12 @@ class HTTPSClientCertTransport(suds.transport.http.HttpTransport):
self.key = key self.key = key
self.cert = cert self.cert = cert
def u2open(self, u2request): def u2open(self, u2request, timeout=None):
tm = self.options.timeout tm = timeout or self.options.timeout
url = urllib2.build_opener(HTTPSClientAuthHandler(self.key, self.cert)) 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: if self.u2ver() < 2.6:
socket.setdefaulttimeout(tm) socket.setdefaulttimeout(tm)
return url.open(u2request) return url.open(u2request)
......
# 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 #!/bin/bash
url='https://warden-hub.cesnet.cz/warden-ra/getCert'
key=key.pem key=key.pem
csr=csr.pem csr=csr.pem
cert=cert.pem cert=cert.pem
result=${TMPDIR:-${TMP:-/tmp}}/cert.$$.$RANDOM result=${TMPDIR:-${TMP:-/tmp}}/cert.$$.$RANDOM
config=${TMPDIR:-${TMP:-/tmp}}/conf.$$.$RANDOM config=${TMPDIR:-${TMP:-/tmp}}/conf.$$.$RANDOM
client="$1" if [ "$1" == "--cacert" ]; then
password="$2" cacert="--cacert $2"
incert="$2" shift
inkey="$3" shift
fi
url="$1"
client="$2"
password="$3"
incert="$3"
inkey="$4"
trap 'rm -f "$config $result"' INT TERM HUP EXIT trap 'rm -f "$config $result"' INT TERM HUP EXIT
function flee { echo -e "$1"; exit $2; } function flee { echo -e "$1"; exit $2; }
[ -z "$client" -o -z "$password" ] && flee "Usage: ${0%.*} client.name password\n ${0%.*} client.name cert_file key_file" 255 [ -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 for n in openssl curl; do
command -v "$n" 2>&1 >/dev/null || flee "Haven't found $n binary." 251 command -v "$n" 2>&1 >/dev/null || flee "Haven't found $n binary." 251
...@@ -32,9 +39,13 @@ echo -e "default_bits=2048\ndistinguished_name=rdn\nprompt=no\n[rdn]\ncommonName ...@@ -32,9 +39,13 @@ 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 openssl req -new -nodes -batch -keyout "$key" -out "$csr" -config "$config" || flee "Error generating key/certificate request." 252
if [ -z "$inkey" ]; then if [ -z "$inkey" ]; then
curl --progress-bar --request POST --data-binary '@-' "$url?name=$client&password=$password" < "$csr" > "$result" curl --progress-bar $cacert --request POST --data-binary '@-' "$url?name=$client&password=$password" < "$csr" > "$result"
else else
curl --progress-bar --request POST --data-binary '@-' --cert "$incert" --key "$inkey" "$url?name=$client" < "$csr" > "$result" # 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 fi
case $(<$result) in '-----BEGIN CERTIFICATE-----'*) case $(<$result) in '-----BEGIN CERTIFICATE-----'*)
......
{
"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"
}
}
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
# Copyright (c) 2016, CESNET, z. s. p. o. # Copyright (c) 2016, CESNET, z. s. p. o.
# Use of this source is governed by an ISC license, see LICENSE file. # Use of this source is governed by an ISC license, see LICENSE file.
from __future__ import print_function
import sys import sys
import os import os
import time import time
...@@ -21,18 +23,20 @@ import subprocess ...@@ -21,18 +23,20 @@ import subprocess
import shlex import shlex
import tempfile import tempfile
import M2Crypto import M2Crypto
import ConfigParser
# *ph* server vulnerable to logjam, local openssl too new, use hammer to disable Diffie-Helmann
import ssl
ssl._DEFAULT_CIPHERS += ":!DH"
import ejbcaws import ejbcaws
if sys.version_info[0] >= 3:
import configparser as ConfigParser
else:
import ConfigParser
# usual path to warden server # usual path to warden server
sys.path.append(pth.join(pth.dirname(__file__), "..", "warden-server")) sys.path.append(pth.join(pth.dirname(__file__), "..", "warden_server"))
import warden_server import warden_server
from warden_server import Request, ObjectBase, FileLogger, SysLogger, Server, expose, read_cfg from warden_server import Request, ObjectBase, FileLogger, SysLogger, Server, expose, read_cfg
VERSION = "3.0-beta3"
class ClientDisabledError(Exception): pass class ClientDisabledError(Exception): pass
class ClientNotIssuableError(Exception): pass class ClientNotIssuableError(Exception): pass
...@@ -69,78 +73,6 @@ class Client(object): ...@@ -69,78 +73,6 @@ class Client(object):
return str(self) + (str(self.opaque) if self.opaque and verbose else "") return str(self) + (str(self.opaque) if self.opaque and verbose else "")
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
admins = [u if not u.startswith("RFC822NAME") else u[11:] for u in ejbca_data["subjectAltName"].split(",")]
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))
edata["status"] = self.status_str_to_ejbca.get(client.status, edata["status"])
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()
class OpenSSLRegistry(object): class OpenSSLRegistry(object):
def __init__(self, log, base_dir, def __init__(self, log, base_dir,
...@@ -156,14 +88,20 @@ class OpenSSLRegistry(object): ...@@ -156,14 +88,20 @@ class OpenSSLRegistry(object):
self.log = log self.log = log
self.subject_dn_template = subject_dn_template self.subject_dn_template = subject_dn_template
self.openssl_sign = openssl_sign self.openssl_sign = openssl_sign
os.umask(0o0002) # read privilege for usual apache group
def get_clients(self): 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))] 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): def get_client(self, name):
config = ConfigParser.RawConfigParser() config = ConfigParser.RawConfigParser()
if not config.read(pth.join(self.client_dir, name, "state")): try:
return None 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")) datum = dict(config.items("Client"))
return Client(name, admins=datum["admins"].split(","), status=datum["status"], pwd=datum.get("password")) return Client(name, admins=datum["admins"].split(","), status=datum["status"], pwd=datum.get("password"))
...@@ -186,8 +124,9 @@ class OpenSSLRegistry(object): ...@@ -186,8 +124,9 @@ class OpenSSLRegistry(object):
except OSError as e: except OSError as e:
if e.errno != errno.EEXIST: if e.errno != errno.EEXIST:
raise raise
with tempfile.NamedTemporaryFile(dir=client_path, delete=False) as cf: with tempfile.NamedTemporaryFile(dir=client_path, delete=False, mode="w") as cf:
config.write(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 os.rename(cf.name, pth.join(client_path, "state")) # atomic + rewrite, so no need for locking
def get_certs(self, client): def get_certs(self, client):
...@@ -251,6 +190,99 @@ class OpenSSLRegistry(object): ...@@ -251,6 +190,99 @@ class OpenSSLRegistry(object):
return "%s<%s>" % (type(self).__name__, self.base_dir) 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): def format_cert(cert):
return ( return (
"Subject: %s\n" "Subject: %s\n"
...@@ -262,7 +294,7 @@ def format_cert(cert): ...@@ -262,7 +294,7 @@ def format_cert(cert):
cert.get_subject().as_text(), cert.get_subject().as_text(),
cert.get_not_before().get_datetime().isoformat(), cert.get_not_before().get_datetime().isoformat(),
cert.get_not_after().get_datetime().isoformat(), cert.get_not_after().get_datetime().isoformat(),
":".join(["%02x" % ord(c) for c in struct.pack('!Q', cert.get_serial_number())]), ":".join(["%02x" % relaxed_ord(c) for c in struct.pack('!Q', cert.get_serial_number())]),
cert.get_fingerprint("md5"), cert.get_fingerprint("md5"),
cert.get_fingerprint("sha1"), cert.get_fingerprint("sha1"),
cert.get_issuer().as_text() cert.get_issuer().as_text()
...@@ -302,7 +334,7 @@ class OptionalAuthenticator(ObjectBase): ...@@ -302,7 +334,7 @@ class OptionalAuthenticator(ObjectBase):
try: try:
args["password"][0] args["password"][0]
return "pwd" # Ok, pass on, but getCert will have to rely on certificate registry password return "pwd" # Ok, pass on, but getCert will have to rely on certificate registry password
except KeyError, IndexError: except (KeyError, IndexError):
exception = self.req.error(message="authenticate: no certificate nor password present", error=403, cn = cert_name, args = args) exception = self.req.error(message="authenticate: no certificate nor password present", error=403, cn = cert_name, args = args)
exception.log(self.log) exception.log(self.log)
return None return None
...@@ -329,17 +361,17 @@ class CertHandler(ObjectBase): ...@@ -329,17 +361,17 @@ class CertHandler(ObjectBase):
if self.req.client == "cert": if self.req.client == "cert":
# Correctly authenticated by cert, most probably not preactivated with password, # Correctly authenticated by cert, most probably not preactivated with password,
# so generate oneshot password and allow now # so generate oneshot password and allow now
password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16))) 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) self.log.debug("Authorized by X509, enabling cert generation with password %s" % password)
try: try:
client.update(status="Issuable", pwd=password) client.update(status="Issuable", pwd=password[0])
self.registry.save_client(client) self.registry.save_client(client)
except ClientDisabledError as e: except ClientDisabledError as e:
raise self.req.error(message="Error enabling cert generation", error=403, exc=sys.exc_info()) raise self.req.error(message="Error enabling cert generation", error=403, exc=sys.exc_info())
if not password: if not password:
raise self.req.error(message="Missing password and certificate validation failed", error=403, name=name, password=password) raise self.req.error(message="Missing password and certificate validation failed", error=403, name=name, password=password)
try: try:
newcert = self.registry.new_cert(client, csr_data, password) newcert = self.registry.new_cert(client, csr_data.decode('latin1'), password[0])
except Exception as e: except Exception as e:
raise self.req.error(message="Processing error", error=403, exc=sys.exc_info()) raise self.req.error(message="Processing error", error=403, exc=sys.exc_info())
self.log.info("Generated.") self.log.info("Generated.")
...@@ -407,7 +439,7 @@ def list_clients(registry, name=None, verbose=False, show_cert=True): ...@@ -407,7 +439,7 @@ def list_clients(registry, name=None, verbose=False, show_cert=True):
if name is not None: if name is not None:
client = registry.get_client(name) client = registry.get_client(name)
if client is None: if client is None:
print "No such client." print("No such client.")
return return
else: else:
print(client.str(verbose)) print(client.str(verbose))
...@@ -435,14 +467,14 @@ def register_client(registry, name, admins=None, verbose=False): ...@@ -435,14 +467,14 @@ def register_client(registry, name, admins=None, verbose=False):
def applicant(registry, name, password=None, verbose=False): def applicant(registry, name, password=None, verbose=False):
client = registry.get_client(name) client = registry.get_client(name)
if not client: if not client:
print "No such client." print("No such client.")
return return
if password is None: if password is None:
password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16))) password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16)))
try: try:
client.update(status="Issuable", pwd=password) client.update(status="Issuable", pwd=password)
except ClientDisabledError: except ClientDisabledError:
print "This client is disabled. Use 'enable' first." print("This client is disabled. Use 'enable' first.")
return return
registry.save_client(client) registry.save_client(client)
list_clients(registry, name, verbose, show_cert=False) list_clients(registry, name, verbose, show_cert=False)
...@@ -452,7 +484,7 @@ def applicant(registry, name, password=None, verbose=False): ...@@ -452,7 +484,7 @@ def applicant(registry, name, password=None, verbose=False):
def enable(registry, name, verbose=False): def enable(registry, name, verbose=False):
client = registry.get_client(name) client = registry.get_client(name)
if not client: if not client:
print "No such client." print("No such client.")
return return
client.update(status="Passive") client.update(status="Passive")
registry.save_client(client) registry.save_client(client)
...@@ -462,7 +494,7 @@ def enable(registry, name, verbose=False): ...@@ -462,7 +494,7 @@ def enable(registry, name, verbose=False):
def disable(registry, name, verbose=False): def disable(registry, name, verbose=False):
client = registry.get_client(name) client = registry.get_client(name)
if not client: if not client:
print "No such client." print("No such client.")
return return
client.update(status="Disabled") client.update(status="Disabled")
registry.save_client(client) registry.save_client(client)
...@@ -483,7 +515,7 @@ def request(registry, key, csr, verbose=False): ...@@ -483,7 +515,7 @@ def request(registry, key, csr, verbose=False):
"prompt=no\n" "prompt=no\n"
"\n" "\n"
"[req_distinguished_name]\n" "[req_distinguished_name]\n"
"commonName=dummy" "commonName=dummy".encode("ascii")
) )
openssl.stdin.close() openssl.stdin.close()
openssl.wait() openssl.wait()
...@@ -503,7 +535,7 @@ def gen_cert(registry, name, csr, cert, password, verbose=False): ...@@ -503,7 +535,7 @@ def gen_cert(registry, name, csr, cert, password, verbose=False):
print(newcert.as_pem()) print(newcert.as_pem())
with open(cert, "w") as f: with open(cert, "w") as f:
f.write(newcert.as_text()) f.write(newcert.as_text())
f.write(newcert.as_pem()) f.write(newcert.as_pem().decode("ascii"))
def get_args(): def get_args():
......
File moved
+-------------------------+ +-------------------------+
| Warden Server 3.0-beta2 | | Warden Server 3.0-beta3 |
+-------------------------+ +-------------------------+
Content Content
...@@ -46,6 +46,10 @@ B. Dependencies ...@@ -46,6 +46,10 @@ B. Dependencies
python-m2crypto 0.20+ python-m2crypto 0.20+
jsonschema 2.4+ jsonschema 2.4+
3. Database
MySQL | MariaDB >= 5.5
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
C. Installation C. Installation
...@@ -54,29 +58,32 @@ C. Installation ...@@ -54,29 +58,32 @@ C. Installation
# cd /opt # cd /opt
# tar xjf warden_server_3.0.tar.bz2 # tar xjf warden_server_3.0.tar.bz2
# ls # mv warden_server_3.0 warden_server
warden_server_3.0
* Create database and desired database users * Create database and desired database users
(We're using db "warden3" and user "warden@localhost" as an example.) (We're using db "warden3" and user "warden@localhost" as an example.)
# mysql -p # mysql -p
mysql> CREATE DATABASE warden3; > CREATE DATABASE warden3;
mysql> GRANT ALL ON warden3.* TO `warden`@`localhost`; > CREATE USER 'warden'@'localhost' IDENTIFIED BY 'example';
mysql> SET PASSWORD FOR 'warden'@'localhost' = PASSWORD('example'); > GRANT ALL ON warden3.* TO `warden`@`localhost`;
mysql> FLUSH PRIVILEGES; > FLUSH PRIVILEGES;
* Create necessary table structure * Create necessary table structure
mysql -p -u warden warden3 < warden_3.0.sql 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 * Enable mod_wsgi, mod_ssl, include Warden configuration
This depends heavily on your distribution and Apache configuration. This depends heavily on your distribution and Apache configuration.
Basically you need to create and include apache.conf: Basically you need to create and include apache.conf:
Include /opt/warden_server_3.0/apache.conf Include /opt/warden_server/apache.conf
or paste the contents into whichever Directory, Location or VirtualHost or paste the contents into whichever Directory, Location or VirtualHost
you dedicate for Warden. You can use apache22.conf.dist or you dedicate for Warden. You can use apache22.conf.dist or
......
+----------------------------------+
| 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.
-------------------------------------------------------------------------------
...@@ -6,13 +6,13 @@ SSLOptions +StdEnvVars +ExportCertData ...@@ -6,13 +6,13 @@ SSLOptions +StdEnvVars +ExportCertData
#SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL #SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile /opt/warden_server_3/etc/cert.pem SSLCertificateFile /opt/warden_server/etc/cert.pem
SSLCertificateKeyFile /opt/warden_server_3/etc/key.pem SSLCertificateKeyFile /opt/warden_server/etc/key.pem
SSLCACertificateFile /opt/warden_server_3/etc/tcs-ca-bundle.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 Order allow,deny
Allow from all Allow from all
</Directory> </Directory>
...@@ -6,12 +6,12 @@ SSLOptions +StdEnvVars +ExportCertData ...@@ -6,12 +6,12 @@ SSLOptions +StdEnvVars +ExportCertData
#SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL #SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile /opt/warden_server_3/etc/cert.pem SSLCertificateFile /opt/warden_server/etc/cert.pem
SSLCertificateKeyFile /opt/warden_server_3/etc/key.pem SSLCertificateKeyFile /opt/warden_server/etc/key.pem
SSLCACertificateFile /opt/warden_server_3/etc/tcs-ca-bundle.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
<DirectoryMatch /opt/warden_server_3/warden_server.wsgi> <DirectoryMatch /opt/warden_server/warden_server.wsgi>
Require all granted Require all granted
</DirectoryMatch> </DirectoryMatch>
This diff is collapsed.
...@@ -7,5 +7,10 @@ ...@@ -7,5 +7,10 @@
"send_events_limit": 500, "send_events_limit": 500,
"get_events_limit": 1000, "get_events_limit": 1000,
"description": "Warden 3 distribution config" "description": "Warden 3 distribution config"
},
"DB": {
"user": "warden",
"password": "EXAMPLE",
"dbname": "warden3"
} }
} }
\ No newline at end of file