enable authentication via SSL client certificates in the web interface

This commit is contained in:
Michael Bonnet 2007-03-16 20:36:10 -04:00
parent 108e4da8c8
commit 2cb2f3684d
7 changed files with 117 additions and 44 deletions

View file

@ -15,6 +15,8 @@ Alias /koji-hub "/usr/share/koji-hub/XMLRPC"
PythonOption AuthPrincipal kojihub@EXAMPLE.COM
PythonOption AuthKeytab /etc/koji.keytab
PythonOption ProxyPrincipals kojihub@EXAMPLE.COM
# separate multiple DNs with |
PythonOption ProxyDNs "/C=US/ST=Massachusetts/O=Example Org/OU=Example User/CN=example/emailAddress=example@example.com"
PythonOption LoginCreatesUser On
PythonOption KojiWebURL http://kojiweb.example.com/koji
#format string for host principals (%s = hostname)
@ -32,5 +34,5 @@ Alias /koji-hub "/usr/share/koji-hub/XMLRPC"
# SSLVerifyClient require
# SSLVerifyDepth 10
# SSLUserName SSL_CLIENT_S_DN_CN
# SSLOptions +StdEnvVars +ExportCertData
# SSLOptions +StdEnvVars
# </Location>

View file

@ -109,11 +109,13 @@ rm -rf $RPM_BUILD_ROOT
%{_sbindir}/kojira
%{_initrddir}/kojira
%config(noreplace) %{_sysconfdir}/sysconfig/kojira
%{_sysconfdir}/kojira
%config(noreplace) %{_sysconfdir}/kojira/kojira.conf
%files web
%defattr(-,root,root)
%{_datadir}/koji-web
%{_sysconfdir}/kojiweb
%config(noreplace) /etc/httpd/conf.d/kojiweb.conf
%files builder
@ -121,6 +123,7 @@ rm -rf $RPM_BUILD_ROOT
%{_sbindir}/kojid
%{_initrddir}/kojid
%config(noreplace) %{_sysconfdir}/sysconfig/kojid
%{_sysconfdir}/kojid
%config(noreplace) %{_sysconfdir}/kojid/kojid.conf
%attr(-,kojibuilder,kojibuilder) /etc/mock/koji

View file

@ -1178,17 +1178,17 @@ class ClientSession(object):
return 'host/%s@%s' % (servername, domain)
def ssl_login(self, cert, ca, serverca):
def ssl_login(self, cert, ca, serverca, proxyuser=None):
if not self.baseurl.startswith('https:'):
raise AuthError, '%s is not a SSL server URL, and you have configured SSL authentication' % self.baseurl
self.baseurl = self.baseurl.replace('http:', 'https:')
certs = {}
certs['key_and_cert'] = cert
certs['ca_cert'] = ca
certs['peer_ca_cert'] = serverca
# only use a timeout during login
self.proxy = ssl.XMLRPCServerProxy.PlgXMLRPCServerProxy(self.baseurl, certs, timeout=60)
sinfo = self.callMethod('sslLogin')
self.proxy = ssl.XMLRPCServerProxy.PlgXMLRPCServerProxy(self.baseurl, certs, timeout=60, **self.proxyOpts)
sinfo = self.callMethod('sslLogin', proxyuser)
if not sinfo:
return False
self.proxyClass = ssl.XMLRPCServerProxy.PlgXMLRPCServerProxy

View file

@ -276,7 +276,7 @@ class Session(object):
login_principal = cprinc.name
user_id = self.getUserIdFromKerberos(login_principal)
if not user_id:
if context.opts.get('LoginCreatesUser', '').lower() in ('yes', 'on', 'true', '1'):
if context.opts['LoginCreatesUser'].lower() in ('yes', 'on', 'true', '1'):
user_id = self.createUserFromKerberos(login_principal)
else:
raise koji.AuthError, 'Unknown Kerberos principal: %s' % login_principal
@ -320,7 +320,7 @@ class Session(object):
return (local_ip, local_port, remote_ip, remote_port)
def sslLogin(self):
def sslLogin(self, proxyuser=None):
if self.logged_in:
raise koji.AuthError, "Already logged in"
@ -328,28 +328,40 @@ class Session(object):
context.req.add_common_vars()
env = context.req.subprocess_env
if not (env.has_key('HTTPS') and
env['HTTPS'] == 'on'):
if env.get('HTTPS') != 'on':
raise koji.AuthError, 'cannot call sslLogin() via a non-https connection'
subject = context.req.user
if not subject:
raise koji.AuthError, 'could not determine the subject of the client certificate'
if env.get('SSL_CLIENT_VERIFY') != 'SUCCESS':
raise koji.AuthError, 'could not verify client: %s' % env.get('SSL_CLIENT_VERIFY')
# assume that the certificate subject is their Koji username
client_name = env.get('SSL_CLIENT_S_DN_CN')
if not client_name:
raise koji.AuthError, 'unable to get user information from client certificate'
if proxyuser:
client_dn = env.get('SSL_CLIENT_S_DN')
proxy_dns = [dn.strip() for dn in context.opts['ProxyDNs'].split('|')]
if client_dn in proxy_dns:
# the SSL-authenticated user authorized to login other users
username = proxyuser
else:
raise koji.AuthError, '%s is not authorized to login other users' % client_dn
else:
username = client_name
cursor = context.cnx.cursor()
query = """SELECT id, status FROM users
WHERE name = %(subject)s"""
WHERE name = %(username)s"""
cursor.execute(query, locals())
result = cursor.fetchone()
if result:
user_id, status = result
else:
if context.opts.get('LoginCreatesUser', '').lower() in ('yes', 'on', 'true', '1'):
user_id = self.createUser(subject, koji.USERTYPES['NORMAL'], '')
if context.opts['LoginCreatesUser'].lower() in ('yes', 'on', 'true', '1'):
user_id = self.createUser(username, koji.USERTYPES['NORMAL'], '')
status = None
else:
raise koji.AuthError, 'Unknown user: %s' % subject
raise koji.AuthError, 'Unknown user: %s' % username
hostip = context.req.connection.remote_ip
if hostip == '127.0.0.1':

View file

@ -14,3 +14,5 @@ install:
mkdir -p $(DESTDIR)/etc/httpd/conf.d
install -p -m 644 kojiweb.conf $(DESTDIR)/etc/httpd/conf.d/kojiweb.conf
mkdir -p $(DESTDIR)/etc/kojiweb

View file

@ -13,6 +13,9 @@ Alias /koji "/usr/share/koji-web/scripts/"
PythonOption WebPrincipal koji/web@EXAMPLE.COM
PythonOption WebKeytab /etc/httpd.keytab
PythonOption WebCCache /var/tmp/kojiweb.ccache
PythonOption WebCert /etc/kojiweb/kojiweb.crt
PythonOption ClientCA /etc/kojiweb/clientca.crt
PythonOption KojiHubCA /etc/kojiweb/kojihubca.crt
PythonOption LoginTimeout 72
# This must be changed before deployment
PythonOption Secret CHANGE_ME
@ -21,18 +24,25 @@ Alias /koji "/usr/share/koji-web/scripts/"
PythonAutoReload Off
</Directory>
# Authentication settings
# uncomment this to enable authentication via Kerberos
# <Location /koji/login>
# AuthType Kerberos
# AuthName "Koji Web UI"
# KrbMethodNegotiate on
# KrbMethodK5Passwd off
# KrbServiceName HTTP
# KrbAuthRealm EXAMPLE.COM
# Krb5Keytab /etc/httpd.keytab
# KrbSaveCredentials off
# Require valid-user
# ErrorDocument 401 /koji-static/errors/unauthorized.html
# </Location>
# uncomment this to enable authentication via SSL client certificates
<Location /koji/login>
AuthType Kerberos
AuthName "Koji Web UI"
KrbMethodNegotiate on
KrbMethodK5Passwd off
KrbServiceName HTTP
KrbAuthRealm EXAMPLE.COM
Krb5Keytab /etc/httpd.keytab
KrbSaveCredentials off
Require valid-user
ErrorDocument 401 /koji-static/errors/unauthorized.html
SSLVerifyClient require
SSLVerifyDepth 10
SSLOptions +StdEnvVars
</Location>
Alias /koji-static/ "/usr/share/koji-web/static/"

View file

@ -46,13 +46,30 @@ def _krbLogin(req, session, principal):
return session.krb_login(principal=wprinc, keytab=keytab,
ccache=ccache, proxyuser=principal)
def _sslLogin(req, session, username):
options = req.get_options()
client_cert = options['WebCert']
client_ca = options['ClientCA']
server_ca = options['KojiHubCA']
return session.ssl_login(client_cert, client_ca, server_ca,
proxyuser=username)
def _assertLogin(req):
if not (hasattr(req, 'currentPrincipal') and
session = req._session
options = req.get_options()
if not (hasattr(req, 'currentLogin') and
hasattr(req, 'currentUser')):
raise StandardError, '_getServer() must be called before _assertLogin()'
elif req.currentPrincipal and req.currentUser:
if not _krbLogin(req, req._session, req.currentPrincipal):
raise koji.AuthError, 'could not login using principal: %s' % req.currentPrincipal
elif req.currentLogin and req.currentUser:
if options.get('WebCert'):
if not _sslLogin(req, session, req.currentLogin):
raise koji.AuthError, 'could not login %s via SSL' % req.currentLogin
elif options.get('WebPrincipal'):
if not _krbLogin(req, req._session, req.currentLogin):
raise koji.AuthError, 'could not login using principal: %s' % req.currentLogin
else:
raise koji.AuthError, 'KojiWeb is incorrectly configured for authentication, contact the system administrator'
else:
mod_python.util.redirect(req, 'login')
assert False
@ -81,12 +98,12 @@ def _getServer(req):
serverURL = req.get_options()['KojiHubURL']
session = koji.ClientSession(serverURL)
req.currentPrincipal = _getUserCookie(req)
if req.currentPrincipal:
req.currentUser = session.getUser(req.currentPrincipal)
req.currentLogin = _getUserCookie(req)
if req.currentLogin:
req.currentUser = session.getUser(req.currentLogin)
if not req.currentUser:
raise koji.AuthError, 'could not get user for principal: %s' % req.currentPrincipal
_setUserCookie(req, req.currentPrincipal)
raise koji.AuthError, 'could not get user for principal: %s' % req.currentLogin
_setUserCookie(req, req.currentLogin)
else:
req.currentUser = None
@ -103,16 +120,43 @@ def _redirectBack(req, page):
def login(req, page=None):
session = _getServer(req)
options = req.get_options()
principal = req.user
if not principal:
raise koji.AuthError, 'configuration error: an external module should have performed authentication before presenting this page'
# try SSL first, fall back to Kerberos
if options.get('WebCert'):
req.add_common_vars()
env = req.subprocess_env
if not env.get('HTTPS') == 'on':
https_url = options['KojiWebURL'].replace('http://', 'https://') + '/login'
if req.args:
https_url += '?' + req.args
mod_python.util.redirect(req, https_url)
return
# login via Kerberos to verify credentials and create the user if it doesn't exist
if not _krbLogin(req, session, principal):
raise koji.AuthError, 'could not login using principal: %s' % principal
if env.get('SSL_CLIENT_VERIFY') != 'SUCCESS':
raise koji.AuthError, 'could not verify client: %s' % env.get('SSL_CLIENT_VERIFY')
_setUserCookie(req, principal)
# use the subject's common name as their username
username = env.get('SSL_CLIENT_S_DN_CN')
if not username:
raise koji.AuthError, 'unable to get user information from client certificate'
if not _sslLogin(req, session, username):
raise koji.AuthError, 'could not login %s using SSL certificates' % username
elif options.get('WebPrincipal'):
principal = req.user
if not principal:
raise koji.AuthError, 'configuration error: mod_auth_kerb should have performed authentication before presenting this page'
if not _krbLogin(req, session, principal):
raise koji.AuthError, 'could not login using principal: %s' % principal
username = principal
else:
raise koji.AuthError, 'KojiWeb is incorrectly configured for authentication, contact the system administrator'
_setUserCookie(req, username)
_redirectBack(req, page)