First sekelton for shib-proxy.
This commit is contained in:
parent
f4d620ba4d
commit
f24f201d46
13 changed files with 787 additions and 0 deletions
0
global/overlay/etc/puppet/soc/facts.d/.empty
Normal file
0
global/overlay/etc/puppet/soc/facts.d/.empty
Normal file
154
global/overlay/etc/puppet/soc/files/sso/acme-dns-auth.py
Executable file
154
global/overlay/etc/puppet/soc/files/sso/acme-dns-auth.py
Executable file
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import sys
|
||||
|
||||
### EDIT THESE: Configuration values ###
|
||||
|
||||
# URL to acme-dns instance
|
||||
ACMEDNS_URL = "https://acme-d.sunet.se"
|
||||
# Path for acme-dns credential storage
|
||||
STORAGE_PATH = "/etc/letsencrypt/acmedns.json"
|
||||
# Whitelist for address ranges to allow the updates from
|
||||
# Example: ALLOW_FROM = ["192.168.10.0/24", "::1/128"]
|
||||
ALLOW_FROM = []
|
||||
# Force re-registration. Overwrites the already existing acme-dns accounts.
|
||||
FORCE_REGISTER = False
|
||||
|
||||
### DO NOT EDIT BELOW THIS POINT ###
|
||||
### HERE BE DRAGONS ###
|
||||
|
||||
DOMAIN = os.environ["CERTBOT_DOMAIN"]
|
||||
if DOMAIN.startswith("*."):
|
||||
DOMAIN = DOMAIN[2:]
|
||||
VALIDATION_DOMAIN = "_acme-challenge."+DOMAIN
|
||||
VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"]
|
||||
|
||||
|
||||
class AcmeDnsClient(object):
|
||||
"""
|
||||
Handles the communication with ACME-DNS API
|
||||
"""
|
||||
|
||||
def __init__(self, acmedns_url):
|
||||
self.acmedns_url = acmedns_url
|
||||
|
||||
def register_account(self, allowfrom):
|
||||
"""Registers a new ACME-DNS account"""
|
||||
|
||||
if allowfrom:
|
||||
# Include whitelisted networks to the registration call
|
||||
reg_data = {"allowfrom": allowfrom}
|
||||
res = requests.post(self.acmedns_url+"/register",
|
||||
data=json.dumps(reg_data))
|
||||
else:
|
||||
res = requests.post(self.acmedns_url+"/register")
|
||||
if res.status_code == 201:
|
||||
# The request was successful
|
||||
return res.json()
|
||||
else:
|
||||
# Encountered an error
|
||||
msg = ("Encountered an error while trying to register a new acme-dns "
|
||||
"account. HTTP status {}, Response body: {}")
|
||||
print(msg.format(res.status_code, res.text))
|
||||
sys.exit(1)
|
||||
|
||||
def update_txt_record(self, account, txt):
|
||||
"""Updates the TXT challenge record to ACME-DNS subdomain."""
|
||||
update = {"subdomain": account['subdomain'], "txt": txt}
|
||||
headers = {"X-Api-User": account['username'],
|
||||
"X-Api-Key": account['password'],
|
||||
"Content-Type": "application/json"}
|
||||
res = requests.post(self.acmedns_url+"/update",
|
||||
headers=headers,
|
||||
data=json.dumps(update))
|
||||
if res.status_code == 200:
|
||||
# Successful update
|
||||
return
|
||||
else:
|
||||
msg = ("Encountered an error while trying to update TXT record in "
|
||||
"acme-dns. \n"
|
||||
"------- Request headers:\n{}\n"
|
||||
"------- Request body:\n{}\n"
|
||||
"------- Response HTTP status: {}\n"
|
||||
"------- Response body: {}")
|
||||
s_headers = json.dumps(headers, indent=2, sort_keys=True)
|
||||
s_update = json.dumps(update, indent=2, sort_keys=True)
|
||||
s_body = json.dumps(res.json(), indent=2, sort_keys=True)
|
||||
print(msg.format(s_headers, s_update, res.status_code, s_body))
|
||||
sys.exit(1)
|
||||
|
||||
class Storage(object):
|
||||
def __init__(self, storagepath):
|
||||
self.storagepath = storagepath
|
||||
self._data = self.load()
|
||||
|
||||
def load(self):
|
||||
"""Reads the storage content from the disk to a dict structure"""
|
||||
data = dict()
|
||||
filedata = ""
|
||||
try:
|
||||
with open(self.storagepath, 'r') as fh:
|
||||
filedata = fh.read()
|
||||
except IOError as e:
|
||||
if os.path.isfile(self.storagepath):
|
||||
# Only error out if file exists, but cannot be read
|
||||
print("ERROR: Storage file exists but cannot be read")
|
||||
sys.exit(1)
|
||||
try:
|
||||
data = json.loads(filedata)
|
||||
except ValueError:
|
||||
if len(filedata) > 0:
|
||||
# Storage file is corrupted
|
||||
print("ERROR: Storage JSON is corrupted")
|
||||
sys.exit(1)
|
||||
return data
|
||||
|
||||
def save(self):
|
||||
"""Saves the storage content to disk"""
|
||||
serialized = json.dumps(self._data)
|
||||
try:
|
||||
with os.fdopen(os.open(self.storagepath,
|
||||
os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
|
||||
fh.truncate()
|
||||
fh.write(serialized)
|
||||
except IOError as e:
|
||||
print("ERROR: Could not write storage file.")
|
||||
sys.exit(1)
|
||||
|
||||
def put(self, key, value):
|
||||
"""Puts the configuration value to storage and sanitize it"""
|
||||
# If wildcard domain, remove the wildcard part as this will use the
|
||||
# same validation record name as the base domain
|
||||
if key.startswith("*."):
|
||||
key = key[2:]
|
||||
self._data[key] = value
|
||||
|
||||
def fetch(self, key):
|
||||
"""Gets configuration value from storage"""
|
||||
try:
|
||||
return self._data[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Init
|
||||
client = AcmeDnsClient(ACMEDNS_URL)
|
||||
storage = Storage(STORAGE_PATH)
|
||||
|
||||
# Check if an account already exists in storage
|
||||
account = storage.fetch(DOMAIN)
|
||||
if FORCE_REGISTER or not account:
|
||||
# Create and save the new account
|
||||
account = client.register_account(ALLOW_FROM)
|
||||
storage.put(DOMAIN, account)
|
||||
storage.save()
|
||||
|
||||
# Display the notification for the user to update the main zone
|
||||
msg = "Please add the following CNAME record to your main DNS zone:\n{}"
|
||||
cname = "{} CNAME {}.".format(VALIDATION_DOMAIN, account["fulldomain"])
|
||||
print(msg.format(cname))
|
||||
|
||||
# Update the TXT record in acme-dns instance
|
||||
client.update_txt_record(account, VALIDATION_TOKEN)
|
19
global/overlay/etc/puppet/soc/files/sso/apache-ssl.conf
Normal file
19
global/overlay/etc/puppet/soc/files/sso/apache-ssl.conf
Normal file
|
@ -0,0 +1,19 @@
|
|||
# This file contains important security parameters. If you modify this file
|
||||
# manually, Certbot will be unable to automatically provide future security
|
||||
# updates. Instead, Certbot will print and log an error message with a path to
|
||||
# the up-to-date file that you will need to refer to when manually updating
|
||||
# this file. Contents are based on https://ssl-config.mozilla.org
|
||||
|
||||
SSLEngine on
|
||||
|
||||
# Intermediate configuration, tweak to your needs
|
||||
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
|
||||
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||
SSLHonorCipherOrder off
|
||||
SSLSessionTickets off
|
||||
|
||||
SSLOptions +StrictRequire
|
||||
|
||||
# Add vhost name to log entries:
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
22
global/overlay/etc/puppet/soc/files/sso/attribute-map.xml
Normal file
22
global/overlay/etc/puppet/soc/files/sso/attribute-map.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<Attributes xmlns="urn:mace:shibboleth:2.0:attribute-map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<!--
|
||||
SWAMID standard attribute-map.xml for SAML 2.0
|
||||
==============================================
|
||||
The mappings are agreed to within the Shibboleth community or directly LDAP attribute names.
|
||||
|
||||
Version: 2017-01-04
|
||||
|
||||
REMEMBER to notify SWAMID saml-admins list when updating this file!
|
||||
-->
|
||||
|
||||
<!-- eduPerson attributes until version 201310 -->
|
||||
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" id="eppn">
|
||||
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
|
||||
</Attribute>
|
||||
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.11" id="assurance"/>
|
||||
|
||||
<!-- Attribute to extract SWAMID Assurance Profiles -->
|
||||
<Attribute name="urn:oasis:names:tc:SAML:attribute:assurance-certification" id="Assurance-Certification"/>
|
||||
|
||||
</Attributes>
|
82
global/overlay/etc/puppet/soc/files/sso/attribute-policy.xml
Normal file
82
global/overlay/etc/puppet/soc/files/sso/attribute-policy.xml
Normal file
|
@ -0,0 +1,82 @@
|
|||
<afp:AttributeFilterPolicyGroup
|
||||
xmlns="urn:mace:shibboleth:2.0:afp:mf:basic"
|
||||
xmlns:saml="urn:mace:shibboleth:2.0:afp:mf:saml"
|
||||
xmlns:basic="urn:mace:shibboleth:2.0:afp:mf:basic"
|
||||
xmlns:afp="urn:mace:shibboleth:2.0:afp"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<!-- Shared rule for affiliation values. -->
|
||||
<afp:PermitValueRule id="eduPersonAffiliationValues" xsi:type="OR">
|
||||
<Rule xsi:type="AttributeValueString" value="faculty"/>
|
||||
<Rule xsi:type="AttributeValueString" value="student"/>
|
||||
<Rule xsi:type="AttributeValueString" value="staff"/>
|
||||
<Rule xsi:type="AttributeValueString" value="alum"/>
|
||||
<Rule xsi:type="AttributeValueString" value="member"/>
|
||||
<Rule xsi:type="AttributeValueString" value="affiliate"/>
|
||||
<Rule xsi:type="AttributeValueString" value="employee"/>
|
||||
<Rule xsi:type="AttributeValueString" value="library-walk-in"/>
|
||||
</afp:PermitValueRule>
|
||||
|
||||
<!--
|
||||
Shared rule for all "scoped" attributes, but you'll have to manually apply it inside
|
||||
an AttributeRule for each attribute you want to check.
|
||||
-->
|
||||
<afp:PermitValueRule id="ScopingRules" xsi:type="AND">
|
||||
<Rule xsi:type="NOT">
|
||||
<Rule xsi:type="AttributeValueRegex" regex="@"/>
|
||||
</Rule>
|
||||
<Rule xsi:type="saml:AttributeScopeMatchesShibMDScope"/>
|
||||
</afp:PermitValueRule>
|
||||
|
||||
<afp:AttributeFilterPolicy>
|
||||
<!-- This policy is in effect in all cases. -->
|
||||
<afp:PolicyRequirementRule xsi:type="ANY"/>
|
||||
|
||||
<!-- Filter out undefined affiliations and ensure only one primary. -->
|
||||
<afp:AttributeRule attributeID="affiliation">
|
||||
<afp:PermitValueRule xsi:type="AND">
|
||||
<RuleReference ref="eduPersonAffiliationValues"/>
|
||||
<RuleReference ref="ScopingRules"/>
|
||||
</afp:PermitValueRule>
|
||||
</afp:AttributeRule>
|
||||
<afp:AttributeRule attributeID="unscoped-affiliation">
|
||||
<afp:PermitValueRuleReference ref="eduPersonAffiliationValues"/>
|
||||
</afp:AttributeRule>
|
||||
<afp:AttributeRule attributeID="primary-affiliation">
|
||||
<afp:PermitValueRuleReference ref="eduPersonAffiliationValues"/>
|
||||
</afp:AttributeRule>
|
||||
|
||||
<afp:AttributeRule attributeID="subject-id">
|
||||
<afp:PermitValueRuleReference ref="ScopingRules"/>
|
||||
</afp:AttributeRule>
|
||||
|
||||
<afp:AttributeRule attributeID="pairwise-id">
|
||||
<afp:PermitValueRuleReference ref="ScopingRules"/>
|
||||
</afp:AttributeRule>
|
||||
|
||||
<afp:AttributeRule attributeID="eppn">
|
||||
<!-- Disabled scope check since the proxy does it for us and the proxies metadata doesn't include scopes from our customers.
|
||||
<afp:PermitValueRuleReference ref="ScopingRules"/>
|
||||
-->
|
||||
</afp:AttributeRule>
|
||||
|
||||
<afp:AttributeRule attributeID="targeted-id">
|
||||
<afp:PermitValueRuleReference ref="ScopingRules"/>
|
||||
</afp:AttributeRule>
|
||||
|
||||
<!-- Require NameQualifier/SPNameQualifier match IdP and SP entityID respectively. -->
|
||||
<afp:AttributeRule attributeID="persistent-id">
|
||||
<afp:PermitValueRule xsi:type="saml:NameIDQualifierString"/>
|
||||
</afp:AttributeRule>
|
||||
|
||||
<!-- Enforce that the values of schacHomeOrganization are a valid Scope. -->
|
||||
<afp:AttributeRule attributeID="schacHomeOrganization">
|
||||
<afp:PermitValueRule xsi:type="saml:AttributeValueMatchesShibMDScope" />
|
||||
</afp:AttributeRule>
|
||||
|
||||
<!-- Catch-all that passes everything else through unmolested. -->
|
||||
<afp:AttributeRule attributeID="*" permitAny="true"/>
|
||||
|
||||
</afp:AttributeFilterPolicy>
|
||||
|
||||
</afp:AttributeFilterPolicyGroup>
|
33
global/overlay/etc/puppet/soc/files/sso/md-signer2.crt
Normal file
33
global/overlay/etc/puppet/soc/files/sso/md-signer2.crt
Normal file
|
@ -0,0 +1,33 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
0
global/overlay/etc/puppet/soc/manifests/.empty
Normal file
0
global/overlay/etc/puppet/soc/manifests/.empty
Normal file
176
global/overlay/etc/puppet/soc/manifests/sso.pp
Normal file
176
global/overlay/etc/puppet/soc/manifests/sso.pp
Normal file
|
@ -0,0 +1,176 @@
|
|||
## Copy from CNAAS, modifications for Sunet CERT
|
||||
#
|
||||
# General SSO documentation: https://wiki.sunet.se/x/sZGLBg
|
||||
#
|
||||
# @param hostname FQDN of the host this is running on.
|
||||
#
|
||||
# @param email Support email used in error messages etc.
|
||||
#
|
||||
# @param service_endpoint Location of service to reverse proxy for.
|
||||
#
|
||||
# @param groups
|
||||
# List of user groups from sso_groups in global/overlay/etc/hiera/data/common.yaml. The
|
||||
# default is a non-existing placeholder group, added to make the Apache config valid.
|
||||
#
|
||||
# @param passthrough List of paths to disable SAML protection for, e.g. API paths.
|
||||
#
|
||||
# @param x_remote_user
|
||||
# If true, EPPN is put in the HTTP header X-Remote-User instead of REMOTE_USER.
|
||||
#
|
||||
# @param single_user
|
||||
# If true, EPPN is discarded and X-Remote-User is set to "cnaas-user". This is useful in
|
||||
# cases where the service we reverse proxy for can't create new accounts automatically.
|
||||
# We use this only for Graylog at the time of writing.
|
||||
#
|
||||
# @param swamid_testing Set this to true if your SP is registered in swamid-testing.
|
||||
#
|
||||
# @param front_clients
|
||||
# Hiera field, defined at common.yaml, with the the frontend IP prefixes that require access
|
||||
# to port 443. Defaults to empty string.
|
||||
#
|
||||
class cnaas::sso(
|
||||
$hostname,
|
||||
$email,
|
||||
$service_endpoint,
|
||||
$groups = ['PLACEHOLDER'],
|
||||
$passthrough = [],
|
||||
$x_remote_user = false,
|
||||
$swamid_testing = false,
|
||||
$single_user = false,
|
||||
$front_clients = '',
|
||||
$translog = 'INFO',
|
||||
$certbot = true,
|
||||
) {
|
||||
|
||||
file { '/opt/sso':
|
||||
ensure => directory,
|
||||
}
|
||||
|
||||
#
|
||||
# Apache files
|
||||
#
|
||||
|
||||
file { '/opt/sso/apache':
|
||||
ensure => directory,
|
||||
}
|
||||
|
||||
file { '/opt/sso/apache/site.conf':
|
||||
ensure => file,
|
||||
content => template('soc/sso/apache-site.conf.erb'),
|
||||
}
|
||||
|
||||
# SSL defaults copied from certbot:
|
||||
# https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf
|
||||
file { '/opt/sso/apache/ssl.conf':
|
||||
ensure => file,
|
||||
content => file('soc/sso/apache-ssl.conf'),
|
||||
}
|
||||
|
||||
file { '/opt/sso/apache/groups.txt':
|
||||
ensure => file,
|
||||
content => template('soc/sso/apache-groups.txt.erb')
|
||||
}
|
||||
|
||||
#
|
||||
# Shibboleth files
|
||||
#
|
||||
|
||||
file { '/opt/sso/shibboleth':
|
||||
ensure => directory,
|
||||
}
|
||||
|
||||
file { '/opt/sso/shibboleth/shibboleth2.xml':
|
||||
ensure => file,
|
||||
content => template('soc/sso/shibboleth2.xml.erb'),
|
||||
}
|
||||
|
||||
file { '/opt/sso/shibboleth/shibd.logger':
|
||||
ensure => file,
|
||||
content => template('soc/sso/shibd.logger.erb'),
|
||||
}
|
||||
|
||||
file { '/opt/sso/shibboleth/attribute-map.xml':
|
||||
ensure => file,
|
||||
content => file('soc/sso/attribute-map.xml'),
|
||||
}
|
||||
|
||||
file { '/opt/sso/shibboleth/md-signer2.crt':
|
||||
ensure => file,
|
||||
content => file('soc/sso/md-signer2.crt'),
|
||||
}
|
||||
sunet::snippets::secret_file { '/opt/sso/shibboleth/sp-key.pem':
|
||||
hiera_key => 'sso_sp_key'
|
||||
}
|
||||
|
||||
#
|
||||
# Certbot
|
||||
#
|
||||
|
||||
if $certbot {
|
||||
package { ['certbot', 'python3-requests']:
|
||||
ensure => 'latest',
|
||||
}
|
||||
|
||||
file { '/etc/letsencrypt/acme-dns-auth.py':
|
||||
ensure => file,
|
||||
content => file('cnaas/sso/acme-dns-auth.py'),
|
||||
mode => '0744',
|
||||
}
|
||||
|
||||
file { '/etc/letsencrypt/renewal-hooks/deploy/soc-sso-reload':
|
||||
ensure => file,
|
||||
mode => '0700',
|
||||
content => "#!/bin/sh -eu\ndocker exec sso service apache2 reload",
|
||||
}
|
||||
|
||||
sunet::scriptherder::cronjob { 'le_renew':
|
||||
cmd => '/bin/echo "Keeping this cronjob, but disabled to avoid scriptherder complainign about unknown check"',
|
||||
special => 'daily',
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Docker
|
||||
#
|
||||
|
||||
exec {"Create Docker network \"sso\" to talk to service":
|
||||
# We OR with true to ignore errors, since the network often already exists.
|
||||
# We specify a subnet so that services which have the option/requirement can
|
||||
# specify this subnet as source of trusted proxies. This is used in Graylog,
|
||||
# for example; see setting "trusted_proxies".
|
||||
command => 'docker network create sso --subnet 172.29.0.0/24 || true'
|
||||
}
|
||||
|
||||
file { '/opt/sso/docker-compose.yml':
|
||||
ensure => file,
|
||||
mode => '0600',
|
||||
content => template('soc/sso/docker-compose.yml.erb'),
|
||||
}
|
||||
|
||||
sunet::docker_compose_service { 'sso':
|
||||
description => '',
|
||||
compose_file => '/opt/sso/docker-compose.yml',
|
||||
}
|
||||
|
||||
#
|
||||
# NFT Rules
|
||||
#
|
||||
|
||||
if 'wg0' in $facts['networking']['interfaces'].keys {
|
||||
if $front_clients != '' {
|
||||
$front_clients_exposed = hiera_array($front_clients,[])
|
||||
sunet::nftables::docker_expose { 'clients_https' :
|
||||
allow_clients => $front_clients_exposed,
|
||||
port => 443,
|
||||
iif => 'wg0',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sunet::nftables::docker_expose { 'apache_sso_https' :
|
||||
allow_clients => ['0.0.0.0/0'],
|
||||
port => 443,
|
||||
iif => 'ens3',
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<%- scope.call_function('safe_hiera',['sso_groups']).each do | group, users | -%>
|
||||
<%= group %>: <% users.each.with_index do | user, i | %><%= user %><%= ' ' if i < (users.size - 1) %><% end %>
|
||||
<%- end -%>
|
|
@ -0,0 +1,63 @@
|
|||
<IfModule mod_ssl.c>
|
||||
<VirtualHost *:443>
|
||||
# The ServerName directive sets the request scheme, hostname and port that
|
||||
# the server uses to identify itself. This is used when creating
|
||||
# redirection URLs. In the context of virtual hosts, the ServerName
|
||||
# specifies what hostname must appear in the request's Host: header to
|
||||
# match this virtual host. For the default virtual host (this file) this
|
||||
# value is not decisive as it is used as a last resort host regardless.
|
||||
# However, you must set it for any further virtual host explicitly.
|
||||
|
||||
ServerName <%= @hostname %>
|
||||
ServerAdmin <%= @email %>
|
||||
|
||||
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
|
||||
# error, crit, alert, emerg.
|
||||
# It is also possible to configure the loglevel for particular
|
||||
# modules, e.g.
|
||||
#LogLevel info ssl:warn
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
# include a line for only one particular virtual host. For example the
|
||||
# following line enables the CGI configuration for this host only
|
||||
# after it has been globally disabled with "a2disconf".
|
||||
#Include conf-available/serve-cgi-bin.conf
|
||||
|
||||
<Location />
|
||||
AuthType shibboleth
|
||||
ShibRequestSetting requireSession On
|
||||
|
||||
<%- if @x_remote_user -%>
|
||||
RequestHeader set X-Remote-User %{REMOTE_USER}s
|
||||
<%- elsif @single_user -%>
|
||||
RequestHeader set X-Remote-User soc-user
|
||||
<%- else -%>
|
||||
ShibUseHeaders On
|
||||
<%- end -%>
|
||||
|
||||
AuthGroupFile /etc/apache2/groups.txt
|
||||
Require group <% @groups.each.with_index do |group, i| %><%= group %><%= ' ' if i < (@groups.size - 1) %><% end %>
|
||||
</Location>
|
||||
<%- @passthrough.each do |path| -%>
|
||||
|
||||
<Location <%= path %>>
|
||||
AuthType None
|
||||
Require all granted
|
||||
</Location>
|
||||
<%- end -%>
|
||||
|
||||
ProxyPass "/" "<%= @service_endpoint %>/"
|
||||
ProxyPassReverse "/" "<%= @service_endpoint %>/"
|
||||
UseCanonicalName On
|
||||
ProxyPreserveHost On
|
||||
|
||||
ServerAlias <%= @hostname %>
|
||||
SSLCertificateFile /etc/letsencrypt/live/<%= @hostname %>/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/<%= @hostname %>/privkey.pem
|
||||
Include /etc/apache2/ssl.conf
|
||||
</VirtualHost>
|
||||
</IfModule>
|
|
@ -0,0 +1,23 @@
|
|||
version: "3"
|
||||
services:
|
||||
sso:
|
||||
container_name: sso
|
||||
image: "docker.sunet.se/apache-shib"
|
||||
ports:
|
||||
- "443:443"
|
||||
networks:
|
||||
- sso
|
||||
volumes:
|
||||
- /etc/letsencrypt:/etc/letsencrypt
|
||||
- ./apache/site.conf:/etc/apache2/sites-enabled/site.conf
|
||||
- ./apache/ssl.conf:/etc/apache2/ssl.conf
|
||||
- ./apache/groups.txt:/etc/apache2/groups.txt
|
||||
- ./shibboleth/shibboleth2.xml:/etc/shibboleth/shibboleth2.xml
|
||||
- ./shibboleth/shibd.logger:/etc/shibboleth/shibd.logger
|
||||
- ./shibboleth/attribute-map.xml:/etc/shibboleth/attribute-map.xml
|
||||
- ./shibboleth/md-signer2.crt:/etc/shibboleth/md-signer2.crt
|
||||
- ./shibboleth/sp-cert.pem:/etc/shibboleth/sp-cert.pem
|
||||
- ./shibboleth/sp-key.pem:/etc/shibboleth/sp-key.pem
|
||||
networks:
|
||||
sso:
|
||||
external: true
|
140
global/overlay/etc/puppet/soc/templates/sso/shibboleth2.xml.erb
Normal file
140
global/overlay/etc/puppet/soc/templates/sso/shibboleth2.xml.erb
Normal file
|
@ -0,0 +1,140 @@
|
|||
<SPConfig xmlns="urn:mace:shibboleth:3.0:native:sp:config"
|
||||
xmlns:conf="urn:mace:shibboleth:3.0:native:sp:config"
|
||||
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||
clockSkew="180">
|
||||
|
||||
<!--
|
||||
By default, in-memory StorageService, ReplayCache, ArtifactMap, and SessionCache
|
||||
are used. See example-shibboleth2.xml for samples of explicitly configuring them.
|
||||
-->
|
||||
|
||||
<!-- The ApplicationDefaults element is where most of Shibboleth's SAML bits are defined. -->
|
||||
<ApplicationDefaults entityID="https://<%= @hostname %>"
|
||||
REMOTE_USER="eppn subject-id"
|
||||
metadataAttributePrefix="Meta-">
|
||||
|
||||
<!--
|
||||
Controls session lifetimes, address checks, cookie handling, and the protocol handlers.
|
||||
You MUST supply an effectively unique handlerURL value for each of your applications.
|
||||
The value defaults to /Shibboleth.sso, and should be a relative path, with the SP computing
|
||||
a relative value based on the virtual host. Using handlerSSL="true", the default, will force
|
||||
the protocol to be https. You should also set cookieProps to "https" for SSL-only sites.
|
||||
Note that while we default checkAddress to "false", this has a negative impact on the
|
||||
security of your site. Stealing sessions via cookie theft is much easier with this disabled.
|
||||
-->
|
||||
<Sessions lifetime="28800" timeout="3600" relayState="ss:mem"
|
||||
checkAddress="false" handlerSSL="true" cookieProps="https">
|
||||
|
||||
<!--
|
||||
Configures SSO for a default IdP. To allow for >1 IdP, remove
|
||||
entityID property and adjust discoveryURL to point to discovery service.
|
||||
If you use this, you have to remove the SessionInitiator below.
|
||||
<SSO entityID="https://swamididp.example.org
|
||||
discoveryProtocol="SAMLDS" discoveryURL="https://ds.example.org/DS/WAYF">
|
||||
SAML2 SAML1
|
||||
</SSO>
|
||||
-->
|
||||
|
||||
<!-- SAML and local-only logout. -->
|
||||
|
||||
<Logout>SAML2 Local</Logout>
|
||||
<SessionInitiator type="Chaining" Location="/DS/Login" id="swamid-ds-default" relayState="cookie">
|
||||
<SessionInitiator type="SAML2" defaultACSIndex="1" acsByIndex="false" template="bindingTemplate.html"/>
|
||||
<SessionInitiator type="Shib1" defaultACSIndex="5"/>
|
||||
<%- if @swamid_testing -%>
|
||||
<SessionInitiator type="SAMLDS" URL="https://ds-test.swamid.se/role/idp.ds"/>
|
||||
<%- else -%>
|
||||
<SessionInitiator type="SAMLDS" URL="https://service.seamlessaccess.org/ds"/>
|
||||
<%- end -%>
|
||||
</SessionInitiator>
|
||||
|
||||
<!--
|
||||
md:AssertionConsumerService locations handle specific SSO protocol bindings,
|
||||
such as SAML 2.0 POST or SAML 1.1 Artifact. The isDefault and index attributes
|
||||
are used when sessions are initiated to determine how to tell the IdP where and
|
||||
how to return the response.
|
||||
-->
|
||||
<md:AssertionConsumerService Location="/SAML2/POST" index="1"
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
conf:ignoreNoPassive="true"/>
|
||||
|
||||
<!-- Extension service that generates "approximate" metadata based on SP configuration. -->
|
||||
<Handler type="MetadataGenerator" Location="/Metadata" signing="false"/>
|
||||
|
||||
<!-- Status reporting service. -->
|
||||
<Handler type="Status" Location="/Status" acl="127.0.0.1 ::1"/>
|
||||
|
||||
<!-- Session diagnostic service. -->
|
||||
<Handler type="Session" Location="/Session" showAttributeValues="false"/>
|
||||
|
||||
<!-- JSON feed of discovery information. -->
|
||||
<Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
|
||||
|
||||
<!--
|
||||
md:ArtifactResolutionService locations resolve artifacts issued when using the
|
||||
SAML 2.0 HTTP-Artifact binding on outgoing messages, generally uses SOAP.
|
||||
-->
|
||||
<md:ArtifactResolutionService Location="/Artifact/SOAP" index="1"
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"/>
|
||||
|
||||
</Sessions>
|
||||
|
||||
<!--
|
||||
Allows overriding of error template information/filenames. You can
|
||||
also add attributes with values that can be plugged into the templates.
|
||||
-->
|
||||
<Errors supportContact="<%= @email %>"
|
||||
helpLocation="/about.html"
|
||||
styleSheet="/shibboleth-sp/main.css"/>
|
||||
|
||||
<!-- Example of remotely supplied batch of signed metadata. -->
|
||||
|
||||
<!-- SWAMID Metadata -->
|
||||
<%- if @swamid_testing -%>
|
||||
<MetadataProvider
|
||||
type="XML"
|
||||
url="https://mds.swamid.se/md/swamid-testing-idp-1.0.xml"
|
||||
backingFilePath="swamid-testing-idp-1.0.xml" reloadInterval="300">
|
||||
<MetadataFilter type="Signature" certificate="md-signer2.crt"/>
|
||||
</MetadataProvider>
|
||||
<%- elsif @satosa -%>
|
||||
<MetadataProvider
|
||||
type="XML"
|
||||
path="frontend.xml"/>
|
||||
<%- else -%>
|
||||
<MetadataProvider
|
||||
type="XML"
|
||||
url="http://mds.swamid.se/md/swamid-idp.xml"
|
||||
backingFilePath="swamid-idp.xml" reloadInterval="300">
|
||||
<MetadataFilter type="Signature" certificate="md-signer2.crt"/>
|
||||
</MetadataProvider>
|
||||
<%- end -%>
|
||||
|
||||
<!-- Example of locally maintained metadata. -->
|
||||
<!--
|
||||
<MetadataProvider type="XML" file="partner-metadata.xml"/>
|
||||
-->
|
||||
|
||||
<!-- Map to extract attributes from SAML assertions. -->
|
||||
<AttributeExtractor type="XML" validate="true" reloadChanges="false" path="attribute-map.xml"/>
|
||||
|
||||
<!-- Use a SAML query if no attributes are supplied during SSO. -->
|
||||
<AttributeResolver type="Query" subjectMatch="true"/>
|
||||
|
||||
<!-- Default filtering policy for recognized attributes, lets other data pass. -->
|
||||
<AttributeFilter type="XML" validate="true" path="attribute-policy.xml"/>
|
||||
|
||||
<!-- Simple file-based resolver for using a single keypair. -->
|
||||
<CredentialResolver type="File" key="sp-key.pem" certificate="sp-cert.pem"/>
|
||||
|
||||
</ApplicationDefaults>
|
||||
|
||||
<!-- Policies that determine how to process and authenticate runtime messages. -->
|
||||
<SecurityPolicyProvider type="XML" validate="true" path="security-policy.xml"/>
|
||||
|
||||
<!-- Low-level configuration about protocols and bindings available for use. -->
|
||||
<ProtocolProvider type="XML" validate="true" reloadChanges="false" path="protocols.xml"/>
|
||||
|
||||
</SPConfig>
|
72
global/overlay/etc/puppet/soc/templates/sso/shibd.logger.erb
Normal file
72
global/overlay/etc/puppet/soc/templates/sso/shibd.logger.erb
Normal file
|
@ -0,0 +1,72 @@
|
|||
# set overall behavior
|
||||
log4j.rootCategory=INFO, shibd_log, warn_log
|
||||
|
||||
# fairly verbose for DEBUG, so generally leave at INFO
|
||||
log4j.category.XMLTooling.XMLObject=INFO
|
||||
log4j.category.XMLTooling.KeyInfoResolver=INFO
|
||||
log4j.category.Shibboleth.IPRange=INFO
|
||||
log4j.category.Shibboleth.PropertySet=INFO
|
||||
|
||||
# raise for low-level tracing of SOAP client HTTP/SSL behavior
|
||||
log4j.category.XMLTooling.libcurl=INFO
|
||||
|
||||
# useful categories to tune independently:
|
||||
#
|
||||
# tracing of SAML messages and security policies
|
||||
#log4j.category.OpenSAML.MessageDecoder=DEBUG
|
||||
#log4j.category.OpenSAML.MessageEncoder=DEBUG
|
||||
#log4j.category.OpenSAML.SecurityPolicyRule=DEBUG
|
||||
#log4j.category.XMLTooling.SOAPClient=DEBUG
|
||||
# interprocess message remoting
|
||||
#log4j.category.Shibboleth.Listener=DEBUG
|
||||
# mapping of requests to applicationId
|
||||
#log4j.category.Shibboleth.RequestMapper=DEBUG
|
||||
# high level session cache operations
|
||||
#log4j.category.Shibboleth.SessionCache=DEBUG
|
||||
# persistent storage and caching
|
||||
#log4j.category.XMLTooling.StorageService=DEBUG
|
||||
|
||||
# logs XML being signed or verified if set to DEBUG
|
||||
log4j.category.XMLTooling.Signature.Debugger=INFO, sig_log
|
||||
log4j.additivity.XMLTooling.Signature.Debugger=false
|
||||
log4j.ownAppenders.XMLTooling.Signature.Debugger=true
|
||||
|
||||
# the tran log blocks the "default" appender(s) at runtime
|
||||
# Level should be left at INFO for this category
|
||||
log4j.category.Shibboleth-TRANSACTION=<%= @translog %>, tran_log
|
||||
log4j.additivity.Shibboleth-TRANSACTION=false
|
||||
log4j.ownAppenders.Shibboleth-TRANSACTION=true
|
||||
|
||||
# uncomment to suppress particular event types
|
||||
#log4j.category.Shibboleth-TRANSACTION.AuthnRequest=WARN
|
||||
#log4j.category.Shibboleth-TRANSACTION.Login=WARN
|
||||
#log4j.category.Shibboleth-TRANSACTION.Logout=WARN
|
||||
|
||||
# define the appenders
|
||||
|
||||
log4j.appender.shibd_log=org.apache.log4j.RollingFileAppender
|
||||
log4j.appender.shibd_log.fileName=/var/log/shibboleth/shibd.log
|
||||
log4j.appender.shibd_log.maxFileSize=1000000
|
||||
log4j.appender.shibd_log.maxBackupIndex=10
|
||||
log4j.appender.shibd_log.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.shibd_log.layout.ConversionPattern=%d{%Y-%m-%d %H:%M:%S} %p %c %x: %m%n
|
||||
|
||||
log4j.appender.warn_log=org.apache.log4j.RollingFileAppender
|
||||
log4j.appender.warn_log.fileName=/var/log/shibboleth/shibd_warn.log
|
||||
log4j.appender.warn_log.maxFileSize=1000000
|
||||
log4j.appender.warn_log.maxBackupIndex=10
|
||||
log4j.appender.warn_log.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.warn_log.layout.ConversionPattern=%d{%Y-%m-%d %H:%M:%S} %p %c %x: %m%n
|
||||
log4j.appender.warn_log.threshold=WARN
|
||||
|
||||
log4j.appender.tran_log=org.apache.log4j.RollingFileAppender
|
||||
log4j.appender.tran_log.fileName=/var/log/shibboleth/transaction.log
|
||||
log4j.appender.tran_log.maxFileSize=1000000
|
||||
log4j.appender.tran_log.maxBackupIndex=20
|
||||
log4j.appender.tran_log.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.tran_log.layout.ConversionPattern=%d{%Y-%m-%d %H:%M:%S}|%c|%m%n
|
||||
|
||||
log4j.appender.sig_log=org.apache.log4j.FileAppender
|
||||
log4j.appender.sig_log.fileName=/var/log/shibboleth/signature.log
|
||||
log4j.appender.sig_log.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.sig_log.layout.ConversionPattern=%m
|
Loading…
Add table
Reference in a new issue