mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 09:29:13 +00:00 
			
		
		
		
	Add preliminary PAM authentication backend
This commit is contained in:
		| @@ -7,6 +7,7 @@ import logging | |||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import socket | import socket | ||||||
|  | from certidude import config | ||||||
|  |  | ||||||
| logger = logging.getLogger("api") | logger = logging.getLogger("api") | ||||||
|  |  | ||||||
| @@ -20,20 +21,47 @@ logger = logging.getLogger("api") | |||||||
|  |  | ||||||
| FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3] | FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3] | ||||||
|  |  | ||||||
| if not os.getenv("KRB5_KTNAME"): | if config.AUTHENTICATION_BACKEND == "kerberos": | ||||||
|     click.echo("Kerberos keytab not specified, set environment variable 'KRB5_KTNAME'", err=True) |     if not os.getenv("KRB5_KTNAME"): | ||||||
|     exit(250) |         click.echo("Kerberos keytab not specified, set environment variable 'KRB5_KTNAME'", err=True) | ||||||
|  |         exit(250) | ||||||
|  |  | ||||||
| try: |     try: | ||||||
|     principal = kerberos.getServerPrincipalDetails("HTTP", FQDN) |         principal = kerberos.getServerPrincipalDetails("HTTP", FQDN) | ||||||
| except kerberos.KrbError as exc: |     except kerberos.KrbError as exc: | ||||||
|     click.echo("Failed to initialize Kerberos, reason: %s" % exc, err=True) |         click.echo("Failed to initialize Kerberos, reason: %s" % exc, err=True) | ||||||
|     exit(249) |         exit(249) | ||||||
|  |     else: | ||||||
|  |         click.echo("Kerberos enabled, service principal is HTTP/%s" % FQDN) | ||||||
| else: | else: | ||||||
|     click.echo("Kerberos enabled, service principal is HTTP/%s" % FQDN) |     NotImplemented | ||||||
|  |  | ||||||
| def login_required(func): | def login_required(func): | ||||||
|     def authenticate(resource, req, resp, *args, **kwargs): |     def pam_authenticate(resource, req, resp, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |         Authenticate against PAM with WWW Basic Auth credentials | ||||||
|  |         """ | ||||||
|  |         authorization = req.get_header("Authorization") | ||||||
|  |         if not authorization: | ||||||
|  |             resp.append_header("WWW-Authenticate", "Basic") | ||||||
|  |             raise falcon.HTTPUnauthorized("Forbidden", "Please authenticate") | ||||||
|  |  | ||||||
|  |         if not authorization.startswith("Basic "): | ||||||
|  |             raise falcon.HTTPForbidden("Forbidden", "Bad header: %s" % authorization) | ||||||
|  |  | ||||||
|  |         from base64 import b64decode | ||||||
|  |         basic, token = authorization.split(" ", 1) | ||||||
|  |         user, passwd = b64decode(token).split(":", 1) | ||||||
|  |  | ||||||
|  |         import simplepam | ||||||
|  |         if not simplepam.authenticate(user, passwd, "sshd"): | ||||||
|  |             raise falcon.HTTPForbidden("Forbidden", "Invalid password") | ||||||
|  |  | ||||||
|  |         req.context["user"] = user | ||||||
|  |         return func(resource, req, resp, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def kerberos_authenticate(resource, req, resp, *args, **kwargs): | ||||||
|         authorization = req.get_header("Authorization") |         authorization = req.get_header("Authorization") | ||||||
|  |  | ||||||
|         if not authorization: |         if not authorization: | ||||||
| @@ -82,7 +110,12 @@ def login_required(func): | |||||||
|             # TODO: logger.error |             # TODO: logger.error | ||||||
|             raise falcon.HTTPForbidden("Forbidden", "Tried GSSAPI") |             raise falcon.HTTPForbidden("Forbidden", "Tried GSSAPI") | ||||||
|  |  | ||||||
|     return authenticate |     if config.AUTHENTICATION_BACKEND == "kerberos": | ||||||
|  |         return kerberos_authenticate | ||||||
|  |     elif config.AUTHENTICATION_BACKEND == "pam": | ||||||
|  |         return pam_authenticate | ||||||
|  |     else: | ||||||
|  |         NotImplemented | ||||||
|  |  | ||||||
|  |  | ||||||
| def authorize_admin(func): | def authorize_admin(func): | ||||||
|   | |||||||
| @@ -1006,6 +1006,7 @@ def certidude_serve(user, port, listen, enable_signature): | |||||||
|     from wsgiref.simple_server import make_server, WSGIServer |     from wsgiref.simple_server import make_server, WSGIServer | ||||||
|     from socketserver import ThreadingMixIn |     from socketserver import ThreadingMixIn | ||||||
|     from certidude.api import certidude_app, StaticResource |     from certidude.api import certidude_app, StaticResource | ||||||
|  |     from certidude import config | ||||||
|  |  | ||||||
|     class ThreadingWSGIServer(ThreadingMixIn, WSGIServer): |     class ThreadingWSGIServer(ThreadingMixIn, WSGIServer): | ||||||
|         pass |         pass | ||||||
| @@ -1024,13 +1025,26 @@ def certidude_serve(user, port, listen, enable_signature): | |||||||
|         from jinja2.debug import make_traceback as _make_traceback |         from jinja2.debug import make_traceback as _make_traceback | ||||||
|         "".encode("charmap") |         "".encode("charmap") | ||||||
|  |  | ||||||
|  |         if config.AUTHENTICATION_BACKEND == "pam": | ||||||
|  |             # PAM needs access to /etc/shadow | ||||||
|  |             import grp | ||||||
|  |             name, passwd, gid, mem = grp.getgrnam("shadow") | ||||||
|  |             click.echo("Adding current user to shadow group due to PAM authentication backend") | ||||||
|  |             os.setgroups([gid]) | ||||||
|  |         else: | ||||||
|  |             os.setgroups([]) | ||||||
|  |  | ||||||
|         _, _, uid, gid, gecos, root, shell = pwd.getpwnam(user) |         _, _, uid, gid, gecos, root, shell = pwd.getpwnam(user) | ||||||
|         if uid == 0: |         if uid == 0: | ||||||
|             click.echo("Please specify unprivileged user") |             click.echo("Please specify unprivileged user") | ||||||
|             exit(254) |             exit(254) | ||||||
|         click.echo("Switching to user %s (uid=%d, gid=%d)" % (user, uid, gid)) |  | ||||||
|         os.setgid(gid) |         os.setgid(gid) | ||||||
|         os.setuid(uid) |         os.setuid(uid) | ||||||
|  |  | ||||||
|  |         click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" % | ||||||
|  |             (user, uid, gid, ", ".join([str(j) for j in os.getgroups()]))) | ||||||
|  |  | ||||||
|         os.umask(0o007) |         os.umask(0o007) | ||||||
|     elif os.getuid() == 0: |     elif os.getuid() == 0: | ||||||
|         click.echo("Warning: running as root, this is not recommended!") |         click.echo("Warning: running as root, this is not recommended!") | ||||||
|   | |||||||
| @@ -14,6 +14,9 @@ FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket. | |||||||
| cp = configparser.ConfigParser() | cp = configparser.ConfigParser() | ||||||
| cp.readfp(codecs.open("/etc/certidude/server.conf", "r", "utf8")) | cp.readfp(codecs.open("/etc/certidude/server.conf", "r", "utf8")) | ||||||
|  |  | ||||||
|  | AUTHENTICATION_BACKEND = cp.get("authentication", "backend") # kerberos, pam | ||||||
|  | AUTHORIZATION_BACKEND = cp.get("authorization", "backend") # whitelist, ldap, pam | ||||||
|  |  | ||||||
| ADMIN_USERS = set([j for j in  cp.get("authorization", "admin_users").split(" ") if j]) | ADMIN_USERS = set([j for j in  cp.get("authorization", "admin_users").split(" ") if j]) | ||||||
| ADMIN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "admin_subnets").split(" ") if j]) | ADMIN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "admin_subnets").split(" ") if j]) | ||||||
| AUTOSIGN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "autosign_subnets").split(" ") if j]) | AUTOSIGN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "autosign_subnets").split(" ") if j]) | ||||||
|   | |||||||
| @@ -18,4 +18,5 @@ pyOpenSSL==0.15.1 | |||||||
| python-mimeparse==0.1.4 | python-mimeparse==0.1.4 | ||||||
| requests==2.2.1 | requests==2.2.1 | ||||||
| setproctitle==1.1.9 | setproctitle==1.1.9 | ||||||
|  | simplepam==0.1.5 | ||||||
| six==1.9.0 | six==1.9.0 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user