Add totpcgi role.

This commit is contained in:
Kevin Fenzi 2014-12-06 23:57:44 +00:00
parent 07e24f8448
commit 6af7a7b8a7
20 changed files with 1174 additions and 0 deletions

View file

@ -41,6 +41,7 @@
- rsyncd
- fas_server
- sudo
- totpcgi
tasks:
- include: "{{ tasks }}/yumrepos.yml"

View file

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Fedora Project Google Authenticator provisioning - Error</title>
<link href="${css_root}provisioning.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="mainwindow" class="status">
<div id="title">Fedora Project - Error</div>
<div id="mainarea">
<p class="error">
$errormsg
</p>
<p>
You can <a href="$action_url">try again</a> or you can contact
the Fedora Infrastructure team at <a href="mailto:admin@fedoraproject.org">admin@fedoraproject.org</a>.
</p>
</div>
<div id="copyright">
&copy; 2012 Red Hat, Inc. and others.
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,49 @@
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Fedora Project Google Authenticator provisioning</title>
<link href="${css_root}provisioning.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="mainwindow" class="login">
<div id="title">Fedora Project</div>
<div id="mainarea">
<div class="message">
Please log in to obtain your new Google Authenticator
secret.
</div>
<form id="login_form" name="login_form" action="$action_url" method="post">
<table border="0" align="center">
<tr>
<td class="fieldname">
<label for="username">Username:</label>
</td>
<td>
<input type="text" id="username" name="username" class="inputfield"/>
</td>
</tr>
<tr>
<td class="fieldname">
<label for="pincode">Pincode:</label>
</td>
<td>
<input type="password" id="pincode" name="pincode" class="inputfield"/>
</td>
</tr>
<tr>
<td class="fieldname"></td>
<td>
<input type="submit" value="Submit &raquo;"/>
</td>
</tr>
</table>
</form>
</div>
<div id="copyright">
&copy; 2012 Red Hat, Inc. and others.
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,53 @@
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Fedora Project Google Authenticator Provisioning</title>
<link href="${css_root}provisioning.css" rel="stylesheet" type="text/css" />
<link href="${css_root}provisioning-print.css" rel="stylesheet" media="print" type="text/css" />
</head>
<body>
<div id="mainwindow" class="totp">
<div id="title">Fedora Project Google Authenticator Provisioning</div>
<div id="mainarea">
<div id="qrcode">
$qrcode_embed
</div>
<div id="explanation">
<p>
Your new Google Authenticator token has been issued.
To import this token into your device, simply go to your
Google Authenticator app, select the option to add an
account, and then select "Scan Barcode". Point the camera
at the QR Barcode displayed next to this message. Google
Authenticator will then import your new token into the
device. It should be ready to use immediately.
</p>
</div>
<div id="scratch_tokens">
<p>
If the administrator permitted the use of scratch tokens,
you should see them listed below. If you lose access to
your Google Authenticator device, you should be able to
use one of these tokens to gain emergency access to your
account. Please write them down.
</p>
<div id="scratch_tokens_list">
$scratch_tokens
</div>
</div>
<div id="support_requests">
<p>
If you require any help with your Google Authenticator
token or experience any difficulty importing it into
your mobile device, please email
<a href="mailto:admin@fedoraproject.org">admin@fedoraproject.org</a>.
</div>
</div>
<div id="copyright">
&copy; 2012 Red Hat, Inc. and others.
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,189 @@
#!/usr/bin/python -tt
##
# Copyright (C) 2012 by Konstantin Ryabitsev and contributors
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
import os
import re
import sys
import cgi
import syslog
import logging
import urllib2
import cgitb
cgitb.enable()
import totpcgi
import totpcgi.backends
if len(sys.argv) > 1:
# blindly assume it's the config file
config_file = sys.argv[1]
else:
config_file = '/etc/totpcgi/totpcgi.conf'
import ConfigParser
from fedora.client import AuthError
from fedora.client.fasproxy import FasProxyClient
config = ConfigParser.RawConfigParser()
config.read(config_file)
require_pincode = config.getboolean('main', 'require_pincode')
success_string = config.get('main', 'success_string')
fas_url = config.get('main', 'fas_url')
try:
fas = FasProxyClient(fas_url)
except Exception, e:
syslog.syslog(syslog.LOG_CRIT, 'Problem connecting to fas %s' % e)
sys.exit(1)
backends = totpcgi.backends.Backends()
try:
backends.load_from_config(config)
except totpcgi.backends.BackendNotSupported, ex:
syslog.syslog(syslog.LOG_CRIT,
'Backend engine not supported: %s' % ex)
sys.exit(1)
syslog.openlog('totp.cgi', syslog.LOG_PID, syslog.LOG_AUTH)
### Begin custom Fedora Functions
def google_auth_fas_pincode_verify(user, pincode):
if not fas.verify_password(user, pincode):
raise totpcgi.UserPincodeError('User Password Error')
backends.pincode_backend.verify_user_pincode = google_auth_fas_pincode_verify
client_id = '1'
def parse_token(token):
if token > 44:
otp = token[-44:]
if otp.startswith('ccccc'):
return token[:-44], otp
# Not a password + yubikey
return False
class YubikeyAuthenticator(object):
auth_regex = re.compile('^status=(?P<rc>\w{2})')
def __init__(self, require_pincode=False):
self.require_pincode = require_pincode
def verify_user_token(self, user, token):
# Parse the token apart into a password and token
password, otp = parse_token(token)
# Verify token against yubikey server
server_prefix = 'http://localhost/yk-val/verify?id='
server_url = server_prefix + client_id + "&otp=" + otp
fh = urllib2.urlopen(server_url)
for line in fh:
match = self.auth_regex.search(line.strip('\n'))
if match:
if match.group('rc') == 'OK':
# Yubikey token is valid
break
raise totpcgi.VerifyFailed(line.split('=')[1])
else:
raise totpcgi.VerifyFailed('yk-val returned malformed response')
# Verify that the yubikey token belongs to the user
# As a side effect, verify the password is good as well
# if the user+password are wrong, this will raise a fedora.client.AuthError
try:
response = fas.send_request('/config/list/%s/yubikey' % user,
auth_params={'username': user, 'password': password})
except AuthError, e:
raise totpcgi.VerifyFailed('User Password Error: %s' % e)
if not response[1].configs.prefix or not response[1].configs.enabled:
raise totpcgi.VerifyFailed('Yubikey OTP unconfigured')
elif len(response[1].configs.prefix) != 12:
raise totpcgi.VerifyFailed('Invalid Yubikey OTP prefix')
if not otp.startswith(response[1].configs.prefix):
raise totpcgi.VerifyFailed('Unauthorized/Invalid OTP')
# Okay, everything passed
return 'Valid yubikey returned'
### End of custom Fedora Functions
def bad_request(why):
output = 'ERR\n' + why + '\n'
sys.stdout.write('Status: 400 BAD REQUEST\n')
sys.stdout.write('Content-type: text/plain\n')
sys.stdout.write('Content-Length: %s\n' % len(output))
sys.stdout.write('\n')
sys.stdout.write(output)
sys.exit(0)
def cgimain():
form = cgi.FieldStorage()
must_keys = ('user', 'token', 'mode')
for must_key in must_keys:
if must_key not in form:
bad_request("Missing field: %s" % must_key)
user = form.getfirst('user')
token = form.getfirst('token')
mode = form.getfirst('mode')
remote_host = os.environ['REMOTE_ADDR']
if mode != 'PAM_SM_AUTH':
bad_request('We only support PAM_SM_AUTH')
if parse_token(token):
ga = YubikeyAuthenticator(require_pincode)
else:
# totp/googleauth
ga = totpcgi.GoogleAuthenticator(backends, require_pincode)
try:
status = ga.verify_user_token(user, token)
except Exception, ex:
syslog.syslog(syslog.LOG_NOTICE,
'Failure: user=%s, mode=%s, host=%s, message=%s' % (user, mode,
remote_host, str(ex)))
bad_request(str(ex))
syslog.syslog(syslog.LOG_NOTICE,
'Success: user=%s, mode=%s, host=%s, message=%s' % (user, mode,
remote_host, status))
sys.stdout.write('Status: 200 OK\n')
sys.stdout.write('Content-type: text/plain\n')
sys.stdout.write('Content-Length: %s\n' % len(success_string))
sys.stdout.write('\n')
sys.stdout.write(success_string)
if __name__ == '__main__':
cgimain()

View file

@ -0,0 +1,21 @@
pam_url:
{
settings:
{
url = "https://fas-all.phx2.fedoraproject.org:8443/"; # URI to fetch
returncode = "OK"; # The remote script/cgi should return a 200 http code and this string as its only results
userfield = "user"; # userfield name to send
passwdfield = "token"; # passwdfield name to send
extradata = "&do=login"; # extradata to send
prompt = "Password+Token: "; # password prompt
};
ssl:
{
verify_peer = true; # Should we verify SSL ?
verify_host = true; # Should we verify the CN in the SSL cert?
client_cert = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side certificate
client_key = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side key (can be same file as above if a single cert)
ca_cert = "/etc/pki/tls/private/totpcgi-ca.cert";
};
};

View file

@ -0,0 +1,21 @@
pam_url:
{
settings:
{
url = "https://fas-all.phx2.fedoraproject.org:8443/"; # URI to fetch
returncode = "OK"; # The remote script/cgi should return a 200 http code and this string as its only results
userfield = "user"; # userfield name to send
passwdfield = "token"; # passwdfield name to send
extradata = "&do=login"; # extradata to send
prompt = "Password+Token: "; # password prompt
};
ssl:
{
verify_peer = true; # Should we verify SSL ?
verify_host = true; # Should we verify the CN in the SSL cert?
client_cert = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side certificate
client_key = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side key (can be same file as above if a single cert)
ca_cert = "/etc/pki/tls/private/totpcgi-ca.cert";
};
};

View file

@ -0,0 +1,21 @@
pam_url:
{
settings:
{
url = "https://fas-all.stg.phx2.fedoraproject.org:8443/"; # URI to fetch
returncode = "OK"; # The remote script/cgi should return a 200 http code and this string as its only results
userfield = "user"; # userfield name to send
passwdfield = "token"; # passwdfield name to send
extradata = "&do=login"; # extradata to send
prompt = "Password+Token: "; # password prompt
};
ssl:
{
verify_peer = true; # Should we verify SSL ?
verify_host = true; # Should we verify the CN in the SSL cert?
client_cert = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side certificate
client_key = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side key (can be same file as above if a single cert)
ca_cert = "/etc/pki/tls/private/totpcgi-ca.cert";
};
};

View file

@ -0,0 +1,21 @@
pam_url:
{
settings:
{
url = "https://fas-all.vpn.fedoraproject.org:8443/"; # URI to fetch
returncode = "OK"; # The remote script/cgi should return a 200 http code and this string as its only results
userfield = "user"; # userfield name to send
passwdfield = "token"; # passwdfield name to send
extradata = "&do=login"; # extradata to send
prompt = "Password+Token: "; # password prompt
};
ssl:
{
verify_peer = true; # Should we verify SSL ?
verify_host = true; # Should we verify the CN in the SSL cert?
client_cert = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side certificate
client_key = "/etc/pki/tls/private/totpcgi.pem"; # file to use as client-side key (can be same file as above if a single cert)
ca_cert = "/etc/pki/tls/private/totpcgi-ca.cert";
};
};

View file

@ -0,0 +1,265 @@
#!/usr/bin/python -tt
##
# Copyright (C) 2012 by Konstantin Ryabitsev and contributors
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
import os
import sys
import cgi
import syslog
import logging
import cgitb
cgitb.enable()
import totpcgi
import totpcgi.backends
import totpcgi.utils
import qrcode
from qrcode.image import svg
from StringIO import StringIO
from string import Template
if len(sys.argv) > 1:
# blindly assume it's the config file
config_file = sys.argv[1]
else:
config_file = '/etc/totpcgi/provisioning.conf'
import ConfigParser
config = ConfigParser.RawConfigParser()
config.read(config_file)
backends = totpcgi.backends.Backends()
try:
backends.load_from_config(config)
except totpcgi.backends.BackendNotSupported, ex:
syslog.syslog(syslog.LOG_CRIT,
'Backend engine not supported: %s' % ex)
sys.exit(1)
syslog.openlog('provisioning.cgi', syslog.LOG_PID, syslog.LOG_AUTH)
def bad_request(config, why):
templates_dir = config.get('secret', 'templates_dir')
fh = open(os.path.join(templates_dir, 'error.html'))
tpt = Template(fh.read())
fh.close()
vals = {
'action_url': config.get('secret', 'action_url'),
'css_root': config.get('secret', 'css_root'),
'errormsg': cgi.escape(why)
}
out = tpt.safe_substitute(vals)
sys.stdout.write('Status: 400 BAD REQUEST\n')
sys.stdout.write('Content-type: text/html\n')
sys.stdout.write('Content-Length: %s\n' % len(out))
sys.stdout.write('\n')
sys.stdout.write(out)
sys.exit(0)
def show_qr_code(data):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=5,
border=4)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image()
fh = StringIO()
img.save(fh)
out = fh.getvalue()
fh.close()
sys.stdout.write('Status: 200 OK\n')
sys.stdout.write('Content-type: image/png\n')
sys.stdout.write('Content-Length: %s\n' % len(out))
sys.stdout.write('\n')
sys.stdout.write(out)
sys.exit(0)
def show_login_form(config):
templates_dir = config.get('secret', 'templates_dir')
fh = open(os.path.join(templates_dir, 'login.html'))
tpt = Template(fh.read())
fh.close()
vals = {
'action_url': config.get('secret', 'action_url'),
'css_root': config.get('secret', 'css_root')
}
out = tpt.safe_substitute(vals)
sys.stdout.write('Status: 200 OK\n')
sys.stdout.write('Content-type: text/html\n')
sys.stdout.write('Content-Length: %s\n' % len(out))
sys.stdout.write('\n')
sys.stdout.write(out)
sys.exit(0)
def show_totp_page(config, user, gaus):
# generate provisioning URI
tpt = Template(config.get('secret', 'totp_user_mask'))
totp_user = tpt.safe_substitute(username=user)
totp_qr_uri = gaus.totp.provisioning_uri(totp_user)
action_url = config.get('secret', 'action_url')
qrcode_embed = '<img src="%s?qrcode=%s"/>' % (action_url, totp_qr_uri)
templates_dir = config.get('secret', 'templates_dir')
fh = open(os.path.join(templates_dir, 'totp.html'))
tpt = Template(fh.read())
fh.close()
if gaus.scratch_tokens:
scratch_tokens = '<br/>'.join(gaus.scratch_tokens)
else:
scratch_tokens = '&nbsp;'
vals = {
'action_url': action_url,
'css_root': config.get('secret', 'css_root'),
'qrcode_embed': qrcode_embed,
'scratch_tokens': scratch_tokens
}
out = tpt.safe_substitute(vals)
sys.stdout.write('Status: 200 OK\n')
sys.stdout.write('Content-type: text/html\n')
sys.stdout.write('Content-Length: %s\n' % len(out))
sys.stdout.write('\n')
sys.stdout.write(out)
sys.exit(0)
def generate_secret(config):
encrypt_secret = config.getboolean('secret', 'encrypt_secret')
window_size = config.getint('secret', 'window_size')
rate_limit = config.get('secret', 'rate_limit')
# scratch tokens don't make any sense with encrypted secret
if not encrypt_secret:
scratch_tokens_n = config.getint('secret', 'scratch_tokens_n')
else:
scratch_tokens_n = 0
(times, secs) = rate_limit.split(',')
rate_limit = (int(times), int(secs))
gaus = totpcgi.utils.generate_secret(rate_limit, window_size,
scratch_tokens_n)
return gaus
def cgimain():
form = cgi.FieldStorage()
if 'qrcode' in form:
#if os.environ['HTTP_REFERER'].find(os.environ['SERVER_NAME']) == -1:
# bad_request(config, 'Sorry, you failed the HTTP_REFERER check')
qrcode = form.getfirst('qrcode')
show_qr_code(qrcode)
remote_host = os.environ['REMOTE_ADDR']
try:
trust_http_auth = config.getboolean('secret', 'trust_http_auth')
except ConfigParser.NoOptionError:
trust_http_auth = False
if trust_http_auth and os.environ.has_key('REMOTE_USER'):
user = os.environ['REMOTE_USER']
pincode = None
syslog.syslog(syslog.LOG_NOTICE,
'Success (http-auth): user=%s, host=%s' % (user, remote_host))
else:
must_keys = ('username', 'pincode')
for must_key in must_keys:
if must_key not in form:
show_login_form(config)
user = form.getfirst('username')
pincode = form.getfirst('pincode')
# start by verifying the pincode
try:
backends.pincode_backend.verify_user_pincode(user, pincode)
except Exception, ex:
syslog.syslog(syslog.LOG_NOTICE,
'Failure: user=%s, host=%s, message=%s' % (user, remote_host,
str(ex)))
bad_request(config, str(ex))
# pincode verified
syslog.syslog(syslog.LOG_NOTICE,
'Success: user=%s, host=%s' % (user, remote_host))
# is there an existing secret for this user?
exists = True
try:
backends.secret_backend.get_user_secret(user, pincode)
except totpcgi.UserNotFound:
# if we got it, then there isn't an existing secret in place
exists = False
if exists:
syslog.syslog(syslog.LOG_NOTICE,
'Secret exists: user=%s, host=%s' % (user, remote_host))
bad_request(config, 'Existing secret found. It must be removed first.')
# now generate the secret and store it
gaus = generate_secret(config)
# if we don't need to encrypt the secret, set pincode to None
encrypt_secret = config.getboolean('secret', 'encrypt_secret')
if not encrypt_secret:
pincode = None
backends.secret_backend.save_user_secret(user, gaus, pincode)
# purge all old state, as it's now obsolete
backends.state_backend.delete_user_state(user)
show_totp_page(config, user, gaus)
if __name__ == '__main__':
cgimain()

View file

@ -0,0 +1,11 @@
#%PAM-1.0
auth required pam_env.so
auth sufficient pam_url.so config=/etc/pam_url.conf
auth requisite pam_succeed_if.so uid >= 500 quiet
auth required pam_deny.so
auth include system-auth
account include system-auth
password include system-auth
session optional pam_keyinit.so revoke
session required pam_limits.so

View file

@ -0,0 +1,6 @@
#%PAM-1.0
auth include system-auth
account include system-auth
password include system-auth
session optional pam_keyinit.so revoke
session required pam_limits.so

View file

@ -0,0 +1,6 @@
#%PAM-1.0
auth include system-auth
account include system-auth
password include system-auth
session optional pam_keyinit.so revoke
session required pam_limits.so

View file

@ -0,0 +1,34 @@
Listen 8443
<VirtualHost 10.5.126.30:8443 10.5.126.25:8443 10.5.126.26:8443>
# Load this module locally here.
LoadModule suexec_module modules/mod_suexec.so
ServerAdmin admin@fedoraproject.org
DocumentRoot /var/www/totpcgi
ServerName fas-all.phx2.fedoraproject.org:8443
ErrorLog /var/log/httpd/totpcgi-error.log
SuexecUserGroup totpcgi totpcgi
# Use this for totp.cgi
AddHandler cgi-script .cgi
DirectoryIndex index.cgi
# Or use this for totp.fcgi:
#AddHandler fcgid-script .fcgi
#DirectoryIndex index.fcgi
SSLEngine on
SSLCertificateFile /etc/pki/totpcgi/totpcgi-server.crt
SSLCertificateKeyFile /etc/pki/totpcgi/totpcgi-server.key
SSLCACertificateFile /etc/pki/totpcgi/totpcgi-ca.crt
SSLVerifyClient require
SSLVerifyDepth 10
CustomLog /var/log/httpd/totpcgi-ssl-request-log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Directory "/var/www/totpcgi">
Options ExecCGI
</Directory>
</VirtualHost>

View file

@ -0,0 +1,34 @@
Listen 8443
<VirtualHost *:8443>
# Load this module locally here.
LoadModule suexec_module modules/mod_suexec.so
ServerAdmin admin@fedoraproject.org
DocumentRoot /var/www/totpcgi
ServerName fas-all.stg.phx2.fedoraproject.org:8443
ErrorLog /var/log/httpd/totpcgi-error.log
SuexecUserGroup totpcgi totpcgi
# Use this for totp.cgi
AddHandler cgi-script .cgi
DirectoryIndex index.cgi
# Or use this for totp.fcgi:
#AddHandler fcgid-script .fcgi
#DirectoryIndex index.fcgi
SSLEngine on
SSLCertificateFile /etc/pki/totpcgi/totpcgi-server.crt
SSLCertificateKeyFile /etc/pki/totpcgi/totpcgi-server.key
SSLCACertificateFile /etc/pki/totpcgi/totpcgi-ca.crt
SSLVerifyClient require
SSLVerifyDepth 10
CustomLog /var/log/httpd/totpcgi-ssl-request-log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Directory "/var/www/totpcgi">
Options ExecCGI
</Directory>
</VirtualHost>

View file

@ -0,0 +1,33 @@
<VirtualHost 192.168.1.38:8443 192.168.1.39:8443 192.168.1.49:8443>
# Load this module locally here.
LoadModule suexec_module modules/mod_suexec.so
ServerAdmin admin@fedoraproject.org
DocumentRoot /var/www/totpcgi
ServerName fas-all.vpn.fedoraproject.org:8443
ErrorLog /var/log/httpd/totpcgi-error.log
SuexecUserGroup totpcgi totpcgi
# Use this for totp.cgi
AddHandler cgi-script .cgi
DirectoryIndex index.cgi
# Or use this for totp.fcgi:
#AddHandler fcgid-script .fcgi
#DirectoryIndex index.fcgi
SSLEngine on
SSLCertificateFile /etc/pki/totpcgi/totpcgi-server-vpn.crt
SSLCertificateKeyFile /etc/pki/totpcgi/totpcgi-server-vpn.key
SSLCACertificateFile /etc/pki/totpcgi/totpcgi-ca.crt
SSLVerifyClient require
SSLVerifyDepth 10
CustomLog /var/log/httpd/totpcgi-ssl-request-log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Directory "/var/www/totpcgi">
Options ExecCGI
</Directory>
</VirtualHost>

View file

@ -0,0 +1,220 @@
- name: install needed packages
yum: pkg={{ item }} state=present
with_items:
- mod_auth_psql
- totpcgi
- totpcgi-selinux
- totpcgi-provisioning
- python-qrcode
- httpd
- mod_ssl
tags:
- packages
- name: add totpcgi user
user: name=totpcgi uid=430 state=present home=/var/lib/totpcgi createhome=yes system=yes
tags:
- config
- name: Install the cgi apache configuration files
template: >
src={{ item }}.j2 dest=/etc/httpd/conf.d/{{ item }}
owner=root group=root mode=0444
with_items:
- provisioning-httpd.conf
tags:
- files
- config
notify:
- restart apache
- name: create directories
file: path=/etc/{{ item.path }} state=directory owner=root group=totpcgi mode=750
with_items:
- pki/totpcgi
- totpcgi
- totpcgi/templates
- totpcgi/totp
- name: copy index file over
copy: >
src=html
dest=/etc/totpcgi/templates/html
owner=root
group=totpcgiprov
mode=0750
tags:
- files
- config
- name: copy index file over
copy: >
src=provisioning.cgi
dest=/var/www/totpcgi-provisioning/index.cgi
owner=totpcgiprov
group=totpcgiprov
mode=0550
tags:
- files
- config
- name: copy index file over
copy: >
src=index.cgi
dest=/var/www/totpcgi/index.cgi
owner=totpcgiprov
group=totpcgiprov
mode=0550
tags:
- files
- config
- name: copy totpcgi.conf file over
template: >
src=totpcgi.conf.j2
dest=/etc/totpcgi/totpcgi.conf
owner=root
group=totpcgiprov
mode=0640
tags:
- files
- config
# staging certs
- name: copy server cert file over
copy: >
src={{ puppet_secure }}/2fa-certs/keys/fas-all.stg.phx2.fedoraproject.org.crt
dest=/etc/pki/totpcgi/totpcgi-server.crt
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
when: env == "staging"
- name: copy server cert file over
copy: >
src={{ puppet_secure }}/fa-certs/keys/fas-all.stg.phx2.fedoraproject.org.key
dest=/etc/pki/totpcgi/totpcgi-server.key
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
when: env == "staging"
- name: copy server cert file over
copy: >
src=totpcgi-httpd.conf.stg
dest=/etc/httpd/conf.d/totpcgi.conf
owner=root
group=root
mode=0444
tags:
- files
- config
when: env == "staging"
# prod certs
- name: copy server cert file over
copy: >
src={{ puppet_secure }}/2fa-certs/keys/fas-all.phx2.fedoraproject.org.crt
dest=/etc/pki/totpcgi/totpcgi-server.crt
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
when: env == "production"
- name: copy server cert file over
copy: >
src={{ puppet_secure }}/fa-certs/keys/fas-all.phx2.fedoraproject.org.key
dest=/etc/pki/totpcgi/totpcgi-server.key
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
when: env == "production"
- name: copy server cert file over
copy: >
src=totpcgi-httpd.conf
dest=/etc/httpd/conf.d/totpcgi.conf
owner=root
group=root
mode=0444
tags:
- files
- config
when: env == "production"
# vpn certs
- name: copy server cert file over
copy: >
src={{ puppet_secure }}/2fa-certs/keys/fas-all.phx2.fedoraproject.org.crt
dest=/etc/pki/totpcgi/totpcgi-server.crt
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
when: env == "production"
- name: copy server cert file over
copy: >
src={{ puppet_secure }}/fa-certs/keys/fas-all.phx2.fedoraproject.org.key
dest=/etc/pki/totpcgi/totpcgi-server.key
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
when: env == "production"
- name: copy server cert file over
copy: >
src=totpcgi-httpd.conf
dest=/etc/httpd/conf.d/totpcgi.conf
owner=root
group=root
mode=0444
tags:
- files
- config
when: env == "production"
#
# TODO: vpn certs
#
- name: copy server cert file over
copy: >
src={{ puppet_private }}/2fa-certs/keys/ca.crt
dest=/etc/pki/totpcgi/totpcgi-ca.crt
owner=root
group=totpcgi
mode=0640
tags:
- files
- config
- name: copy server cert file over
template: >
src=provisioning.conf.j2
dest=/etc/totpcgi/provisioning.conf
owner=root
group=totpcgiprov
mode=0640
tags:
- files
- config

View file

@ -0,0 +1,44 @@
Listen 8444
<VirtualHost *:8444>
LoadModule suexec_module modules/mod_suexec.so
DocumentRoot /var/www/totpcgi-provisioning
ServerName fas01.stg.phx2.fedoraproject.org:8444
ErrorLog /var/log/httpd/totpcgi-provisioning-error.log
SuexecUserGroup totpcgiprov totpcgiprov
AddHandler cgi-script .cgi
DirectoryIndex index.cgi
Header set Cache-Control no-cache
Header set Expires 0
#SSLEngine on
#SSLCertificateFile /etc/pki/totpcgi/totpcgi-server.crt
#SSLCertificateKeyFile /etc/pki/totpcgi/totpcgi-server.key
#SSLCACertificateFile /etc/pki/totpcgi/totpcgi-ca.crt
#CustomLog /var/log/httpd/totpcgi-provisioning-ssl-request-log \
# "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Directory "/var/www/totpcgi-provisioning">
Options ExecCGI
</Directory>
<Location />
AuthType Basic
AuthName "Fedora totpcgi"
Auth_PG_host db-fas
Auth_PG_port 5432
Auth_PG_user fasreadonly
Auth_PG_pwd {{ fasReadOnlyPassword }}
Auth_PG_database fas2
Auth_PG_pwd_table people
Auth_PG_uid_field username
Auth_PG_pwd_field password
Auth_PG_pwd_whereclause " and status='active'"
Require valid-user
</Location>
</VirtualHost>

View file

@ -0,0 +1,88 @@
[secret]
# Whether to encrypt the secret when we generate it. Encrypting the secret
# with the user's pincode means that even if the .totp file is leaked, an
# attacker will not be able to get the secret without knowing the user's
# pincode. The downside is that if a user forgets their pincode, both the
# pincode and the secret will need to be fully re-provisioned.
# Setting to "True" will also turn off scratch-token support.
encrypt_secret = False
# You can allow for some clock drift between the client and server by setting
# the permitted window size. Window size is calculated in 10-second intervals,
# so a window size of 6 allows clock drift of 60 seconds in either direction.
window_size = 3
# First value is the number of times. Second value is the number of seconds.
# So, "3, 30" means "3 falures within 30 seconds"
rate_limit = 3, 30
# How many scratch tokens to generate. Note, that this setting is ignored
# if encrypt_secret is set to True.
scratch_tokens_n = 5
# This identifies the token in the Google Authenticator application. It comes
# very handy when users have more than one token, so set this to something
# descriptive of your environment.
{% if environment == "staging" %}
totp_user_mask = $username@stg.fedoraproject.org
{% else %}
totp_user_mask = $username@fedoraproject.org
{% endif %}
# Used by provisioning.cgi
# Where the provisioning CGI is located, with regards to the web root.
action_url = /totpcgiprovision/index.cgi
# Used by provisioning.cgi
# Where provisioning.css and provisioning-print.css are located with regards
# to the web root.
css_root = /totpcgiprovision/
# Used by provisioning.cgi
# Where to find the templates files.
templates_dir = /etc/totpcgi/templates
# Used by provisioning.cgi
# Whether to rely on HTTP auth to handle authentication.
# As we don't get the password, only the username, turning this on
# will automatically set encrypt_secret to false.
#
# Be careful turning this on.
trust_http_auth = True
[pincode]
# Which hashing mechanism to use. Valid entries: md5, bcrypt, sha256, sha512
usehash = sha256
# Whether to compile the DBM database (only meaningful with the file backend)
makedb = True
# The backends are pretty much the same as in totpcgi.conf, except if you
# are using the postgresql secret backend, you need to connect as a user
# that is allowed to modify user records (e.g. totpcgi_admin).
[secret_backend]
;engine = file
;secrets_dir = /etc/totpcgi/totp
; For PostgreSQL backend:
engine = pgsql
pg_connect_string = user={{ totpcgiadminDBUser }} password={{ totpcgiadminDBPassword }} host=db-fas01 dbname=totpcgi
[pincode_backend]
engine = pgsql
pg_connect_string = user={{ totpcgiadminDBUser }} password={{ totpcgiadminDBPassword }} host=db-fas01 dbname=totpcgi
; For LDAP backend (simple bind auth):
;engine = ldap
;ldap_url = ldaps://ipa.example.com:636/
;ldap_dn = uid=$username,cn=users,cn=accounts,dc=example,dc=com
;ldap_cacert = /etc/pki/tls/certs/ipa-ca.crt
[state_backend]
;engine = file
;state_dir = /var/lib/totpcgi
; For PostgreSQL backend:
engine = pgsql
pg_connect_string = user={{ totpcgiadminDBUser }} password={{ totpcgiadminDBPassword }} host=db-fas01 dbname=totpcgi

View file

@ -0,0 +1,31 @@
[main]
require_pincode = True
success_string = OK
{% if env == "staging" %}
fas_url = https://admin.stg.fedoraproject.org/accounts/
{% else %}
fas_url = https://admin.fedoraproject.org/accounts/
{% endif %}
[secret_backend]
; For PostgreSQL backend:
engine = pgsql
pg_connect_string = user={{ totpcgiDBUser }} password={{ totpcgiDBPassword }} host=db-fas01 dbname=totpcgi
[pincode_backend]
engine = pgsql
pg_connect_string = user={{ totpcgiDBUser }} password={{ totpcgiDBPassword }} host=db-fas01 dbname=totpcgi
; For LDAP backend (simple bind auth):
;engine = ldap
;ldap_url = ldaps://ipa.example.com:636/
;ldap_dn = uid=$username,cn=users,cn=accounts,dc=example,dc=com
;ldap_cacert = /etc/pki/tls/certs/ipa-ca.crt
[state_backend]
;engine = file
;state_dir = /var/lib/totpcgi
; For PostgreSQL backend:
engine = pgsql
pg_connect_string = user={{ totpcgiDBUser }} password={{ totpcgiDBPassword }} host=db-fas01 dbname=totpcgi