.
This commit is contained in:
547
library/gitea_auth.py
Executable file
547
library/gitea_auth.py
Executable file
@@ -0,0 +1,547 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Sebastian Hamann
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: gitea_auth
|
||||
|
||||
short_description: Manage external authentication sources in Gitea
|
||||
|
||||
version_added: none
|
||||
|
||||
description:
|
||||
- "The `gitea_auth` module allows adding, updating and removing external
|
||||
authentication sources in an instance of Gitea."
|
||||
|
||||
requirements:
|
||||
- Gitea >= 1.12.0
|
||||
|
||||
notes:
|
||||
- Many options are required when adding new authentication sources. If the authentication source named as in I(name) already exists, the required options can be omitted.
|
||||
- If I(state) is C(present), this module always reports a changed result, since Gitea does not currently provide full information about configured authentication sources.
|
||||
|
||||
options:
|
||||
admin_filter:
|
||||
description:
|
||||
- An LDAP filter specifying if a user should be given administrator privileges. If a user account passes the filter, the user will be privileged as an administrator.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
auto_discover_url:
|
||||
description:
|
||||
- OpenID Connect auto discovery URL
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
bind_dn:
|
||||
description:
|
||||
- If I(type) is C(ldap): The DN to bind to the LDAP server with when searching for the user. Omit to perform an anonymous search.
|
||||
- If I(type) is C(ldap-simple): A template to use as the user's DN. The %s matching parameter will be substituted with the login name given on sign-in form.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
- Required if I(type) is C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
bind_password:
|
||||
description:
|
||||
- The password for the Bind DN specified above, if any.
|
||||
- Note: The password is stored in plaintext on the server. As such, ensure that the Bind DN has as few privileges as possible.
|
||||
- Only used if I(type) is C(ldap) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
client_id:
|
||||
description:
|
||||
- OAuth2 Client ID
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
- Required in this case.
|
||||
type: str
|
||||
required: False
|
||||
client_secret:
|
||||
description:
|
||||
- OAuth2 Client secret
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
- Required in this case.
|
||||
type: str
|
||||
required: False
|
||||
config:
|
||||
description:
|
||||
- Path to the Gitea config file (C(app.ini)).
|
||||
- The config file must contain the C(RUN_USER) setting.
|
||||
type: str
|
||||
required: False
|
||||
default: /etc/gitea/app.ini
|
||||
custom_tenant_id:
|
||||
description:
|
||||
- Use custom Tenant ID for OAuth endpoints
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
custom_auth_url:
|
||||
description:
|
||||
- Use a custom Authorization URL (option for GitLab/GitHub).
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
custom_email_url:
|
||||
description:
|
||||
- Use a custom Email URL (option for GitHub).
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
custom_profile_url:
|
||||
description:
|
||||
- Use a custom Profile URL (option for GitLab/GitHub).
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
custom_token_url:
|
||||
description:
|
||||
- Use a custom Token URL (option for GitLab/GitHub).
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
email_attribute:
|
||||
description:
|
||||
- The attribute of the user's LDAP record containing the user's email address.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
encryption:
|
||||
description:
|
||||
- Whether and how to use TLS when connecting to the LDAP server.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
- Required in this case.
|
||||
type: str
|
||||
required: False
|
||||
choices: ['disable', 'starttls', 'ldaps']
|
||||
firstname_attribute:
|
||||
description:
|
||||
- The attribute of the user's LDAP record containing the user's first name.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
host:
|
||||
description:
|
||||
- The host name of the LDAP server.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
- Required in this case.
|
||||
type: str
|
||||
required: False
|
||||
name:
|
||||
description:
|
||||
- The name of the external authentication source.
|
||||
- The name needs to be unique in the Gitea installation.
|
||||
type: str
|
||||
required: True
|
||||
port:
|
||||
description:
|
||||
- The port to use when connecting to the server.
|
||||
- Default is 636 if I(encryption) is C(ldaps) and otherwise 389.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: int
|
||||
required: False
|
||||
provider:
|
||||
description:
|
||||
- The name of an OAuth2 provider supported by Gitea. Valid names include "github", "gitlab" or "twitter", for instance.
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
- Required in this case.
|
||||
type: str
|
||||
required: False
|
||||
sshkey_attribute:
|
||||
description:
|
||||
- The attribute of the user's LDAP record containing the user's public SSH key.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
state:
|
||||
description:
|
||||
- Whether the authentication source should exist or not, taking action if the state is different from what is stated.
|
||||
type: str
|
||||
required: False
|
||||
default: 'present'
|
||||
choices: ['present', 'absent']
|
||||
surname_attribute:
|
||||
description:
|
||||
- The attribute of the user's LDAP record containing the user's surname.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
sync_users:
|
||||
description:
|
||||
- This option enables a periodic task that synchronizes the Gitea users with the LDAP server.
|
||||
- Only used if I(type) is C(ldap) and I(state) is C(present).
|
||||
type: bool
|
||||
required: False
|
||||
default: False
|
||||
type:
|
||||
description:
|
||||
- The type of external authentication provider to set up.
|
||||
- Only used if I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
choices: ['oauth', 'ldap', 'ldap-simple']
|
||||
use_custom_urls:
|
||||
description:
|
||||
- Whether to use custom URLs for GitLab/GitHub OAuth endpoints.
|
||||
- Only used if I(type) is C(oauth) and I(state) is C(present).
|
||||
type: bool
|
||||
required: False
|
||||
user_filter:
|
||||
description:
|
||||
- An LDAP filter declaring when a user should be allowed to log in. The %s matching parameter will be substituted with login name given on sign-in form.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
- Required in this case.
|
||||
type: str
|
||||
required: False
|
||||
user_search_base:
|
||||
description:
|
||||
- The LDAP base at which user accounts will be searched for.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
- Required if I(type) is C(ldap) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
username_attribute:
|
||||
description:
|
||||
- The attribute of the user's LDAP record containing the user name. The attribute value will be used for new Gitea accounts' user name after the first successful sign-in. Leave empty to use the login name given on sign-in form.
|
||||
- This is useful when the supplied login name is matched against multiple attributes, but only a single specific attribute should be used for the Gitea account name.
|
||||
- Only used if I(type) is C(ldap) or C(ldap-simple) and I(state) is C(present).
|
||||
type: str
|
||||
required: False
|
||||
|
||||
author:
|
||||
- Sebastian Hamann (@s-hamann)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create an OAuth2 authentication source
|
||||
- name: Enable login with GitHub
|
||||
gitea_auth:
|
||||
name: GitHub
|
||||
type: oauth
|
||||
provider: github
|
||||
client_id: gitea
|
||||
client_secret: some_token
|
||||
|
||||
# Create an LDAP authentication source
|
||||
- name: Enable LDAP login
|
||||
gitea_auth:
|
||||
name: OpenLDAP
|
||||
type: ldap
|
||||
host: ldap.my.domain
|
||||
encryption: starttls
|
||||
bind_dn: uid=gitea,ou=machines,dc=my,dc=domain
|
||||
bind_password: some_password
|
||||
user_search_base: ou=people,dc=my,dc=domain
|
||||
user_filter: '(&(objectClass=posixAccount)(uid=%s)(memberOf=cn=Gitea Users,ou=groups,dc=my,dc=domain))'
|
||||
admin_filter: '(memberOf=cn=Gitea Admins,ou=groups,dc=my,dc=domain)'
|
||||
username_attribute: uid
|
||||
firstname_attribute: givenName
|
||||
surname_attribute: sn
|
||||
email_attribute: mail
|
||||
sshkey_attribute: sshPublicKey
|
||||
sync_users: true
|
||||
|
||||
# Create an LDAP authentication source
|
||||
- name: Enable Active Directory login
|
||||
gitea_auth:
|
||||
name: Active Directory
|
||||
type: ldap
|
||||
host: dc.my.domain
|
||||
encryption: ldaps
|
||||
bind_dn: uid=gitea,ou=machines,dc=my,dc=domain
|
||||
bind_password: some_password
|
||||
user_search_base: ou=people,dc=my,dc=domain
|
||||
user_filter: '(&(objectCategory=Person)(memberOf=cn=Gitea Users,ou=groups,dc=my,dc=domain)(sAMAccountName=%s)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))'
|
||||
admin_filter: '(memberOf=cn=Gitea Admins,ou=groups,dc=my,dc=domain)'
|
||||
username_attribute: sAMAccountName
|
||||
firstname_attribute: givenName
|
||||
surname_attribute: sn
|
||||
email_attribute: mail
|
||||
sync_users: true
|
||||
|
||||
# Delete an authentication source
|
||||
- name: Remove login with GitHub
|
||||
gitea_auth:
|
||||
name: GitHub
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
AuthSrc = namedtuple('AuthSrc', ['id', 'name', 'type', 'enabled'])
|
||||
CommandResult = namedtuple('CommandResult', ['stdout', 'stderr', 'returncode'])
|
||||
|
||||
|
||||
def gitea_cmd(command, app_ini_path):
|
||||
"""Run the given Gitea auth command and return the output.
|
||||
|
||||
:command: The auth command to run, as a list (e.g. ['delete', '--id', '1'])
|
||||
:app_ini_path: The absolute path to the configuration file (app.ini)
|
||||
:returns: The output and return code of the given command as a named tuple
|
||||
(stdout, stderr, returncode)
|
||||
|
||||
"""
|
||||
|
||||
def become_gitea(uid, gid):
|
||||
"""Return a function that changes the uid and gid to the given
|
||||
user and group."""
|
||||
def result():
|
||||
os.setgroups([gid])
|
||||
os.setgid(gid)
|
||||
os.setuid(uid)
|
||||
return result
|
||||
|
||||
import configparser
|
||||
app_ini = configparser.ConfigParser()
|
||||
app_ini.read(app_ini_path)
|
||||
user = app_ini['DEFAULT']['RUN_USER']
|
||||
|
||||
userinfo = pwd.getpwnam(user)
|
||||
uid = userinfo.pw_uid
|
||||
gid = userinfo.pw_gid
|
||||
home = userinfo.pw_dir
|
||||
|
||||
env = os.environ.copy()
|
||||
env['HOME'] = home
|
||||
|
||||
cmd = subprocess.Popen(['gitea', '--config', app_ini_path, 'admin', 'auth'] + command,
|
||||
preexec_fn=become_gitea(uid, gid), cwd=home, env=env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = cmd.communicate()
|
||||
return CommandResult(stdout, stderr, cmd.returncode)
|
||||
|
||||
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
admin_filter=dict(type='str'),
|
||||
auto_discover_url=dict(type='str'),
|
||||
bind_dn=dict(type='str'),
|
||||
bind_password=dict(type='str', no_log=True),
|
||||
client_id=dict(type='str'),
|
||||
client_secret=dict(type='str', no_log=True),
|
||||
config=dict(type='str', default='/etc/gitea/app.ini'),
|
||||
custom_tenant_id=dict(type='str'),
|
||||
custom_auth_url=dict(type='str'),
|
||||
custom_email_url=dict(type='str'),
|
||||
custom_profile_url=dict(type='str'),
|
||||
custom_token_url=dict(type='str'),
|
||||
email_attribute=dict(type='str'),
|
||||
encryption=dict(type='str', choices=['disable', 'starttls', 'ldaps']),
|
||||
firstname_attribute=dict(type='str'),
|
||||
host=dict(type='str'),
|
||||
name=dict(type='str', required=True),
|
||||
port=dict(type='int'),
|
||||
provider=dict(type='str'),
|
||||
sshkey_attribute=dict(type='str'),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
surname_attribute=dict(type='str'),
|
||||
sync_users=dict(type='bool', default=False),
|
||||
type=dict(type='str', choices=['oauth', 'ldap', 'ldap-simple']),
|
||||
use_custom_urls=dict(type='bool'),
|
||||
user_filter=dict(type='str'),
|
||||
user_search_base=dict(type='str'),
|
||||
username_attribute=dict(type='str')
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
auth_providers = []
|
||||
|
||||
# Get the currently configured authentication sources.
|
||||
header_pos = {}
|
||||
for line in gitea_cmd(['list'], module.params['config'])[0].splitlines():
|
||||
# Gitea may print random cruft before the actual information, i.e. the
|
||||
# header may not be in the first line. Search it.
|
||||
line = line.decode().split()
|
||||
if not header_pos:
|
||||
if line[0] == 'ID':
|
||||
header_pos['id'] = line.index('ID')
|
||||
header_pos['name'] = line.index('Name')
|
||||
header_pos['type'] = line.index('Type')
|
||||
header_pos['enabled'] = line.index('Enabled')
|
||||
continue
|
||||
else:
|
||||
a = AuthSrc(id=int(line[header_pos['id']]),
|
||||
name=line[header_pos['name']],
|
||||
type=line[header_pos['type']],
|
||||
enabled=line[header_pos['enabled']].lower() == 'true'
|
||||
)
|
||||
auth_providers.append(a)
|
||||
|
||||
# Set `id` to the ID of the authentication source with the given name, if any.
|
||||
for p in auth_providers:
|
||||
if p.name == module.params['name']:
|
||||
id = p.id
|
||||
break
|
||||
else:
|
||||
id = None
|
||||
|
||||
# Sanity checks on the parameters.
|
||||
if module.params['state'] == 'present' and not module.params['type']:
|
||||
module.fail_json(rc=256, msg='type is required with state=present')
|
||||
if module.params['state'] == 'present' and id is None:
|
||||
if module.params['type'] == 'oauth':
|
||||
required_params = ['provider', 'client_id', 'client_secret']
|
||||
elif module.params['type'] == 'ldap':
|
||||
required_params = ['host', 'encryption', 'user_filter', 'user_search_base']
|
||||
elif module.params['type'] == 'ldap-simple':
|
||||
required_params = ['host', 'encryption', 'user_filter', 'bind_dn']
|
||||
missing_params = []
|
||||
for p in required_params:
|
||||
if module.params[p] is None:
|
||||
missing_params.append(p)
|
||||
if missing_params:
|
||||
msg = ('The following parameters are required: {lst}'.
|
||||
format(lst=', '.join(missing_params)))
|
||||
module.fail_json(rc=256, msg=msg)
|
||||
|
||||
if module.params['state'] == 'absent' and id is not None:
|
||||
# Delete an authentication source.
|
||||
if not module.check_mode:
|
||||
retval = gitea_cmd(['delete', '--id', str(id)], module.params['config'])
|
||||
if retval.returncode > 0:
|
||||
msg = ('Could not delete authentication source {name}'.
|
||||
format(name=module.params['name']))
|
||||
module.fail_json(msg=msg, stdout=retval.stdout, rc=retval.returncode, **result)
|
||||
result['changed'] = True
|
||||
|
||||
elif module.params['state'] == 'present':
|
||||
# Add/update an authentication source.
|
||||
if module.params['type'] == 'oauth':
|
||||
if id is None:
|
||||
cmd = ['add-oauth']
|
||||
else:
|
||||
cmd = ['update-oauth', '--id', str(id)]
|
||||
cmd += ['--name', module.params['name']]
|
||||
if module.params['provider']:
|
||||
cmd += ['--provider', module.params['provider']]
|
||||
if module.params['client_id']:
|
||||
cmd += ['--key', module.params['client_id']]
|
||||
if module.params['client_secret']:
|
||||
cmd += ['--secret', module.params['client_secret']]
|
||||
if module.params['auto_discover_url']:
|
||||
cmd += ['--auto-discover-url', module.params['auto_discover_url']]
|
||||
if module.params['use_custom_urls']:
|
||||
cmd += ['--use-custom-urls', str(module.params['use_custom_urls'])]
|
||||
if module.params['custom_tenant_id']:
|
||||
cmd += ['--custom-tenant-id', module.params['custom_tenant_id']]
|
||||
if module.params['custom_auth_url']:
|
||||
cmd += ['--custom-auth-url', module.params['custom_auth_url']]
|
||||
if module.params['custom_token_url']:
|
||||
cmd += ['--custom-token-url', module.params['custom_token_url']]
|
||||
if module.params['custom_profile_url']:
|
||||
cmd += ['--custom-profile-url', module.params['custom_profile_url']]
|
||||
if module.params['custom_email_url']:
|
||||
cmd += ['--custom-email-url', module.params['custom_email_url']]
|
||||
|
||||
elif module.params['type'] == 'ldap' or module.params['type'] == 'ldap-simple':
|
||||
if module.params['type'] == 'ldap':
|
||||
if id is None:
|
||||
cmd = ['add-ldap']
|
||||
else:
|
||||
cmd = ['update-ldap', '--id', str(id)]
|
||||
if module.params['bind_dn']:
|
||||
cmd += ['--bind-dn', module.params['bind_dn']]
|
||||
cmd += ['--attributes-in-bind']
|
||||
if module.params['bind_password']:
|
||||
cmd += ['--bind-password', module.params['bind_password']]
|
||||
if module.params['sync_users']:
|
||||
cmd += ['--synchronize-users']
|
||||
|
||||
elif module.params['type'] == 'ldap-simple':
|
||||
if id is None:
|
||||
cmd = ['add-ldap-simple']
|
||||
else:
|
||||
cmd = ['update-ldap-simple', '--id', str(id)]
|
||||
if module.params['bind_dn']:
|
||||
cmd += ['--user-dn', module.params['bind_dn']]
|
||||
|
||||
cmd += ['--name', module.params['name']]
|
||||
if module.params['host']:
|
||||
cmd += ['--host', module.params['host']]
|
||||
if module.params['port']:
|
||||
cmd += ['--port', module.params['port']]
|
||||
elif id is None:
|
||||
if module.params['encryption'] == 'ldaps':
|
||||
port = '636'
|
||||
else:
|
||||
port = '389'
|
||||
cmd += ['--port', port]
|
||||
if module.params['encryption']:
|
||||
if module.params['encryption'] == 'disable':
|
||||
encryption = 'unencrypted'
|
||||
elif module.params['encryption'] == 'starttls':
|
||||
encryption = 'StartTLS'
|
||||
elif module.params['encryption'] == 'ldaps':
|
||||
encryption = 'LDAPS'
|
||||
cmd += ['--security-protocol', encryption]
|
||||
if module.params['user_search_base']:
|
||||
cmd += ['--user-search-base', module.params['user_search_base']]
|
||||
if module.params['user_filter']:
|
||||
cmd += ['--user-filter', module.params['user_filter']]
|
||||
if module.params['admin_filter']:
|
||||
cmd += ['--admin-filter', module.params['admin_filter']]
|
||||
if module.params['username_attribute']:
|
||||
cmd += ['--username-attribute', module.params['username_attribute']]
|
||||
if module.params['firstname_attribute']:
|
||||
cmd += ['--firstname-attribute', module.params['firstname_attribute']]
|
||||
if module.params['surname_attribute']:
|
||||
cmd += ['--surname-attribute', module.params['surname_attribute']]
|
||||
if module.params['email_attribute'] or id is None:
|
||||
email_attribute = module.params['email_attribute']
|
||||
if not email_attribute:
|
||||
email_attribute = 'mail'
|
||||
cmd += ['--email-attribute', email_attribute]
|
||||
if module.params['sshkey_attribute']:
|
||||
cmd += ['--public-ssh-key-attribute', module.params['sshkey_attribute']]
|
||||
|
||||
if not module.check_mode:
|
||||
retval = gitea_cmd(cmd, module.params['config'])
|
||||
if retval.returncode > 0:
|
||||
if id is None:
|
||||
verb = 'add'
|
||||
else:
|
||||
verb = 'update'
|
||||
msg = ('Could not {verb} authentication source {name}'.
|
||||
format(verb=verb, name=module.params['name']))
|
||||
module.fail_json(msg=msg, stdout=retval.stdout, rc=retval.returncode, **result)
|
||||
|
||||
# We can not know if anything was changed, since we can not get the
|
||||
# full configuration of an authentication source out of Gitea.
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user