create settings

This commit is contained in:
H.T. Kruitbosch 2017-11-28 10:55:28 +01:00
parent 6737e5b68c
commit 00478e626b
7 changed files with 222 additions and 22 deletions

View File

@ -1,2 +1,7 @@
# RuG website template with SAML2 login
# CENTOS dependencies
yum install libxml2-devel libxslt-devel python34-devel xmlsec1-devel libmcrypt libmcrypt-devel xmlsec1-openssl

View File

@ -0,0 +1,83 @@
from django.core.management.base import BaseCommand, CommandError
import datetime
import os.path
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography import x509
from cryptography.x509.oid import NameOID
try:
import urllib # python 2
except:
import urllib.request as urllib # python 3
class Command(BaseCommand):
help = 'Create SAML2 settings, they are printed to standard out and it is up to de developer or deployer to add' \
'them to the django settings.'
def add_arguments(self, parser):
parser.add_argument('--country', required=True, nargs=1, type=str, dest='country', help='For example: US, NL, DE, etc')
parser.add_argument('--city', required=True, nargs=1, type=str, dest='city', help='For example: London or Groningen')
parser.add_argument('--state', required=True, nargs=1, type=str, dest='state', help='For example: State or province, for example California or Groningen.')
parser.add_argument('--organisation', required=True, nargs=1, type=str, dest='organisation', help='Typically \'University of Groningen\'')
parser.add_argument('--organisation-unit', required=True, nargs=1, type=str, dest='organisation-unit', help='For example: \'Research and Innovation\' Support or \'Faculty of smart people\'')
parser.add_argument('--common-name', required=True, nargs=1, type=str, dest='common-name')
parser.add_argument('--alternative', nargs='*', type=str, dest='alternatives')
parser.add_argument('--support-name', required=True, nargs=1, type=str, dest='support_name')
parser.add_argument('--support-email', required=True, nargs=1, type=str, dest='support_email')
parser.add_argument('--technical-name', required=True, nargs=1, type=str, dest='technical_name')
parser.add_argument('--technical-email', required=True, nargs=1, type=str, dest='technical_email')
parser.add_argument('--entity-id', required=True, nargs=1, type=str, dest='entity_id', help='Used as an identifyer of your service, typically the url to your service: www.rug.nl/yourservice')
parser.add_argument('--base-url', required=True, nargs=1, type=str, dest='base_url', help='The base url of your service, the route <base_url>sso/saml/ should exist.')
parser.add_argument('--expires-after-days', nargs='?', type=int, default=10 * 365, dest='expires')
def handle(self, *args, **options):
key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend = default_backend())
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, options['country'][0]),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, options['state'][0]),
x509.NameAttribute(NameOID.LOCALITY_NAME, options['city'][0]),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, options['organisation'][0]),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, options['organisation-unit'][0]),
x509.NameAttribute(NameOID.COMMON_NAME, options['common-name'][0]),
])
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=options['expires']))
.add_extension(x509.SubjectAlternativeName([
x509.DNSName(alternative)
for alternative in options['alternatives']
] if options['alternatives'] is not None else []), critical = False)
.sign(key, hashes.SHA256(), default_backend())
)
settings_template = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings_template.py')
key_text = key.private_bytes(encoding=serialization.Encoding.PEM,
format = serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm = serialization.NoEncryption()).decode('utf-8')
x509_text = cert.public_bytes(encoding=serialization.Encoding.PEM).decode('utf-8')
assert (
key_text.startswith('-----BEGIN RSA PRIVATE KEY-----\n') and
key_text.endswith('\n-----END RSA PRIVATE KEY-----\n')), "Private key starts and ends improperly, should " \
"be -----BEGIN RSA PRIVATE KEY----- and " \
"-----END RSA PRIVATE KEY-----"
assert (
x509_text.startswith('-----BEGIN CERTIFICATE-----\n') and
x509_text.endswith('\n-----END CERTIFICATE-----\n')), "Certificate starts and ends improperly, should " \
"be -----BEGIN CERTIFICATE----- and " \
"-----END CERTIFICATE-----"
key_text = key_text[len('-----BEGIN RSA PRIVATE KEY-----\n'):-len('\n-----END RSA PRIVATE KEY-----\n')]
x509_text = x509_text[len('-----BEGIN CERTIFICATE-----\n'):-len('\n-----END CERTIFICATE-----\n')]
with open(settings_template, 'r') as f:
print(f.read().format(private_key=key_text , x509=x509_text, **options))

View File

@ -1,19 +0,0 @@
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
try:
import urllib # python 2
except:
import urllib.request as urllib # python 3
class Command(BaseCommand):
help = 'Initialize saml2 authentication files'
def add_arguments(self, parser):
pass
def handle(self, *args, **options):
urllib.urlretrieve(settings.SAML_PROVIDER_METADATA_URL, os.path.join())

View File

@ -0,0 +1,131 @@
AUTHENTICATION_BACKENDS = [
'django_saml2_pro_auth.auth.Backend'
]
# identifier for SAML to the service provider.
ENTITY_ID = '{entity_id}'
# Important to make sure redirects and such work properly
BASE_URL = '{base_url}'
# This support information is used for the SAML2 service provider contact information
TECHNICAL_NAME = '{technical_name}'
TECHNICAL_EMAIL = '{technical_email}'
SUPPORT_NAME = '{support_name}'
SUPPORT_EMAIL = '{support_email}'
ORGANISATION = '{organisation}'
SAML_ROUTE = BASE_URL + 'sso/saml/'
# redirection after successful SAML2 login
SAML_REDIRECT = BASE_URL + '/'
# Mapping used to move the SAML2 attributes to the django-auth user database
SAML_USERS_MAP = [{{
"RuG": {{
"email": dict(key="urn:mace:dir:attribute-def:mail", index=0),
"username": dict(key="urn:mace:dir:attribute-def:uid", index=0),
"first_name": dict(key="urn:mace:dir:attribute-def:gn", index=0),
"last_name": dict(key="urn:mace:dir:attribute-def:sn", index=0),
}}
}}]
#Private key stripped from the ---BEGIN ... and ---END ... part
PRIVATE_KEY = """{private_key}"""
# Idem for the certificate
X509 = """{x509}"""
# RuG metadata url, should not change unless you want another service provider.
SAML_PROVIDER_METADATA_URL = 'https://tst-idp.id.rug.nl/nidp/saml2/metadata'
#Code to get the RuG identity provider certificate
import sys
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML
if sys.version_info[0] == 2:
import urllib # python 2
else:
assert sys.version_info[0] == 3
import urllib.request as urllib # python 3
with urllib.urlopen(SAML_PROVIDER_METADATA_URL) as u:
RUG_PROVIDER_METADATA = u.read()
RUG_PROVIDER_X509CERT = OneLogin_Saml2_XML.query(
OneLogin_Saml2_XML.to_etree(RUG_PROVIDER_METADATA),
'/md:EntityDescriptor/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate'
)
assert len(RUG_PROVIDER_X509CERT) > 0, "Excepted a X509 RUG Provider Certificate"
assert len(RUG_PROVIDER_X509CERT) == 1, "Excepted no more than 1 X509 RUG Provider Certificate"
RUG_PROVIDER_X509CERT = RUG_PROVIDER_X509CERT[0].text.strip()
# Construction of the service provider metadata.
SAML_PROVIDERS = [{{
"RuG": {{
"strict": True,
"debug": True,
"custom_base_path": "",
"sp": {{
"entityId": ENTITY_ID,
"assertionConsumerService": {{
"url": BASE_URL + "/sso/saml/?provider=RuG&amp;acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
}},
"singleLogoutService": {{
"url": BASE_URL + "/sso/saml/?provider=RuG",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
"x509cert": X509,
"privateKey": PRIVATE_KEY,
}},
"idp": {{
"entityId": "https://tst-idp.id.rug.nl/nidp/saml2/metadata",
"singleSignOnService": {{
"url": "https://tst-idp.id.rug.nl/nidp/saml2/sso",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}},
"singleLogoutService": {{
"url": "https://tst-idp.id.rug.nl/nidp/saml2/spslo",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}},
"x509cert": RUG_PROVIDER_X509CERT,
}},
"organization": {{
"en-US": {{
"name": ORGANISATION,
"displayname": ORGANISATION,
"url": BASE_URL
}}
}},
"contact_person": {{
"technical": {{
"given_name": TECHNICAL_NAME,
"email_address": TECHNICAL_EMAIL
}},
"support": {{
"given_name": SUPPORT_NAME,
"email_address": SUPPORT_EMAIL
}}
}},
"security": {{
"requestedAuthnContext": False,
"name_id_encrypted": False,
"authn_requests_signed": True,
"logout_requests_signed": False,
"logout_response_signed": False,
"sign_metadata": False,
"want_messages_signed": False,
"want_assertions_signed": True,
"want_name_id": True,
"want_name_id_encrypted": False,
"want_assertions_encrypted": True,
"signature_algorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
"digest_algorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
}}
}}
}}]

View File

@ -61,8 +61,6 @@ USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
LOGOUT_URL = '/logout/'
LOGIN_URL = '/login/'
AUTHENTICATION_BACKENDS = [
@ -76,7 +74,7 @@ SAML_USERS_MAP = [{
"RuG": {
"email": dict(key="urn:mace:dir:attribute-def:mail", index=0),
"username": dict(key="urn:mace:dir:attribute-def:uid", index=0),
"first_name": dict(key="urn:mace:dir:attribute-def:givenName", index=0),
"first_name": dict(key="urn:mace:dir:attribute-def:gn", index=0),
"last_name": dict(key="urn:mace:dir:attribute-def:sn", index=0),
}
}]

View File

@ -27,6 +27,8 @@ setup(
include_package_data=True,
install_requires=[
'pyOpenSSL>=17.4.0',
'cryptography>=2.1.3,<3'
'django>=1.11.7,<1.12',
'django-bootstrap4>=0.0.4,<0.1',
'django-saml2-pro-auth>=0.0.2,<0.1'