Skip to content

Instantly share code, notes, and snippets.

@juliedavila
Created September 27, 2017 14:04
Show Gist options
  • Select an option

  • Save juliedavila/fc21488e44d73cdd919f81ee1b43e204 to your computer and use it in GitHub Desktop.

Select an option

Save juliedavila/fc21488e44d73cdd919f81ee1b43e204 to your computer and use it in GitHub Desktop.
AWX SAML with Mapping
<?xml version="1.0" encoding="UTF-8"?>
<saml2:Assertion
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="id74539476266456101610211577" IssueInstant="2016-11-11T02:15:18.004Z" Version="2.0">
<saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.EXAMPLE-SAML-PROVIDER.com/Issuer</saml2:Issuer>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">userName</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData NotOnOrAfter="2016-11-11T02:20:18.004Z" Recipient="https://10.220.73.142/sso/complete/saml/"/>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="2016-11-11T02:10:18.004Z" NotOnOrAfter="2016-11-11T02:20:18.004Z">
<saml2:AudienceRestriction>
<saml2:Audience>https://tower.example.com</saml2:Audience>
</saml2:AudienceRestriction>
</saml2:Conditions>
<saml2:AuthnStatement AuthnInstant="2016-11-11T02:15:18.004Z">
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
<saml2:AttributeStatement>
<saml2:Attribute Name="User.FirstName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.firstName
</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="User.LastName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.lastName
</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="User.email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.email
</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="User.username" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.login
</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="Groups" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">GroupName Match Starts with "Ansible" (ignores case)
</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
SOCIAL_AUTH_SAML_SP_ENTITY_ID = 'https://tower.example.com'
# Create a keypair for Tower to use as a service provider (SP) and include the
# certificate and private key contents here.
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = open('/etc/tower/conf.d/saml_sp_certs/server.crt', 'r').read()
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = open('/etc/tower/conf.d/saml_sp_certs/server.key', 'r').read()
# Configure the following settings with information about your app and contact
# information.
SOCIAL_AUTH_SAML_ORG_INFO = {
'en-US': {
'name': 'Ansible Tower',
'displayname': 'Ansible Tower',
'url': 'http://www.example-org.com',
},
}
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = {
'givenName': 'Some User',
'emailAddress': 'suser@example.com',
}
SOCIAL_AUTH_SAML_SUPPORT_CONTACT = {
'givenName': 'Some User',
'emailAddress': 'suser@example.com',
}
# Configure the entity ID, SSO URL and certificate for each identity provider
# (IdP) in use. Multiple SAML IdPs are supported.
# Some IdPs may provide user data using attribute names that differ from the
# default OIDs (https://github.com/omab/python-social-auth/blob/master/social/backends/saml.py#L16).
# Attribute names may be overridden for each IdP as shown below.
SOCIAL_AUTH_SAML_ENABLED_IDPS = {
'okta': {
'entity_id': 'http://www.okta.com/dsklfdjksdjkl',
'url': 'https://example.saml-provider.com/app/skljdjlksdfkljd/sdjklfsdfklj/sso/saml',
'x509cert': open('/etc/tower/conf.d/saml_sp_certs/saml-provider.cert').read(),
'attr_user_permanent_id': 'name_id',
'attr_first_name': 'User.FirstName',
'attr_last_name': 'User.LastName',
'attr_username': 'User.username',
'attr_email': 'User.email',
},
}
# Once configuration is complete, you will need to register your SP with each
# IdP. Provide the entity ID and the following callback URL for your
# application, replacing tower.example.com with the FQDN to your Tower server:
#
# https://tower.example.com/sso/complete/saml/
#
# If your IdP allows uploading an XML metadata file, you can download one from
# your Tower installation customized with the settings above:
#
# https://tower.example.com/sso/metadata/saml/
#
# Superuser mapping
# -----------------
# SUPERUSERS: A list of SAML group names whose members should become Tower super users
# REMOVE_SUPERUSERS: True/False, if a is user is currently a super user but is not part
# of the group, remove that user's super admin permissions
SOCIAL_AUTH_SUPERUSERS = ['SOME_SAML_GROUP']
SOCIAL_AUTH_REMOVE_SUPERUSERS = True
# Organiztion and Team Mapping
# ----------------------------
# Mapping to organization admins/users from social auth accounts. This setting
# controls which users are placed into which Tower organizations based on
# their username and email address. Dictionary keys are organization names.
# organizations will be created if not present if the license allows for
# multiple organizations, otherwise the single default organization is used
# regardless of the key. Values are dictionaries defining the options for
# each organization's membership. For each organization it is possible to
# specify which users are automatically users of the organization and also
# which users can administer the organization.
#
# - admins: None, True/False, string or list/tuple of strings.
# If None, organization admins will not be updated.
# If True, all users using social auth will automatically be added as admins
# of the organization.
# If False, no social auth users will be automatically added as admins of
# the organiation.
# If a string or list of strings, specifies the usernames and emails for
# users who will be added to the organization. Compiled regular expressions
# may also be used instead of string literals.
# - remove_admins: True/False. Defaults to False.
# If True, a user who does not match will be removed from the organization's
# administrative list.
# - users: None, True/False, string or list/tuple of strings. Same rules apply
# as for admins.
# - remove_users: True/False. Defaults to False. Same rules as apply for remove_admins
SOCIAL_AUTH_ORGANIZATION_MAP = {
# Add all users to the default organization.
'Default': {
'users': True,
},
'General': {
'admins': False,
'users': ['SOME_OTHER_SAML_GROUP'],
},
#'Test Org 2': {
# 'admins': ['admin@example.com', re.compile(r'^tower-[^@]+*?@.*$],
# 'users': re.compile(r'^[^@].*?@example\.com$'),
#},
}
# Organization mappings may be specified separately for each social auth
# backend. If defined, these configurations will take precedence over the
# global configuration above.
#SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_SAML_ORGANIZATION_MAP = {}
# Mapping of team members (users) from social auth accounts. Keys are team
# names (will be created if not present). Values are dictionaries of options
# for each team's membership, where each can contain the following parameters:
# - organization: string. The name of the organization to which the team
# belongs. The team will be created if the combination of organization and
# team name does not exist. The organization will first be created if it
# does not exist. If the license does not allow for multiple organizations,
# the team will always be assigned to the single default organization.
# - users: None, True/False, string or list/tuple of strings.
# If None, team members will not be updated.
# If True/False, all social auth users will be added/removed as team
# members.
# If a string or list of strings, specifies expressions used to match users.
# User will be added as a team member if the username or email matches.
# Compiled regular expressions may also be used instead of string literals.
# - remove: True/False. Defaults to False. If True, a user who does not match
# the rules above will be removed from the team.
SOCIAL_AUTH_TEAM_MAP = {
'SOME_GROUP_NAME_FOR_TOWER': {
'organization': 'General',
'users': ['YET_ANOTHER_SAML_GROUP'],
'remove': True,
},
#'Other Team': {
# 'organization': 'Test Org 2',
# 'users': re.compile(r'^[^@]+?@test2\.example\.com$'),
# 'remove': False,
#},
}
# Team mappings may be specified separately for each social auth backend. If
# defined, these configurations will take precedence over the the global
# configuration above.
#SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP = {}
#SOCIAL_AUTH_SAML_TEAM_MAP = {}
# Uncomment the line below (i.e. set SOCIAL_AUTH_USER_FIELDS to an empty list)
# to prevent new user accounts from being created. Only users who have
# previously logged in using social auth or have a user account with a matching
# email address will be able to login.
#SOCIAL_AUTH_USER_FIELDS = []
# It is also possible to add custom functions to the social auth pipeline for
# more advanced organization and team mapping. Use at your own risk.
def custom_social_auth_pipeline_function(backend, user, response, *args, **kwargs):
from awx.api.license import feature_enabled
groups = response['attributes']['Groups']
## Super user logic
explicit_su = False
superusers = backend.setting('SUPERUSERS') or {}
remove_su = backend.setting('REMOVE_SUPERUSERS') or None
if superusers:
for su_group in superusers:
if su_group in groups:
explicit_su = True
user.is_superuser = True
break
if not explicit_su and remove_su:
user.is_superuser = False
def _update_m2m_from_expression(user, rel, expr, remove=True):
'''
Helper function to update m2m relationship based on user matching one or
more expressions.
'''
should_add = False
if expr is None:
return
elif not expr:
pass
elif expr is True:
should_add = True
else:
if isinstance(expr, basestring):
expr = [expr]
for ex in expr:
if isinstance(ex, basestring):
if ex in groups:
should_add = True
if should_add:
rel.add(user)
elif remove:
rel.remove(user)
from awx.main.models import Organization, Team
multiple_orgs = feature_enabled('multiple_organizations')
org_map = backend.setting('ORGANIZATION_MAP') or {}
for org_name, org_opts in org_map.items():
# Get or create the org to update. If the license only allows for one
# org, always use the first active org, unless no org exists.
if multiple_orgs:
org = Organization.objects.get_or_create(name=org_name)[0]
else:
try:
org = Organization.objects.order_by('pk')[0]
except IndexError:
continue
# Update org admins from expression(s).
remove = bool(org_opts.get('remove', True))
admins_expr = org_opts.get('admins', None)
remove_admins = bool(org_opts.get('remove_admins', remove))
_update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins)
# Update org users from expression(s).
users_expr = org_opts.get('users', None)
remove_users = bool(org_opts.get('remove_users', remove))
_update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users)
## Add users to teams logic
team_map = backend.setting('TEAM_MAP') or {}
for team_name, team_opts in team_map.items():
# Get or create the org to update. If the license only allows for one
# org, always use the first active org, unless no org exists.
if multiple_orgs:
if 'organization' not in team_opts:
continue
org = Organization.objects.get_or_create(name=team_opts['organization'])[0]
else:
try:
org = Organization.objects.order_by('pk')[0]
except IndexError:
continue
# Update team members from expression(s).
team = Team.objects.get_or_create(name=team_name, organization=org)[0]
users_expr = team_opts.get('users', None)
remove = bool(team_opts.get('remove', True))
_update_m2m_from_expression(user, team.member_role.members, users_expr, remove)
SOCIAL_AUTH_PIPELINE += (
'awx.settings.production.custom_social_auth_pipeline_function',
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment