From c95f1099a053d27d74331c450d0dd5e24a8cb7d3 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Wed, 23 Oct 2019 15:54:32 -0700 Subject: [PATCH] Added some Okta work. This lets Ansible become the source of truth soon. Need to work the api module to exact search rather then a fuzzy search. Started to get back into docker to maintain homelab. --- .vscode/settings.json | 8 + ansible.cfg | 9 +- dev.yml | 46 -- inventory/home.yaml | 32 ++ modules/okta_groups.py | 317 ++++++++++++ modules/okta_users.py | 455 ++++++++++++++++++ playbook/{linux => }/docker/InstallDocker.yml | 0 playbook/docker/mediaserver/hydra.yml | 47 ++ playbook/docker/mediaserver/vars.yml | 12 + playbook/linux/docker/deployOwnCloud.yml | 12 - playbook/localhost/okta/debug.yml | 41 ++ playbook/localhost/okta/debugGroupResult.yml | 18 + playbook/localhost/okta/debugUserResult.yml | 32 ++ playbook/localhost/okta/grouptest.yml | 13 + playbook/localhost/okta/okta_role.yml | 39 ++ playbook/localhost/okta/okta_vars.yml | 25 + playbook/localhost/readme.md | 7 + playbook/macos/installDevTools.yml | 1 + roles/luther38.okta/defaults/main.yml | 12 + roles/luther38.okta/tasks/main.yml | 90 ++++ 20 files changed, 1155 insertions(+), 61 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 dev.yml create mode 100644 inventory/home.yaml create mode 100644 modules/okta_groups.py create mode 100644 modules/okta_users.py rename playbook/{linux => }/docker/InstallDocker.yml (100%) create mode 100644 playbook/docker/mediaserver/hydra.yml create mode 100644 playbook/docker/mediaserver/vars.yml delete mode 100644 playbook/linux/docker/deployOwnCloud.yml create mode 100644 playbook/localhost/okta/debug.yml create mode 100644 playbook/localhost/okta/debugGroupResult.yml create mode 100644 playbook/localhost/okta/debugUserResult.yml create mode 100644 playbook/localhost/okta/grouptest.yml create mode 100644 playbook/localhost/okta/okta_role.yml create mode 100644 playbook/localhost/okta/okta_vars.yml create mode 100644 playbook/localhost/readme.md create mode 100644 roles/luther38.okta/defaults/main.yml create mode 100644 roles/luther38.okta/tasks/main.yml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ad63129 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "cSpell.ignoreWords": [ + "ansible", + "okta", + "specialcounsel", + "vault" + ] +} \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg index 73f9df6..e65f6b8 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -11,8 +11,11 @@ # some basic default values... -inventory = ./hosts -library = /usr/share/my_modules/ +inventory = ./inventory/ +#library = /usr/share/my_modules/:./modules/ + +# Looks like modules can only be pointed to a single directory +library = ./modules/ module_utils = /usr/share/my_module_utils/ remote_tmp = ~/.ansible/tmp local_tmp = ~/.ansible/tmp @@ -255,7 +258,7 @@ vault_password_file = ./.ansible_vault # You can enable this feature by setting retry_files_enabled to True # and you can change the location of the files by setting retry_files_save_path -#retry_files_enabled = False +retry_files_enabled = False #retry_files_save_path = ~/.ansible-retry # squash actions diff --git a/dev.yml b/dev.yml deleted file mode 100644 index 7296023..0000000 --- a/dev.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -all: - hosts: - dmi-dev01: - children: - linux: - hosts: - 172.20.0.142: - vars: - ansible_user: ansible - ansible_connection: ssh - ansible_password: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 33353561613531336536313335356236643530346538373638653330306636386435633965336136 - 3464366635373661383466333464663238663565343839310a376666386237313566386235633739 - 66323434636365303335326133396137393031396531313533326263363066636237313139353538 - 6134616232336365610a363033343639616563316330363966326330646162613034633532366230 - 3439 - ansible_become_method: sudo - ansible_become_pass: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 33353561613531336536313335356236643530346538373638653330306636386435633965336136 - 3464366635373661383466333464663238663565343839310a376666386237313566386235633739 - 66323434636365303335326133396137393031396531313533326263363066636237313139353538 - 6134616232336365610a363033343639616563316330363966326330646162613034633532366230 - 3439 - ansible_python_interpreter: /usr/bin/python3 - windows: - hosts: - dmi-dev01: - vars: - ansible_user: svcansible - ansible_password: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 33353561613531336536313335356236643530346538373638653330306636386435633965336136 - 3464366635373661383466333464663238663565343839310a376666386237313566386235633739 - 66323434636365303335326133396137393031396531313533326263363066636237313139353538 - 6134616232336365610a363033343639616563316330363966326330646162613034633532366230 - 3439 - ansible_connection: winrm - ansible_port: 5985 - ansible_winrm_scheme: http - ansible_winrm_transport: ntlm - ansible_winrm_server_cert_validation: ignore - - diff --git a/inventory/home.yaml b/inventory/home.yaml new file mode 100644 index 0000000..39a6d0a --- /dev/null +++ b/inventory/home.yaml @@ -0,0 +1,32 @@ +--- +all: + hosts: + + children: + linux: + 192.160.0.60 + vars: + ansible_user: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 33393963653363383638373235363531386238626138366632336531663437373634333766656437 + 3462613266323931343431616465623439626534646238640a616461633434626631376266383234 + 61386535373738386632626331353665643137373234323566326633386234666534616330306639 + 3739633437353337380a373437643563663163653834653637363861663639363635333866613435 + 6636 + ansible_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 62386164373234666130346666653866663136623261386364636632303430616135343132303631 + 3632316465666237613232313761353135316433376562360a633931646238326139336532373633 + 32666636326332346536366165373030346265626633646335303033636337333336323164636338 + 3036393466633634640a313262373661623731623639343565653061663830303537666136346165 + 3834 + ansible_connection: ssh + ansible_become_method: sudo + ansible_become_pass: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 62386164373234666130346666653866663136623261386364636632303430616135343132303631 + 3632316465666237613232313761353135316433376562360a633931646238326139336532373633 + 32666636326332346536366165373030346265626633646335303033636337333336323164636338 + 3036393466633634640a313262373661623731623639343565653061663830303537666136346165 + 3834 + ansible_python_interpreter: /usr/bin/python3 diff --git a/modules/okta_groups.py b/modules/okta_groups.py new file mode 100644 index 0000000..7d5991f --- /dev/null +++ b/modules/okta_groups.py @@ -0,0 +1,317 @@ +#!/usr/bin/python +# (c) 2019, Whitney Champion +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +module: okta_groups +short_description: Communicate with the Okta API to manage groups +description: + - The Okta groups module manages Okta groups +version_added: "1.0" +author: "Whitney Champion (@shortstack)" +options: + organization: + description: + - Okta subdomain for your organization. (i.e. + mycompany.okta.com). + required: false + default: None + api_key: + description: + - Okta API key. + required: false + default: None + action: + description: + - Action to take against groups API. + required: false + default: list + choices: [ create, update, delete, list, add_user, remove_user ] + id: + description: + - ID of the group. + required: false + default: None + name: + description: + - Group name. + required: false + default: None + description: + description: + - Group description. + required: false + default: yes + limit: + description: + - List limit. + required: false + default: 200 + user_id: + description: + - ID of user to add to group. + required: false + default: None +""" + +EXAMPLES = ''' +# List groups +- okta_groups: + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + limit: 200 + +# Create group +- okta_groups: + action: create + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + name: "Imaginary Creatures" + description: "They are so majestic" + +# Update group +- okta_groups: + action: update + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM457" + name: "Imaginary Creatures" + description: "They are so majestic and beautiful" + +# Add user to group +- okta_groups: + action: add_user + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM457" + user_id: "01c5pEucucMPWXjFM456" + +# Remove user from group +- okta_groups: + action: remove_user + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM457" + user_id: "01c5pEucucMPWXjFM456" + +# Delete group +- okta_groups: + action: delete + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM457" +''' + +RETURN = r''' +json: + description: The JSON response from the Okta API + returned: always + type: complex +msg: + description: The HTTP message from the request + returned: always + type: str + sample: OK (unknown bytes) +status: + description: The HTTP status code from the request + returned: always + type: int + sample: 200 +url: + description: The actual URL used for the request + returned: always + type: str + sample: https://www.ansible.com/ +''' + +def create(module,base_url,api_key,name,description): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + payload = {} + profile = {} + + if name is not None: + profile['name'] = name + if description is not None: + profile['description'] = description + + payload['profile'] = profile + + url = base_url + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='POST', data=module.jsonify(payload)) + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def update(module,base_url,api_key,id,name,description): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + payload = {} + profile = {} + + if name is not None: + profile['name'] = name + if description is not None: + profile['description'] = description + + payload['profile'] = profile + + url = base_url+"/%s" % (id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='PUT', data=module.jsonify(payload)) + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def delete(module,base_url,api_key,id): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s" % (id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='DELETE') # delete + + if info['status'] != 204: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def list(module,base_url,api_key,limit): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/?limit=%s" % (limit) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='GET') + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def add_user(module,base_url,api_key,id,user_id): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s/users/%s" % (id,user_id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='PUT') + + if info['status'] != 204: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def remove_user(module,base_url,api_key,id,user_id): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s/users/%s" % (id,user_id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='DELETE') + + if info['status'] != 204: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def main(): + module = AnsibleModule( + argument_spec = dict( + organization = dict(type='str', default=None), + api_key = dict(type='str', no_log=True), + action = dict(type='str', default='list', choices=['create', 'update', 'delete', 'list', 'add_user', 'remove_user']), + id = dict(type='str', default=None), + user_id = dict(type='str', default=None), + name = dict(type='str', default=None), + description = dict(type='str', default=None), + limit = dict(type='int', default=200) + ) + ) + + organization = module.params['organization'] + api_key = module.params['api_key'] + action = module.params['action'] + id = module.params['id'] + user_id = module.params['user_id'] + name = module.params['name'] + description = module.params['description'] + limit = module.params['limit'] + + base_url = "https://%s-admin.okta.com/api/v1/groups" % (organization) + + if action == "create": + status, message, content, url = create(module,base_url,api_key,name,description) + elif action == "update": + status, message, content, url = update(module,base_url,api_key,id,name,description) + elif action == "delete": + status, message, content, url = delete(module,base_url,api_key,id) + elif action == "list": + status, message, content, url = list(module,base_url,api_key,limit) + elif action == "add_user": + status, message, content, url = add_user(module,base_url,api_key,id,user_id) + elif action == "remove_user": + status, message, content, url = remove_user(module,base_url,api_key,id,user_id) + + uresp = {} + content = to_text(content, encoding='UTF-8') + + try: + js = json.loads(content) + except ValueError, e: + js = "" + + uresp['json'] = js + uresp['status'] = status + uresp['msg'] = message + uresp['url'] = url + + module.exit_json(**uresp) + +# import module snippets +import json +from ansible.module_utils.basic import * +from ansible.module_utils.urls import * + +if __name__ == '__main__': + main() diff --git a/modules/okta_users.py b/modules/okta_users.py new file mode 100644 index 0000000..7e4eb48 --- /dev/null +++ b/modules/okta_users.py @@ -0,0 +1,455 @@ +#!/usr/bin/python +# (c) 2019, Whitney Champion +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +module: okta_users +short_description: Communicate with the Okta API to manage users +description: + - The Okta user module manages Okta users +version_added: "1.0" +author: "Whitney Champion (@shortstack)" +options: + organization: + description: + - Okta subdomain for your organization. (i.e. + mycompany.okta.com). + required: false + default: None + api_key: + description: + - Okta API key. + required: false + default: None + action: + description: + - Action to take against user API. + required: false + default: list + choices: [ create, update, delete, list, activate, deactivate ] + id: + description: + - ID of the user. + required: false + default: None + login: + description: + - Username. + required: false + default: None + activate: + description: + - Whether or not the new user is activate. + required: false + default: yes + password: + description: + - Password. + required: false + default: None + first_name: + description: + - First name. + required: false + default: None + last_name: + description: + - Last name. + required: false + default: None + email: + description: + - Email. + required: false + default: None + group_ids: + description: + - List of Group IDs to add the user to. + required: false + default: None + limit: + description: + - List limit. + required: false + default: 25 +""" + +EXAMPLES = ''' +# List users +- okta_users: + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + limit: 25 + +# Create user +- okta_users: + action: create + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + login: "whitney@unicorns.lol" + first_name: "Whitney" + last_name: "Champion" + email: "whitney@unicorns.lol" + password: "cookiesaredelicious" + activate: yes + +# Create user in group(s) +- okta_users: + action: create + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + login: "whitney@unicorns.lol" + first_name: "Whitney" + last_name: "Champion" + email: "whitney@unicorns.lol" + password: "cookiesaredelicious" + group_ids: + - "00f5b3gqiLpE114tV2M7" + activate: yes + +# Create multiple users in group +- okta_users: + action: create + organization: "crypto" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + login: "{{ item.login }}" + first_name: "{{ item.first_name }}" + last_name: "{{ item.last_name }}" + email: "{{ item.email }}" + password: "{{ item.password }}" + group_ids: + - "00f5b3gqiLpE324tV2M7" + activate: "{{ item.activate }}" + with_items: + - { login: "alice@aol.com", first_name: "Alice", last_name: "A", email: "alice@aolcom", password: "ilovebob111", activate: yes } + - { login: "bob@aol.com", first_name: "Bob", last_name: "B", email: "bob@aolcom", password: "ilovealice111", activate: yes } + +# Update user's email address +- okta_users: + action: update + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM456" + email: "whitney@ihateunicorns.lol" + +# Activate user +- okta_users: + action: activate + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM456" + +# Deactivate user +- okta_users: + action: deactivate + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM456" + +# Delete user +- okta_users: + action: delete + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM456" + +# Get a list of groups a user is in +- okta_users: + action: usergroups + organization: "unicorns" + api_key: "TmHvH4LY9HH9MDRDiLChLGwhRjHsarTCBzpwbua3ntnQ" + id: "01c5pEucucMPWXjFM456" + + +''' + +RETURN = r''' +json: + description: The JSON response from the Okta API + returned: always + type: complex +msg: + description: The HTTP message from the request + returned: always + type: str + sample: OK (unknown bytes) +status: + description: The HTTP status code from the request + returned: always + type: int + sample: 200 +url: + description: The actual URL used for the request + returned: always + type: str + sample: https://www.ansible.com/ +''' + +def create(module,base_url,api_key,login,password_input,email,first_name,last_name,group_ids,activate): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + payload = {} + profile = {} + credentials = {} + password = {} + groupIds= [] + + if first_name is not None: + profile['firstName'] = first_name + if last_name is not None: + profile['lastName'] = last_name + if email is not None: + profile['email'] = email + if login is not None: + profile['login'] = login + if password_input is not None: + password['value'] = password_input + if group_ids is not None: + groupIds = group_ids + + credentials['password'] = password + payload['credentials'] = credentials + payload['groupIds'] = groupIds + payload['profile'] = profile + + url = base_url+"?activate=%s" % (activate) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='POST', data=module.jsonify(payload)) + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def update(module,base_url,api_key,id,login,email,first_name,last_name): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s" % (id) + + payload = {} + profile = {} + + if first_name is not None: + profile['firstName'] = first_name + if last_name is not None: + profile['lastName'] = last_name + if email is not None: + profile['email'] = email + if login is not None: + profile['login'] = login + + payload['profile'] = profile + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='POST', data=module.jsonify(payload)) + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def delete(module,base_url,api_key,id): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s" % (id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='DELETE') + + if info['status'] != 204: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def activate(module,base_url,api_key,id): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s/lifecycle/activate" % (id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='POST') + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def deactivate(module,base_url,api_key,id): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/%s/lifecycle/deactivate" % (id) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='POST') + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def list(module,base_url,api_key,limit): + + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + + url = base_url+"/?limit=%s" % (limit) + + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='GET') + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def findByLogin(module, base_url, api_key, login): + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + #url = base_url+"?q=%s&limit=1" % (login) + url = base_url+"?filter=profile.login+eq+\"%s\"" % (login) + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='GET') + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + return info['status'], info['msg'], content, url + +def findByName(module, base_url, api_key, Name): + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + url = base_url+"?q=%s&limit=1" % (Name) + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='GET') + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + return info['status'], info['msg'], content, url + +def getusergroups(module, base_url, api_key, id): + headers = '{ "Content-Type": "application/json", "Authorization": "SSWS %s", "Accept": "application/json" }' % (api_key) + url = base_url+"/%s/groups" % (id) + response, info = fetch_url(module=module, url=url, headers=json.loads(headers), method='GET') + + if info['status'] != 200: + module.fail_json(msg="Fail: %s" % ( "Status: "+str(info['msg']) + ", Message: " + str(info['body']))) + + try: + content = response.read() + except AttributeError: + content = info.pop('body', '') + + return info['status'], info['msg'], content, url + +def main(): + module = AnsibleModule( + argument_spec = dict( + organization = dict(type='str', default=None), + api_key = dict(type='str', no_log=True), + action = dict(type='str', default='list', choices=['create', 'update', 'delete', 'list', 'activate', 'deactivate', 'usergroups']), + id = dict(type='str', default=None), + login = dict(type='str', default=None), + password = dict(type='str', default=None, no_log=True), + first_name = dict(type='str', default=None), + last_name = dict(type='str', default=None), + email = dict(type='str', default=None), + group_ids = dict(type='list', default=None), + limit = dict(type='int', default=25), + activate = dict(type='bool', default='yes') + ) + ) + + organization = module.params['organization'] + api_key = module.params['api_key'] + action = module.params['action'] + id = module.params['id'] + login = module.params['login'] + password = module.params['password'] + first_name = module.params['first_name'] + last_name = module.params['last_name'] + email = module.params['email'] + group_ids = module.params['group_ids'] + limit = module.params['limit'] + activate = module.params['activate'] + + base_url = "https://%s-admin.okta.com/api/v1/users" % (organization) + + if action == "create": + status, message, content, url = create(module,base_url,api_key,login,password,email,first_name,last_name,group_ids,activate) + elif action == "update": + status, message, content, url = update(module,base_url,api_key,id,login,email,first_name,last_name) + elif action == "delete": + status, message, content, url = deactivate(module,base_url,api_key,id) + status, message, content, url = delete(module,base_url,api_key,id) + elif action == "activate": + status, message, content, url = activate(module,base_url,api_key,id) + elif action == "deactivate": + status, message, content, url = deactivate(module,base_url,api_key,id) + elif action == "list": + if login is not None: + status, message, content, url = findByLogin(module,base_url,api_key,login) + elif first_name is not None: + status, message, content, url = findByName(module,base_url,api_key, first_name) + elif last_name is not None: + status, message, content, url = findByName(module,base_url,api_key,last_name) + else: + status, message, content, url = list(module,base_url,api_key,limit) + elif action == "usergroups": + status, message, content, url = getusergroups(module, base_url, api_key, id) + + uresp = {} + content = to_text(content, encoding='UTF-8') + + try: + js = json.loads(content) + except ValueError, e: + js = "" + + uresp['json'] = js + uresp['status'] = status + uresp['msg'] = message + uresp['url'] = url + + module.exit_json(**uresp) + +# import module snippets +import json +from ansible.module_utils.basic import * +from ansible.module_utils.urls import * + +if __name__ == '__main__': + main() diff --git a/playbook/linux/docker/InstallDocker.yml b/playbook/docker/InstallDocker.yml similarity index 100% rename from playbook/linux/docker/InstallDocker.yml rename to playbook/docker/InstallDocker.yml diff --git a/playbook/docker/mediaserver/hydra.yml b/playbook/docker/mediaserver/hydra.yml new file mode 100644 index 0000000..1380c94 --- /dev/null +++ b/playbook/docker/mediaserver/hydra.yml @@ -0,0 +1,47 @@ +--- +# https://github.com/linuxserver/docker-hydra2 +- name: Media Server + hosts: localhost + vars_files: + - vars.yml + + tasks: + - name: pip docker + pip: + name: docker + + - name: Hydra Config + docker_volume: + name: "{{ hydra_config }}" + state: present + + - name: Volume Shared Downloads + docker_volume: + name: "{{ shared_downloads }}" + state: present + + - name: Hydra Network + docker_network: + name: "{{ network }}" + + - name: stop hydra + docker_container: + name: hydra + state: stopped + + - name: Make Hydra + docker_container: + name: hydra + image: linuxserver/hydra2 + state: started + env: + PUID=1000 + PGID=1000 + TZ=Europe/London + ports: + - 5076:5076 + volumes: + - "{{ hydra_config }}:/config" + - "{{ shared_downloads }}:/downloads" + networks: + - name: "{{ network }}" \ No newline at end of file diff --git a/playbook/docker/mediaserver/vars.yml b/playbook/docker/mediaserver/vars.yml new file mode 100644 index 0000000..f9b6e10 --- /dev/null +++ b/playbook/docker/mediaserver/vars.yml @@ -0,0 +1,12 @@ +--- +# Volumes +sonarr_data: sonarr_data +sonarr_config: sonarr_config + +osx_sonarr_data: ~/doc + +hydra_config: hydra_config +shared_downloads: media_downloads + +# Networks +network: net_hydra \ No newline at end of file diff --git a/playbook/linux/docker/deployOwnCloud.yml b/playbook/linux/docker/deployOwnCloud.yml deleted file mode 100644 index 30093c0..0000000 --- a/playbook/linux/docker/deployOwnCloud.yml +++ /dev/null @@ -1,12 +0,0 @@ - -- name: Deploy OwnCloud - hosts: linux - become_method: sudo - - tasks: - - name: Check if docker is installed. - apt: - name: docker - state: present - - \ No newline at end of file diff --git a/playbook/localhost/okta/debug.yml b/playbook/localhost/okta/debug.yml new file mode 100644 index 0000000..067a3c2 --- /dev/null +++ b/playbook/localhost/okta/debug.yml @@ -0,0 +1,41 @@ +--- +- name: maintain okta users + hosts: localhost + vars_files: + - okta_vars.yml + + vars: + first_name: API + last_name: Test + email: "{{ email_deleteme }}" + login: "{{ email_deleteme }}" + isActive: True + + tasks: + - name: Check for {{ login }} + okta_users: + organization: "{{ org }}" + api_key: "{{ api }}" + action: list + login: "{{ login }}" + register: oktalist + + - name: debug + debug: + msg: "{{ oktalist }}" + + + + # if the account is not found, make it + #- name: Create {{ login }} + # okta_users: + ## organization: "{{ organization }}" + # api_key: "{{ api_key }}" +# action: create +# login: "{{ login }}" +# email: "{{ email }}" +# first_name: "{{ first_name }}" +# last_name: "{{ last_name }}" +# when: +# - oktalist['json'] is not defined +# - isActive|bool == True \ No newline at end of file diff --git a/playbook/localhost/okta/debugGroupResult.yml b/playbook/localhost/okta/debugGroupResult.yml new file mode 100644 index 0000000..cbca8d7 --- /dev/null +++ b/playbook/localhost/okta/debugGroupResult.yml @@ -0,0 +1,18 @@ +--- +- name: Debug Okta Groups + hosts: localhost + vars_files: okta_vars.yml + + tasks: + - name: Get Okta Group + okta_groups: + organization: "{{ org }}" + api_key: "{{ api }}" + action: list + limit: 200 + register: res + + - name: show res + debug: + msg: "{{ item.profile.name }} {{ item.id }}" + loop: "{{ res.json }}" diff --git a/playbook/localhost/okta/debugUserResult.yml b/playbook/localhost/okta/debugUserResult.yml new file mode 100644 index 0000000..744d76b --- /dev/null +++ b/playbook/localhost/okta/debugUserResult.yml @@ -0,0 +1,32 @@ + +- name: Show okta_users result + hosts: localhost + vars_files: okta_vars.yml + + #include_vars: okta_vars.yml + + tasks: + - name: list 1 + okta_users: + organization: "{{ org }}" + api_key: "{{ api }}" + action: list + email: "{{ email_deleteme }}" + #limit: 1 + register: res + + - name: display result + debug: + msg: "{{ res.json }}" + + - name: user groups + okta_users: + organization: "{{ org }}" + api_key: "{{ api }}" + action: usergroups + id: "{{ res.json.0.id }}" + register: grps + + - name: display groups + debug: + msg: "{{ grps }}" diff --git a/playbook/localhost/okta/grouptest.yml b/playbook/localhost/okta/grouptest.yml new file mode 100644 index 0000000..9ef98e9 --- /dev/null +++ b/playbook/localhost/okta/grouptest.yml @@ -0,0 +1,13 @@ +--- +- name: Group Test + hosts: localhost + vars_files: okta_vars.yml + + tasks: + - name: add group + okta_groups: + organization: "{{ org }}" + api_key: "{{ api }}" + action: add_user + id: "00g1jfcegwGw1Lsuh357" + user_id: 00u1kekdg8ewrmdmt357 \ No newline at end of file diff --git a/playbook/localhost/okta/okta_role.yml b/playbook/localhost/okta/okta_role.yml new file mode 100644 index 0000000..08b90ed --- /dev/null +++ b/playbook/localhost/okta/okta_role.yml @@ -0,0 +1,39 @@ + +- name: maintain okta users + hosts: localhost + vars_files: + - okta_vars.yml + + tasks: + - name: manage delete.me + include_role: + name: luther38.okta + vars: + organization: "{{ org }}" + api_key: "{{ api }}" + first_name: API + last_name: Test + email: "{{ email_deleteme }}" + login: "{{ email_deleteme }}" + isActive: True + add_groups: + #- 00g1jfcegwGw1Lsuh357 + remove_groups: + - 00g1jfcegwGw1Lsuh357 + + - name: delete me2 + include_role: + name: luther38.okta + vars: + organization: "{{ org }}" + api_key: "{{ api }}" + first_name: API + last_name: Test2 + email: "{{ email_deleteme2 }}" + login: "{{ email_deleteme2 }}" + isActive: True + add_groups: + #- 00g1jfcegwGw1Lsuh357 + remove_groups: + - 00g1jfcegwGw1Lsuh357 + diff --git a/playbook/localhost/okta/okta_vars.yml b/playbook/localhost/okta/okta_vars.yml new file mode 100644 index 0000000..dd6adb5 --- /dev/null +++ b/playbook/localhost/okta/okta_vars.yml @@ -0,0 +1,25 @@ +org: specialcounsel +api: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 38653364346561346262373864383039383062363465656531393561313162313764363230616461 + 3861383930646331366132626132346430313238366439320a363636363938363431646631316362 + 30663937343631353430326161616331373539343064613663393862653364643838623335303639 + 6561326532373939630a633134323530303632653733663039323238363961373832386638666535 + 61646234656263643439613135303934323264303235313737656238366462633365333762646162 + 6461376235383531643464396638323162373364373433353934 + +email_deleteme: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 61326435326361386537303861313332643434316433643737356434333836346435626361396539 + 6536373565343363366433343931643363653465636264320a363466646337633138663134666263 + 64613133383336653132366663393734343135633932323731326164653137323936366432613765 + 3737653131353635370a656339366330373761326135653739313731613230383561306431646165 + 36656339616330393539336563343663303638626337336564306234376161383634 + +email_demeteme2: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 39646663353734646332613430656433356265623235383931636334653463393030653733653963 + 6638326232613733613462363133663833323532653939620a646239303535626462636331616562 + 36353836393333333664386131376139323662336538373435653939626565363837356234306531 + 6539386434623366320a303438643832353732393833353434363662326635666430613333636431 + 61366265643632363939666534613138623262626230613739373833363538383030 \ No newline at end of file diff --git a/playbook/localhost/readme.md b/playbook/localhost/readme.md new file mode 100644 index 0000000..ef871b5 --- /dev/null +++ b/playbook/localhost/readme.md @@ -0,0 +1,7 @@ +# localhost + +The playbooks found here are not working against another host to maintain the configuration. They only need the ansible host that contains the playbook and some modules. + +## Okta + +These playbooks work with Okta's REST API so you do not need to jump into another server. diff --git a/playbook/macos/installDevTools.yml b/playbook/macos/installDevTools.yml index 7030dff..5237409 100644 --- a/playbook/macos/installDevTools.yml +++ b/playbook/macos/installDevTools.yml @@ -48,6 +48,7 @@ #TODO The job seems to stop here for some reason - name: remove cask - dotnet-sdk + become: true homebrew_cask: name: dotnet-sdk state: absent diff --git a/roles/luther38.okta/defaults/main.yml b/roles/luther38.okta/defaults/main.yml new file mode 100644 index 0000000..9c9238b --- /dev/null +++ b/roles/luther38.okta/defaults/main.yml @@ -0,0 +1,12 @@ +--- +organization: '' +api_key: '' + +first_name: '' +last_name: '' +email: '' +login: '' +status: '' + +add_groups: +remove_groups: \ No newline at end of file diff --git a/roles/luther38.okta/tasks/main.yml b/roles/luther38.okta/tasks/main.yml new file mode 100644 index 0000000..9403fae --- /dev/null +++ b/roles/luther38.okta/tasks/main.yml @@ -0,0 +1,90 @@ +--- +- name: Check for {{ login }} + okta_users: + organization: "{{ organization }}" + api_key: "{{ api_key }}" + action: list + login: "{{ login }}" + register: oktalist + +# if the account is not found, make it +- name: Create {{ login }} + okta_users: + organization: "{{ organization }}" + api_key: "{{ api_key }}" + action: create + login: "{{ login }}" + email: "{{ email }}" + first_name: "{{ first_name }}" + last_name: "{{ last_name }}" + when: + - oktalist['json'] is not defined + - isActive|bool == True + +- name: Update {{ login }} first_name + okta_users: + organization: "{{ organization }}" + api_key: "{{ api_key }}" + action: update + id: "{{ oktalist.json.0.id }}" + first_name: "{{ first_name }}" + when: + - oktalist.json.0.profile.firstName != first_name + - isActive|bool == True + +- name: Update {{ login }} last_name + okta_users: + organization: "{{ organization }}" + api_key: "{{ api_key }}" + action: update + id: "{{ oktalist.json.0.id }}" + last_name: "{{ last_name }}" + when: + - oktalist.json.0.profile.lastName != last_name + - isActive|bool == True + +- name: Update {{ login }} email + okta_users: + organization: "{{ organization }}" + api_key: "{{ api_key }}" + action: update + id: "{{ oktalist.json.0.id }}" + email: "{{ email }}" + when: + - oktalist.json.0.profile.email != email + - isActive|bool == True + +- name: Disable {{ login }} + okta_users: + organization: "{{ organization }}" + api_key: "{{ api_key }}" + action: deactivate + id: "{{ oktalist.json.0.id }}" + when: + - oktalist.json is defined + - isActive|bool == False + +- name: debug + debug: + msg: "{{ item }}" + with_items: "{{ add_groups }}" + +- name: add groups + okta_groups: + action: add_user + organization: "{{ organization }}" + api_key: "{{ api_key }}" + user_id: "{{ oktalist.json.0.id }}" + id: "{{ item }}" + with_items: "{{ add_groups }}" + ignore_errors: yes + +- name: remove groups + okta_groups: + action: remove_user + organization: "{{ organization }}" + api_key: "{{ api_key }}" + user_id: "{{ oktalist.json.0.id }}" + id: "{{ item }}" + with_items: "{{ remove_groups }}" + ignore_errors: yes \ No newline at end of file