1
0
mirror of https://github.com/laurivosandi/certidude synced 2024-11-16 18:06:44 +00:00

Merge branch 'master' of github.com:laurivosandi/certidude

This commit is contained in:
Lauri Võsandi 2017-01-26 13:00:21 +02:00
commit dc9e01b4ad
7 changed files with 100 additions and 52 deletions

View File

@ -44,7 +44,6 @@ class SessionResource(object):
@login_required @login_required
@event_source @event_source
def on_get(self, req, resp): def on_get(self, req, resp):
return dict( return dict(
user = dict( user = dict(
name=req.context.get("user").name, name=req.context.get("user").name,

View File

@ -12,6 +12,8 @@ KEYWORDS = (
(u"iPhone", u"iphone"), (u"iPhone", u"iphone"),
(u"iPad", u"ipad"), (u"iPad", u"ipad"),
(u"Ubuntu", u"ubuntu"), (u"Ubuntu", u"ubuntu"),
(u"Fedora", u"fedora"),
(u"Linux", u"linux"),
) )
class BundleResource(object): class BundleResource(object):
@ -29,8 +31,15 @@ class BundleResource(object):
hashlib.sha256(req.user_agent).hexdigest()[:8]) hashlib.sha256(req.user_agent).hexdigest()[:8])
logger.info(u"Signing bundle %s for %s", common_name, req.context.get("user")) logger.info(u"Signing bundle %s for %s", common_name, req.context.get("user"))
if config.BUNDLE_FORMAT == "p12":
resp.set_header("Content-Type", "application/x-pkcs12") resp.set_header("Content-Type", "application/x-pkcs12")
resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name.encode("ascii")) resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name.encode("ascii"))
resp.body, cert = authority.generate_pkcs12_bundle(common_name, resp.body, cert = authority.generate_pkcs12_bundle(common_name,
owner=req.context.get("user")) owner=req.context.get("user"))
elif config.BUNDLE_FORMAT == "ovpn":
resp.set_header("Content-Type", "application/x-openvpn")
resp.set_header("Content-Disposition", "attachment; filename=%s.ovpn" % common_name.encode("ascii"))
resp.body, cert = authority.generate_ovpn_bundle(common_name,
owner=req.context.get("user"))
else:
raise ValueError("Unknown bundle format %s" % config.BUNDLE_FORMAT)

View File

@ -122,9 +122,9 @@ def authenticate(optional=False):
import ldap import ldap
if not req.auth: if not req.auth:
resp.append_header("WWW-Authenticate", "Basic") raise falcon.HTTPUnauthorized("Unauthorized",
raise falcon.HTTPUnauthorized("Forbidden", "No authentication header provided",
"Please authenticate with %s domain account or supply UPN" % const.DOMAIN) ("Basic",))
if not req.auth.startswith("Basic "): if not req.auth.startswith("Basic "):
raise falcon.HTTPForbidden("Forbidden", "Bad header: %s" % req.auth) raise falcon.HTTPForbidden("Forbidden", "Bad header: %s" % req.auth)
@ -133,26 +133,35 @@ def authenticate(optional=False):
basic, token = req.auth.split(" ", 1) basic, token = req.auth.split(" ", 1)
user, passwd = b64decode(token).split(":", 1) user, passwd = b64decode(token).split(":", 1)
for server in config.LDAP_SERVERS: click.echo("Connecting to %s as %s" % (config.LDAP_AUTHENTICATION_URI, user))
click.echo("Connecting to %s as %s" % (server, user)) conn = ldap.initialize(config.LDAP_AUTHENTICATION_URI)
conn = ldap.initialize(server)
conn.set_option(ldap.OPT_REFERRALS, 0) conn.set_option(ldap.OPT_REFERRALS, 0)
if "@" not in user:
user = "%s@%s" % (user, const.DOMAIN)
logger.debug("Expanded username to %s", user)
try: try:
conn.simple_bind_s(user if "@" in user else "%s@%s" % (user, const.DOMAIN), passwd) conn.simple_bind_s(user, passwd)
except ldap.LDAPError, e: except ldap.STRONG_AUTH_REQUIRED:
resp.append_header("WWW-Authenticate", "Basic") logger.critical("LDAP server demands encryption, use ldaps:// instead of ldaps://")
raise
except ldap.SERVER_DOWN:
logger.critical("Failed to connect LDAP server at %s, are you sure LDAP server's CA certificate has been copied to this machine?",
config.LDAP_AUTHENTICATION_URI)
raise
except ldap.INVALID_CREDENTIALS:
logger.critical(u"LDAP bind authentication failed for user %s from %s", logger.critical(u"LDAP bind authentication failed for user %s from %s",
repr(user), req.context.get("remote_addr")) repr(user), req.context.get("remote_addr"))
raise falcon.HTTPUnauthorized("Forbidden", raise falcon.HTTPUnauthorized("Forbidden",
"Please authenticate with %s domain account or supply UPN" % const.DOMAIN) "Please authenticate with %s domain account or supply UPN" % const.DOMAIN,
("Basic",))
req.context["ldap_conn"] = conn req.context["ldap_conn"] = conn
break
else:
raise ValueError("No LDAP servers!")
req.context["user"] = User.objects.get(user) req.context["user"] = User.objects.get(user)
return func(resource, req, resp, *args, **kwargs) retval = func(resource, req, resp, *args, **kwargs)
conn.unbind_s()
return retval
def pam_authenticate(resource, req, resp, *args, **kwargs): def pam_authenticate(resource, req, resp, *args, **kwargs):

View File

@ -14,6 +14,7 @@ from cryptography.hazmat.primitives import hashes, serialization
from certidude import config, push, mailer, const from certidude import config, push, mailer, const
from certidude.wrappers import Certificate, Request from certidude.wrappers import Certificate, Request
from certidude import errors from certidude import errors
from jinja2 import Template
RE_HOSTNAME = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(@(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))?$" RE_HOSTNAME = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(@(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))?$"
@ -21,7 +22,6 @@ RE_HOSTNAME = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z
# https://jamielinux.com/docs/openssl-certificate-authority/ # https://jamielinux.com/docs/openssl-certificate-authority/
# http://pycopia.googlecode.com/svn/trunk/net/pycopia/ssl/certs.py # http://pycopia.googlecode.com/svn/trunk/net/pycopia/ssl/certs.py
# Cache CA certificate # Cache CA certificate
certificate = Certificate(open(config.AUTHORITY_CERTIFICATE_PATH)) certificate = Certificate(open(config.AUTHORITY_CERTIFICATE_PATH))
@ -185,6 +185,39 @@ def delete_request(common_name):
requests.delete(config.PUSH_PUBLISH % request.fingerprint(), requests.delete(config.PUSH_PUBLISH % request.fingerprint(),
headers={"User-Agent": "Certidude API"}) headers={"User-Agent": "Certidude API"})
def generate_ovpn_bundle(common_name, owner=None):
# Construct private key
click.echo("Generating 4096-bit RSA key...")
key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
backend=default_backend()
)
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
x509.NameAttribute(k, v) for k, v in (
(NameOID.COMMON_NAME, common_name),
(NameOID.GIVEN_NAME, owner and owner.given_name),
(NameOID.SURNAME, owner and owner.surname),
) if v
]))
# Sign CSR
cert = sign(Request(
csr.sign(key, hashes.SHA512(), default_backend()).public_bytes(serialization.Encoding.PEM)), overwrite=True)
bundle = Template(open(config.OPENVPN_BUNDLE_TEMPLATE).read()).render(
ca = certificate.dump(),
key = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
),
cert = cert.dump(),
crl=export_crl(),
)
return bundle, cert
def generate_pkcs12_bundle(common_name, key_size=4096, owner=None): def generate_pkcs12_bundle(common_name, key_size=4096, owner=None):
""" """

View File

@ -18,8 +18,10 @@ AUTHENTICATION_BACKENDS = set([j for j in
AUTHORIZATION_BACKEND = cp.get("authorization", "backend") # whitelist, ldap, posix AUTHORIZATION_BACKEND = cp.get("authorization", "backend") # whitelist, ldap, posix
ACCOUNTS_BACKEND = cp.get("accounts", "backend") # posix, ldap ACCOUNTS_BACKEND = cp.get("accounts", "backend") # posix, ldap
if ACCOUNTS_BACKEND == "ldap": LDAP_AUTHENTICATION_URI = cp.get("authentication", "ldap uri")
LDAP_GSSAPI_CRED_CACHE = cp.get("accounts", "ldap gssapi credential cache") LDAP_GSSAPI_CRED_CACHE = cp.get("accounts", "ldap gssapi credential cache")
LDAP_ACCOUNTS_URI = cp.get("accounts", "ldap uri")
LDAP_BASE = cp.get("accounts", "ldap base")
USER_SUBNETS = set([ipaddress.ip_network(j) for j in USER_SUBNETS = set([ipaddress.ip_network(j) for j in
cp.get("authorization", "user subnets").split(" ") if j]) cp.get("authorization", "user subnets").split(" ") if j])
@ -38,6 +40,9 @@ SIGNED_DIR = cp.get("authority", "signed dir")
REVOKED_DIR = cp.get("authority", "revoked dir") REVOKED_DIR = cp.get("authority", "revoked dir")
OUTBOX = cp.get("authority", "outbox") OUTBOX = cp.get("authority", "outbox")
BUNDLE_FORMAT = cp.get("authority", "bundle format")
OPENVPN_BUNDLE_TEMPLATE = cp.get("authority", "openvpn bundle template")
USER_CERTIFICATE_ENROLLMENT = { USER_CERTIFICATE_ENROLLMENT = {
"forbidden": False, "single allowed": True, "multiple allowed": True }[ "forbidden": False, "single allowed": True, "multiple allowed": True }[
cp.get("authority", "user certificate enrollment")] cp.get("authority", "user certificate enrollment")]
@ -78,17 +83,4 @@ elif "ldap" == AUTHORIZATION_BACKEND:
else: else:
raise NotImplementedError("Unknown authorization backend '%s'" % AUTHORIZATION_BACKEND) raise NotImplementedError("Unknown authorization backend '%s'" % AUTHORIZATION_BACKEND)
for line in open("/etc/ldap/ldap.conf"):
line = line.strip().lower()
if "#" in line:
line, _ = line.split("#", 1)
if not " " in line:
continue
key, value = line.split(" ", 1)
if key == "uri":
LDAP_SERVERS = set([j for j in value.split(" ") if j])
click.echo("LDAP servers: %s" % " ".join(LDAP_SERVERS))
elif key == "base":
LDAP_BASE = value
# TODO: Check if we don't have base or servers # TODO: Check if we don't have base or servers

View File

@ -9,11 +9,12 @@ backends = pam
;backends = ldap ;backends = ldap
;backends = kerberos ldap ;backends = kerberos ldap
;backends = kerberos pam ;backends = kerberos pam
ldap uri = ldaps://dc1.example.com
[accounts] [accounts]
# The accounts backend specifies how the user's given name, surname and e-mail # The accounts backend specifies how the user's given name, surname and e-mail
# address are looked up. In case of 'posix' basically 'getent passwd' is performed, # address are looked up. In case of 'posix' basically 'getent passwd' is performed,
# in case of 'ldap' a search is performed on LDAP server specified in /etc/ldap/ldap.conf # in case of 'ldap' a search is performed on LDAP server specified by ldap uri
# with Kerberos credential cache initialized at path specified by environment variable KRB5CCNAME # with Kerberos credential cache initialized at path specified by environment variable KRB5CCNAME
# If certidude setup authority was performed correctly the credential cache should be # If certidude setup authority was performed correctly the credential cache should be
# updated automatically by /etc/cron.hourly/certidude # updated automatically by /etc/cron.hourly/certidude
@ -21,6 +22,8 @@ backends = pam
backend = posix backend = posix
;backend = ldap ;backend = ldap
ldap gssapi credential cache = /run/certidude/krb5cc ldap gssapi credential cache = /run/certidude/krb5cc
ldap uri = ldap://dc1.example.com
ldap base = {% if base %}{{ base }}{% else %}dc=example,dc=com{% endif %}
[authorization] [authorization]
# The authorization backend specifies how the users are authorized. # The authorization backend specifies how the users are authorized.
@ -92,3 +95,8 @@ revoked dir = {{ directory }}/revoked/
expired dir = {{ directory }}/expired/ expired dir = {{ directory }}/expired/
outbox = {{ outbox }} outbox = {{ outbox }}
bundle format = p12
;bundle format = ovpn
openvpn bundle template = /etc/certidude/template.ovpn

View File

@ -70,17 +70,15 @@ class DirectoryConnection(object):
raise ValueError("Ticket cache at %s not initialized, unable to " raise ValueError("Ticket cache at %s not initialized, unable to "
"authenticate with computer account against LDAP server!" % config.LDAP_GSSAPI_CRED_CACHE) "authenticate with computer account against LDAP server!" % config.LDAP_GSSAPI_CRED_CACHE)
os.environ["KRB5CCNAME"] = config.LDAP_GSSAPI_CRED_CACHE os.environ["KRB5CCNAME"] = config.LDAP_GSSAPI_CRED_CACHE
for server in config.LDAP_SERVERS: self.conn = ldap.initialize(config.LDAP_ACCOUNTS_URI)
self.conn = ldap.initialize(server)
self.conn.set_option(ldap.OPT_REFERRALS, 0) self.conn.set_option(ldap.OPT_REFERRALS, 0)
click.echo("Connecing to %s using Kerberos ticket cache from %s" % click.echo("Connecing to %s using Kerberos ticket cache from %s" %
(server, config.LDAP_GSSAPI_CRED_CACHE)) (config.LDAP_ACCOUNTS_URI, config.LDAP_GSSAPI_CRED_CACHE))
self.conn.sasl_interactive_bind_s('', ldap.sasl.gssapi()) self.conn.sasl_interactive_bind_s('', ldap.sasl.gssapi())
return self.conn return self.conn
raise ValueError("No LDAP servers specified!")
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
self.conn.unbind_s self.conn.unbind_s()
class ActiveDirectoryUserManager(object): class ActiveDirectoryUserManager(object):