CoSign

De Cliss XXI
Révision datée du 16 mai 2007 à 18:16 par imported>SylvainBeucler (→‎CoSign configuration)
Sauter à la navigation Sauter à la recherche

HOWTO: I found it difficult to install CoSign 2.0.2a because the documentation contains inaccuracies, and because it can be hard to debug configuration errors. Here's a working test configuration:

CoSign is composed of 3 main parts:

  • filter: this is mod_cosign, allowing Apache to automatically check the user's CoSign cookie
  • cgi: cosign.cgi and logout.cgi, for initial login and full logout
  • cosignd: the daemon that centralises authentication sessions, can be called from different computers where filter and cgi are installed


Compilation and Installation

I'm using Apache2 here. Let's also try to use FHS-compliant paths.

Dependencies as Debian packages:

aptitude install build-essential apache2-prefork-dev openssl-dev
./configure --enable-apache2=/usr/bin/apxs2 \
	    --prefix=/var/lib/cosign \
	    --sbindir=/usr/local/sbin \
	    --mandir=/usr/local/share/man \
	    --with-filterdb=/var/lib/cosign/filter \
	    --with-cosigndb=/var/lib/cosign/daemon \
	    --with-cosignconf=/etc/cosign.conf \
	    --with-cosigncadir=/etc/cosign/certs/CA \
	    --with-cosigncert=/etc/cosign/certs/cert.pem \
	    --with-cosignkey=/etc/cosign/certs/key.pem

TODO: use /usr/lib and /usr/share appropriately, also allowing using the standard --prefix=/usr/local. Use FHS-compliant /usr/lib/cosign/cgi-ssl

Old versions of apxs (eg. Debian Sarge's) may not provide the libapr include path, resulting in lengthy compilation errors. In this case, provide it manually by prexifing the configure call with a CFLAGS variable:

CFLAGS=-I/usr/include/apr-0 ./configure --enable-apache2=...

Next:

make everything \
&& sudo make install-all \
&& sudo invoke-rc.d apache2 stop && sleep 1 && sudo invoke-rc.d apache2 start
# mod_cosign dir
mkdir -p -m 750 /var/lib/cosign/filter
chown www-data: /var/lib/cosign/filter

# cosignd dir
mkdir -p -m 750 /var/lib/cosign/daemon
useradd cosign
chown cosign /var/lib/cosign/daemon
# Copy customizable files somewhere else
# so they're not overwritten by an unfortunate 'make install'..
cp -a /var/lib/cosign/templates /var/lib/cosign/templates-local
cp -a /var/lib/cosign/html /var/lib/cosign/html-local
cp -a /var/lib/cosign/services /var/lib/cosign/services-local

Apache2 configuration

/etc/apache2/sites-available/default:

# Automatically added in /etc/apache2/httpd.conf by 'make install-all' (via apxs2)
LoadModule cosign_module /usr/lib/apache2/modules/mod_cosign.so

NameVirtualHost *:80
NameVirtualHost *:443
# TLS VirtualHost 'cause CoSign requires https login
<VirtualHost *:443>
  SSLEngine		On
  SSLCertificateFile	/etc/apache2/ssl/cert.pem
  SSLCertificateKeyFile	/etc/apache2/ssl/key.pem

  Include sites-available/common.inc
</VirtualHost>

<VirtualHost *:80>
  # Don't redirect to https if we come from http
  CosignHttpOnly On

  Include sites-available/common.inc
</VirtualHost>

/etc/apache2/sites-available/common.inc:

CosignProtected         Off
CosignHostname          localhost
CosignRedirect          https://localhost/cosign-bin/cosign.cgi
CosignPostErrorRedirect https://localhost/cosign/post_error.html
CosignService           simpleservice

CosignCrypto /etc/cosign/certs/mod_cosign.key /etc/cosign/certs/mod_cosign.crt /etc/cosign/certs/CA


Alias /cosign/ "/var/lib/cosign/html-local/"
ScriptAlias /cosign-bin/ "/var/lib/cosign/cgi-ssl/"

Alias /services/ "/var/lib/cosign/services-local/"
<Directory "/var/lib/cosign/services-local/">
  CosignProtected On
</Directory>


If you're using Debian, you can easily enable SSL through:

a2enmod ssl
echo "Listen 443" >> /etc/apache2/ports.conf

and restarting Apache.

If you need a simple self-signed certificate:

cd /etc/apache2/ssl/
openssl req -subj "/C=FR/ST=NPDC/L=Lievin/O=Cliss XXI/OU=SABDFL/CN=*/emailAddress=me@home/" \
  -new -x509 -nodes -days 10000 -keyout key.pem -out cert.pem
# check
openssl x509 -noout -text -in cert.pem

CoSign configuration

Pitfalls:

  • The cgi doesn't take more than 1 parameter - only service does.
  • set cosignhost is used by cgi, not cosignd. It specifies the host where cosignd runs, and is not related to replication.
  • Afaics 'factor' lines will always be ran and returns the name of the validated factor. This among others allows to filter which users can access to which service.
  • TODO: fix documentation
  • TODO: explain what service is for

/etc/cosign.conf:

# Allow CGI access from this CN (from the TLS certificate)
cgi localhost

set cosignhost localhost
set cosigncadir /etc/cosign/certs/CA/
set cosigncert /etc/cosign/certs/cgi.crt
set cosignkey /etc/cosign/certs/cgi.key

# Grant the given session type if credentials match the given factor
cookie cosign-simpleservice reauth FACTOR-LDAP
# Argument 3 and later are name of <FORM> fields from the template
factor /usr/share/cosign/factor/ldap login password

# Override the default template directories,
# so our changes won't be ovewritten by an unfortunate 'make install'
set cosigntmpldir /var/lib/cosign/templates-local
set cosignlogouturl http://localhost/
set cosignloopurl https://localhost/cosign/looping.html

Also describe cosign as a known service:

echo "cosign 6663/tcp" >> /etc/services

Certificates generation

We'll generate our own Certificate Authority.

TODO: during tests it's frequent to remove and rebuild everything. Typing password is really inconvenient in this case, and -passin doesn't work for 'ca'. Please find a way to make this unattented!

# Base OpenSSL install
mkdir -p -m 755 /etc/cosign/certs/CA
cd /etc/cosign/certs
PREVIOUS_UMASK=`umask`
umask 0027 # no read access to private keys!
mkdir -m 700 demoCA
pushd demoCA
mkdir -m 755 newcerts
mkdir -m 700 private
echo "01" > serial
touch index.txt
popd

# Root CA
# subj model from `openssl x509 -noout -text -in machin.cert`
openssl req -new -subj "/C=FR/ST=NPDC/L=Lievin/O=Cliss XXI/OU=SSO/CN=Root CA/" \
  -x509 -days 365 -keyout demoCA/private/cakey.pem -out demoCA/cacert.pem
chmod a+r demoCA/cacert.pem

# Certificate request and private key
# cosignd
openssl req -new -subj "/C=FR/ST=NPDC/L=Lievin/O=Cliss XXI/OU=SSO cosignd/CN=localhost/" \
  -nodes -keyout "cosignd.key" -out "cosignd.csr"
# Sign certificate
openssl ca -in "cosignd.csr" -out "cosignd.crt"

# CGI
openssl req -new -subj "/C=FR/ST=NPDC/L=Lievin/O=Cliss XXI/OU=SSO cgi/CN=localhost/" \
  -nodes -keyout "cgi.key" -out "cgi.csr"
# Sign certificate
openssl ca -in "cgi.csr" -out "cgi.crt"
chgrp www-data cgi.key cgi.crt

# mod_cosign
openssl req -new -subj "/C=FR/ST=NPDC/L=Lievin/O=Cliss XXI/OU=SSO mod_cosign/CN=localhost/" \
  -nodes -keyout "mod_cosign.key" -out "mod_cosign.csr"
# Sign certificate
openssl ca -in "mod_cosign.csr" -out "mod_cosign.crt"
# Return to normal permissions
umask $PREVIOUS_UMASK

# Allowed certs path
mkdir -m 755 CA
ln demoCA/cacert.pem CA/
c_rehash CA/

# Tests - cf. http://www.umich.edu/~umweb/software/cosign/faq.html
openssl verify -CApath CA/ -purpose sslclient cgi.crt mod_cosign.crt
openssl verify -CApath CA/ -purpose sslserver cosignd.crt
openssl s_client -connect localhost:6663 -cert cgi.crt -key cgi.key -CApath CA/ \
  -showcerts -state -debug -crlf -starttls smtp
# DO CHECK THAT YOU GET "Verify return code: 0 (ok)"!!!
# (s_client won't stop on error)

cosignd server start

TODO: use/adapt scripts/startup/cosignd

# Launch server with different keys just in case:
cd /var/lib/cosign/certs/
/usr/local/sbin/cosignd -y cosignd.crt -z cosignd.key -x /usr/local/cosign/certs/CA/
# CA path must be a full path, because cosignd chdir to /var/lib/cosign/daemon

Factor

Here's a simple LDAP factor (/usr/share/cosign/factor/ldap):

#!/usr/bin/perl
# Basic LDAP "factor" for CoSign
# Copyright (C) 2007  Cliss XXI
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Author: Sylvain Beucler <beuc@beuc.net>

use strict;

# Base configuration
my $base = "ou=users,dc=cliss21,dc=com";
my $host = "ldap://192.168.1.149";
my $user_filter = "(objectClass=posixAccount)";

# -----

# If authentication is successful, the external authenticator writes
# the factor name on stdout (file descriptor 1) and exits with a value
# of 0.  If an error occurs, the external authenticator writes an
# error message on stdout and exits with a value of 1. All other exit
# values are reserved for future use.
# -- cosign.conf(5)

use Net::LDAP; # aptitude install libnet-ldap-perl

# Grab CoSign parameters from standard input
my $login = <STDIN>; chomp $login;
my $pass = <STDIN>;  chomp $pass;

# Get the login's DN
my $binddn = "";
my $bindpw = "";
my $filter = "&$user_filter(uid=$login)";
my $attrs = [];

my $ldap = Net::LDAP->new($host);
if (!defined($ldap)) {
    # Be sure to catch the error manually and not use die, otherwise
    # the return code will be different than 1, making CoSign unhappy,
    # and the error will be print on stderr, so will be missing in the
    # web interface
    print "Cannot connect to LDAP server: $host\n";
    exit 1;
}
$ldap->bind("$binddn", password => "$bindpw", version => 3);

my $mesg = $ldap->search(base => $base, filter => $filter, attrs => $attrs);
my $count = $mesg->count;
if ($count == 0) {
    print "Unknown user: $login\n";
    exit 1;
}

my $entry = $mesg->entry();
my $dn = $entry->dn();

# Try to login with the DN

$binddn = $dn;
$bindpw = $pass;
my $result = $ldap->bind("$binddn", password => "$bindpw", version => 3);
if ($result->code()) {
	print "Login failed (LDAP error: " . $result->error . ")\n";
	exit 1;
}

$ldap->unbind();

print "FACTOR-LDAP\n"; # factor name
exit 0; # success

Test

Now hit https://localhost/services/

Beyond