diff --git a/apps/accounts/automations/change_secret/host/aix/main.yml b/apps/accounts/automations/change_secret/host/aix/main.yml index 3e3daae7f..4bb571f62 100644 --- a/apps/accounts/automations/change_secret/host/aix/main.yml +++ b/apps/accounts/automations/change_secret/host/aix/main.yml @@ -18,18 +18,18 @@ - name: remove jumpserver ssh key ansible.builtin.lineinfile: - dest: "{{ kwargs.dest }}" - regexp: "{{ kwargs.regexp }}" + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" state: absent when: - account.secret_type == "ssh_key" - - kwargs.strategy == "set_jms" + - ssh_params.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" - exclusive: "{{ kwargs.exclusive }}" + exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - name: Refresh connection diff --git a/apps/accounts/automations/change_secret/host/posix/main.yml b/apps/accounts/automations/change_secret/host/posix/main.yml index 932f3cade..8dea25c12 100644 --- a/apps/accounts/automations/change_secret/host/posix/main.yml +++ b/apps/accounts/automations/change_secret/host/posix/main.yml @@ -18,18 +18,18 @@ - name: remove jumpserver ssh key ansible.builtin.lineinfile: - dest: "{{ kwargs.dest }}" - regexp: "{{ kwargs.regexp }}" + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" state: absent when: - account.secret_type == "ssh_key" - - kwargs.strategy == "set_jms" + - ssh_params.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" - exclusive: "{{ kwargs.exclusive }}" + exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - name: Refresh connection diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py index 7bad9e5f6..05e2b1349 100644 --- a/apps/accounts/automations/change_secret/manager.py +++ b/apps/accounts/automations/change_secret/manager.py @@ -42,7 +42,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): def method_type(cls): return AutomationTypes.change_secret - def get_kwargs(self, account, secret, secret_type): + def get_ssh_params(self, account, secret, secret_type): kwargs = {} if secret_type != SecretType.SSH_KEY: return kwargs @@ -111,6 +111,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): print(f'Windows {asset} does not support ssh key push') return inventory_hosts + host['ssh_params'] = {} for account in accounts: h = deepcopy(host) secret_type = account.secret_type @@ -129,7 +130,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): private_key_path = self.generate_private_key_path(new_secret, path_dir) new_secret = self.generate_public_key(new_secret) - h['kwargs'] = self.get_kwargs(account, new_secret, secret_type) + h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type)) h['account'] = { 'name': account.name, 'username': account.username, diff --git a/apps/accounts/automations/methods.py b/apps/accounts/automations/methods.py index be5890701..557202184 100644 --- a/apps/accounts/automations/methods.py +++ b/apps/accounts/automations/methods.py @@ -1,30 +1,6 @@ import os -import copy -from accounts.const import AutomationTypes from assets.automations.methods import get_platform_automation_methods - -def copy_change_secret_to_push_account(methods): - push_account = AutomationTypes.push_account - change_secret = AutomationTypes.change_secret - copy_methods = copy.deepcopy(methods) - for method in copy_methods: - if not method['id'].startswith(change_secret): - continue - copy_method = copy.deepcopy(method) - copy_method['method'] = push_account.value - copy_method['id'] = copy_method['id'].replace( - change_secret, push_account - ) - copy_method['name'] = copy_method['name'].replace( - 'Change secret', 'Push account' - ) - methods.append(copy_method) - return methods - - BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -automation_methods = get_platform_automation_methods(BASE_DIR) - -platform_automation_methods = copy_change_secret_to_push_account(automation_methods) +platform_automation_methods = get_platform_automation_methods(BASE_DIR) diff --git a/apps/accounts/automations/push_account/database/mongodb/main.yml b/apps/accounts/automations/push_account/database/mongodb/main.yml new file mode 100644 index 000000000..42ccd78ea --- /dev/null +++ b/apps/accounts/automations/push_account/database/mongodb/main.yml @@ -0,0 +1,58 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test MongoDB connection + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + ssl: "{{ jms_asset.spec_info.use_ssl }}" + ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}" + ssl_certfile: "{{ jms_asset.secret_info.client_key }}" + connection_options: + - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" + register: db_info + + - name: Display MongoDB version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change MongoDB password + mongodb_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + ssl: "{{ jms_asset.spec_info.use_ssl }}" + ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}" + ssl_certfile: "{{ jms_asset.secret_info.client_key }}" + connection_options: + - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" + db: "{{ jms_asset.spec_info.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + mongodb_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + ssl: "{{ jms_asset.spec_info.use_ssl }}" + ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}" + ssl_certfile: "{{ jms_asset.secret_info.client_key }}" + connection_options: + - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/accounts/automations/push_account/database/mongodb/manifest.yml b/apps/accounts/automations/push_account/database/mongodb/manifest.yml new file mode 100644 index 000000000..6c91977bf --- /dev/null +++ b/apps/accounts/automations/push_account/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_mongodb +name: Push account for MongoDB +category: database +type: + - mongodb +method: push_account diff --git a/apps/accounts/automations/push_account/database/mysql/main.yml b/apps/accounts/automations/push_account/database/mysql/main.yml new file mode 100644 index 000000000..26858c94e --- /dev/null +++ b/apps/accounts/automations/push_account/database/mysql/main.yml @@ -0,0 +1,43 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + db_name: "{{ jms_asset.spec_info.db_name }}" + + tasks: + - name: Test MySQL connection + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version + register: db_info + + - name: MySQL version + debug: + var: db_info.version.full + + - name: Change MySQL password + community.mysql.mysql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + host: "%" + priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + community.mysql.mysql_info: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version + when: + - db_info is succeeded + - change_info is succeeded \ No newline at end of file diff --git a/apps/accounts/automations/push_account/database/mysql/manifest.yml b/apps/accounts/automations/push_account/database/mysql/manifest.yml new file mode 100644 index 000000000..712d0bfb8 --- /dev/null +++ b/apps/accounts/automations/push_account/database/mysql/manifest.yml @@ -0,0 +1,7 @@ +id: push_account_mysql +name: Push account for MySQL +category: database +type: + - mysql + - mariadb +method: push_account diff --git a/apps/accounts/automations/push_account/database/oracle/main.yml b/apps/accounts/automations/push_account/database/oracle/main.yml new file mode 100644 index 000000000..ad58e0584 --- /dev/null +++ b/apps/accounts/automations/push_account/database/oracle/main.yml @@ -0,0 +1,44 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test Oracle connection + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + mode: "{{ jms_account.mode }}" + register: db_info + + - name: Display Oracle version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change Oracle password + oracle_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + mode: "{{ jms_account.mode }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + oracle_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/accounts/automations/push_account/database/oracle/manifest.yml b/apps/accounts/automations/push_account/database/oracle/manifest.yml new file mode 100644 index 000000000..f60215892 --- /dev/null +++ b/apps/accounts/automations/push_account/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_oracle +name: Push account for Oracle +category: database +type: + - oracle +method: push_account diff --git a/apps/accounts/automations/push_account/database/postgresql/main.yml b/apps/accounts/automations/push_account/database/postgresql/main.yml new file mode 100644 index 000000000..dbb11af12 --- /dev/null +++ b/apps/accounts/automations/push_account/database/postgresql/main.yml @@ -0,0 +1,46 @@ +- hosts: postgre + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test PostgreSQL connection + community.postgresql.postgresql_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.spec_info.db_name }}" + register: result + failed_when: not result.is_available + + - name: Display PostgreSQL version + debug: + var: result.server_version.full + when: result is succeeded + + - name: Change PostgreSQL password + community.postgresql.postgresql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.spec_info.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + role_attr_flags: LOGIN + when: result is succeeded + register: change_info + + - name: Verify password + community.postgresql.postgresql_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.spec_info.db_name }}" + when: + - result is succeeded + - change_info is succeeded + register: result + failed_when: not result.is_available diff --git a/apps/accounts/automations/push_account/database/postgresql/manifest.yml b/apps/accounts/automations/push_account/database/postgresql/manifest.yml new file mode 100644 index 000000000..6488ddd5a --- /dev/null +++ b/apps/accounts/automations/push_account/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_postgresql +name: Push account for PostgreSQL +category: database +type: + - postgresql +method: push_account diff --git a/apps/accounts/automations/push_account/database/sqlserver/main.yml b/apps/accounts/automations/push_account/database/sqlserver/main.yml new file mode 100644 index 000000000..da0427f5c --- /dev/null +++ b/apps/accounts/automations/push_account/database/sqlserver/main.yml @@ -0,0 +1,69 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test SQLServer connection + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: | + SELECT @@version + register: db_info + + - name: SQLServer version + set_fact: + info: + version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}" + - debug: + var: info + + - name: Check whether SQLServer User exist + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';" + when: db_info is succeeded + register: user_exist + + - name: Change SQLServer password + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version" + when: user_exist.query_results[0] | length != 0 + register: change_info + + - name: Add SQLServer user + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version" + when: user_exist.query_results[0] | length == 0 + register: change_info + + - name: Verify password + community.general.mssql_script: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: | + SELECT @@version + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/accounts/automations/push_account/database/sqlserver/manifest.yml b/apps/accounts/automations/push_account/database/sqlserver/manifest.yml new file mode 100644 index 000000000..f1dc32b66 --- /dev/null +++ b/apps/accounts/automations/push_account/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_sqlserver +name: Push account for SQLServer +category: database +type: + - sqlserver +method: push_account diff --git a/apps/accounts/automations/push_account/host/aix/main.yml b/apps/accounts/automations/push_account/host/aix/main.yml new file mode 100644 index 000000000..9ac68d20e --- /dev/null +++ b/apps/accounts/automations/push_account/host/aix/main.yml @@ -0,0 +1,93 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Test privileged account + ansible.builtin.ping: + + - name: Push user + ansible.builtin.user: + name: "{{ account.username }}" + shell: "{{ params.shell }}" + home: "{{ '/home/' + account.username }}" + groups: "{{ params.groups }}" + expires: -1 + state: present + + - name: "Add {{ account.username }} group" + ansible.builtin.group: + name: "{{ account.username }}" + state: present + + - name: Check home dir exists + ansible.builtin.stat: + path: "{{ '/home/' + account.username }}" + register: home_existed + + - name: Set home dir permission + ansible.builtin.file: + path: "{{ '/home/' + account.username }}" + owner: "{{ account.username }}" + group: "{{ account.username }}" + mode: "0700" + when: + - home_existed.stat.exists == true + + - name: Add user groups + ansible.builtin.user: + name: "{{ account.username }}" + groups: "{{ params.groups }}" + when: params.groups + + - name: Push user password + ansible.builtin.user: + name: "{{ account.username }}" + password: "{{ account.secret | password_hash('sha512') }}" + update_password: always + when: account.secret_type == "password" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" + state: absent + when: + - account.secret_type == "ssh_key" + - ssh_params.strategy == "set_jms" + + - name: Push SSH key + ansible.builtin.authorized_key: + user: "{{ account.username }}" + key: "{{ account.secret }}" + exclusive: "{{ ssh_params.exclusive }}" + when: account.secret_type == "ssh_key" + + - name: Set sudo setting + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^{{ account.username }} ALL=" + line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" + validate: visudo -cf %s + when: + - params.sudo + + - name: Refresh connection + ansible.builtin.meta: reset_connection + + - name: Verify password + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + ansible_become: no + when: account.secret_type == "password" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: account.secret_type == "ssh_key" diff --git a/apps/accounts/automations/push_account/host/aix/manifest.yml b/apps/accounts/automations/push_account/host/aix/manifest.yml new file mode 100644 index 000000000..ccc051eac --- /dev/null +++ b/apps/accounts/automations/push_account/host/aix/manifest.yml @@ -0,0 +1,24 @@ +id: push_account_aix +name: Push account for aix +category: host +type: + - AIX +method: push_account +params: + - name: sudo + type: str + label: 'Sudo' + default: '/bin/whoami' + help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig' + + - name: shell + type: str + label: 'Shell' + default: '/bin/bash' + + - name: groups + type: str + label: '用户组' + default: '' + help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)' + diff --git a/apps/accounts/automations/push_account/host/posix/main.yml b/apps/accounts/automations/push_account/host/posix/main.yml new file mode 100644 index 000000000..9ac68d20e --- /dev/null +++ b/apps/accounts/automations/push_account/host/posix/main.yml @@ -0,0 +1,93 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Test privileged account + ansible.builtin.ping: + + - name: Push user + ansible.builtin.user: + name: "{{ account.username }}" + shell: "{{ params.shell }}" + home: "{{ '/home/' + account.username }}" + groups: "{{ params.groups }}" + expires: -1 + state: present + + - name: "Add {{ account.username }} group" + ansible.builtin.group: + name: "{{ account.username }}" + state: present + + - name: Check home dir exists + ansible.builtin.stat: + path: "{{ '/home/' + account.username }}" + register: home_existed + + - name: Set home dir permission + ansible.builtin.file: + path: "{{ '/home/' + account.username }}" + owner: "{{ account.username }}" + group: "{{ account.username }}" + mode: "0700" + when: + - home_existed.stat.exists == true + + - name: Add user groups + ansible.builtin.user: + name: "{{ account.username }}" + groups: "{{ params.groups }}" + when: params.groups + + - name: Push user password + ansible.builtin.user: + name: "{{ account.username }}" + password: "{{ account.secret | password_hash('sha512') }}" + update_password: always + when: account.secret_type == "password" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" + state: absent + when: + - account.secret_type == "ssh_key" + - ssh_params.strategy == "set_jms" + + - name: Push SSH key + ansible.builtin.authorized_key: + user: "{{ account.username }}" + key: "{{ account.secret }}" + exclusive: "{{ ssh_params.exclusive }}" + when: account.secret_type == "ssh_key" + + - name: Set sudo setting + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^{{ account.username }} ALL=" + line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" + validate: visudo -cf %s + when: + - params.sudo + + - name: Refresh connection + ansible.builtin.meta: reset_connection + + - name: Verify password + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + ansible_become: no + when: account.secret_type == "password" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: account.secret_type == "ssh_key" diff --git a/apps/accounts/automations/push_account/host/posix/manifest.yml b/apps/accounts/automations/push_account/host/posix/manifest.yml new file mode 100644 index 000000000..382b48add --- /dev/null +++ b/apps/accounts/automations/push_account/host/posix/manifest.yml @@ -0,0 +1,25 @@ +id: push_account_posix +name: Push account for posix +category: host +type: + - unix + - linux +method: push_account +params: + - name: sudo + type: str + label: 'Sudo' + default: '/bin/whoami' + help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig' + + - name: shell + type: str + label: 'Shell' + default: '/bin/bash' + help_text: '' + + - name: groups + type: str + label: '用户组' + default: '' + help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)' diff --git a/apps/accounts/automations/push_account/host/windows/main.yml b/apps/accounts/automations/push_account/host/windows/main.yml new file mode 100644 index 000000000..8a2a0aef0 --- /dev/null +++ b/apps/accounts/automations/push_account/host/windows/main.yml @@ -0,0 +1,30 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Test privileged account + ansible.windows.win_ping: + +# - name: Print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.secret }}" + + - name: Push user password + ansible.windows.win_user: + fullname: "{{ account.username}}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + password_never_expires: yes + groups: "{{ params.groups }}" + groups_action: add + update_password: always + when: account.secret_type == "password" + + - name: Refresh connection + ansible.builtin.meta: reset_connection + + - name: Verify password + ansible.windows.win_ping: + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + when: account.secret_type == "password" diff --git a/apps/accounts/automations/push_account/host/windows/manifest.yml b/apps/accounts/automations/push_account/host/windows/manifest.yml new file mode 100644 index 000000000..05e3127f9 --- /dev/null +++ b/apps/accounts/automations/push_account/host/windows/manifest.yml @@ -0,0 +1,13 @@ +id: push_account_local_windows +name: Push account local account for Windows +version: 1 +method: push_account +category: host +type: + - windows +params: + - name: groups + type: str + label: '用户组' + default: 'Users,Remote Desktop Users' + help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)' diff --git a/apps/accounts/automations/push_account/manager.py b/apps/accounts/automations/push_account/manager.py index 92fde0b37..fe117f018 100644 --- a/apps/accounts/automations/push_account/manager.py +++ b/apps/accounts/automations/push_account/manager.py @@ -31,6 +31,7 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager): print(msg) return inventory_hosts + host['ssh_params'] = {} for account in accounts: h = deepcopy(host) secret_type = account.secret_type @@ -49,7 +50,7 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager): private_key_path = self.generate_private_key_path(new_secret, path_dir) new_secret = self.generate_public_key(new_secret) - h['kwargs'] = self.get_kwargs(account, new_secret, secret_type) + h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type)) h['account'] = { 'name': account.name, 'username': account.username, diff --git a/apps/accounts/models/automations/push_account.py b/apps/accounts/models/automations/push_account.py index f189a5fbd..d3a14411f 100644 --- a/apps/accounts/models/automations/push_account.py +++ b/apps/accounts/models/automations/push_account.py @@ -51,7 +51,8 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation): def to_attr_json(self): attr_json = super().to_attr_json() attr_json.update({ - 'username': self.username + 'username': self.username, + 'params': self.params, }) return attr_json diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 8bd903149..213672679 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -27,13 +27,16 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): push_now = serializers.BooleanField( default=False, label=_("Push now"), write_only=True ) + params = serializers.JSONField( + decoder=None, encoder=None, required=False, style={'base_template': 'textarea.html'} + ) on_invalid = LabeledChoiceField( choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR, write_only=True, label=_('Exist policy') ) class Meta: - fields = ['template', 'push_now', 'on_invalid'] + fields = ['template', 'push_now', 'params', 'on_invalid'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -93,10 +96,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): initial_data.update(attrs) @staticmethod - def push_account_if_need(instance, push_now, stat): + def push_account_if_need(instance, push_now, params, stat): if not push_now or stat != 'created': return - push_accounts_to_assets_task.delay([str(instance.id)]) + push_accounts_to_assets_task.delay([str(instance.id)], params) def get_validators(self): _validators = super().get_validators() @@ -147,8 +150,9 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): def create(self, validated_data): push_now = validated_data.pop('push_now', None) + params = validated_data.pop('params', None) instance, stat = self.do_create(validated_data) - self.push_account_if_need(instance, push_now, stat) + self.push_account_if_need(instance, push_now, params, stat) return instance def update(self, instance, validated_data): @@ -156,9 +160,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): validated_data.pop('username', None) validated_data.pop('on_invalid', None) push_now = validated_data.pop('push_now', None) + params = validated_data.pop('params', None) validated_data['source_id'] = None instance = super().update(instance, validated_data) - self.push_account_if_need(instance, push_now, 'updated') + self.push_account_if_need(instance, push_now, params, 'updated') return instance diff --git a/apps/accounts/serializers/automations/push_account.py b/apps/accounts/serializers/automations/push_account.py index b9982300b..1d7bb3d36 100644 --- a/apps/accounts/serializers/automations/push_account.py +++ b/apps/accounts/serializers/automations/push_account.py @@ -7,9 +7,10 @@ from .change_secret import ( class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer): + class Meta(ChangeSecretAutomationSerializer.Meta): model = PushAccountAutomation - fields = [ + fields = ['params'] + [ n for n in ChangeSecretAutomationSerializer.Meta.fields if n not in ['recipients'] ] diff --git a/apps/accounts/tasks/push_account.py b/apps/accounts/tasks/push_account.py index 2a753cc1a..623481be9 100644 --- a/apps/accounts/tasks/push_account.py +++ b/apps/accounts/tasks/push_account.py @@ -15,7 +15,7 @@ __all__ = [ queue="ansible", verbose_name=_('Push accounts to assets'), activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None) ) -def push_accounts_to_assets_task(account_ids): +def push_accounts_to_assets_task(account_ids, params=None): from accounts.models import PushAccountAutomation from accounts.models import Account @@ -26,6 +26,7 @@ def push_accounts_to_assets_task(account_ids): task_snapshot = { 'accounts': [str(account.id) for account in accounts], 'assets': [str(account.asset_id) for account in accounts], + 'params': params or {}, } tp = AutomationTypes.push_account diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 5d5d4523c..61f4db985 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -1,10 +1,16 @@ +from rest_framework import generics +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.response import Response + from assets.const import AllTypes -from assets.models import Platform +from assets.models import Platform, Node, Asset from assets.serializers import PlatformSerializer from common.api import JMSModelViewSet +from common.permissions import IsValidUser from common.serializers import GroupedChoiceSerializer -__all__ = ['AssetPlatformViewSet'] +__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi'] class AssetPlatformViewSet(JMSModelViewSet): @@ -18,7 +24,8 @@ class AssetPlatformViewSet(JMSModelViewSet): rbac_perms = { 'categories': 'assets.view_platform', 'type_constraints': 'assets.view_platform', - 'ops_methods': 'assets.view_platform' + 'ops_methods': 'assets.view_platform', + 'filter_nodes_assets': 'assets.view_platform' } def get_queryset(self): @@ -38,3 +45,44 @@ class AssetPlatformViewSet(JMSModelViewSet): request, message={"detail": "Internal platform"} ) return super().check_object_permissions(request, obj) + + @action(methods=['post'], detail=False, url_path='filter-nodes-assets') + def filter_nodes_assets(self, request, *args, **kwargs): + node_ids = request.data.get('node_ids', []) + asset_ids = request.data.get('asset_ids', []) + nodes = Node.objects.filter(id__in=node_ids) + node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) + direct_asset_ids = Asset.objects.filter(id__in=asset_ids).values_list('id', flat=True) + platform_ids = Asset.objects.filter( + id__in=set(list(direct_asset_ids) + list(node_asset_ids)) + ).values_list('platform_id', flat=True) + platforms = Platform.objects.filter(id__in=platform_ids) + serializer = self.get_serializer(platforms, many=True) + return Response(serializer.data) + + +class PlatformAutomationMethodsApi(generics.ListAPIView): + permission_classes = (IsValidUser,) + + @staticmethod + def automation_methods(): + return AllTypes.get_automation_methods() + + def generate_serializer_fields(self): + data = self.automation_methods() + fields = { + i['id']: i['params_serializer']() + if i['params_serializer'] else None + for i in data + } + return fields + + def get_serializer_class(self): + fields = self.generate_serializer_fields() + serializer_name = 'AutomationMethodsSerializer' + return type(serializer_name, (serializers.Serializer,), fields) + + def list(self, request, *args, **kwargs): + data = self.generate_serializer_fields() + serializer = self.get_serializer(data) + return Response(serializer.data) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 63b490fd9..9207bb33e 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -1,12 +1,11 @@ import json import os import shutil -import yaml - from collections import defaultdict from hashlib import md5 from socket import gethostname +import yaml from django.conf import settings from django.utils import timezone from django.utils.translation import gettext as _ @@ -42,6 +41,26 @@ class BasePlaybookManager: self.method_hosts_mapper = defaultdict(list) self.playbooks = [] self.gateway_servers = dict() + params = self.execution.snapshot.get('params') + self.params = params or {} + + def get_params(self, automation, method_type): + method_attr = '{}_method'.format(method_type) + method_params = '{}_params'.format(method_type) + method_id = getattr(automation, method_attr) + automation_params = getattr(automation, method_params) + serializer = self.method_id_meta_mapper[method_id]['params_serializer'] + + if serializer is None: + return {} + + data = self.params.get(method_id, {}) + params = serializer(data).data + return { + field_name: automation_params.get(field_name, '') + if not params[field_name] else params[field_name] + for field_name in params + } @property def platform_automation_methods(self): @@ -102,8 +121,9 @@ class BasePlaybookManager: return host def host_callback(self, host, automation=None, **kwargs): - enabled_attr = '{}_enabled'.format(self.__class__.method_type()) - method_attr = '{}_method'.format(self.__class__.method_type()) + method_type = self.__class__.method_type() + enabled_attr = '{}_enabled'.format(method_type) + method_attr = '{}_method'.format(method_type) method_enabled = automation and \ getattr(automation, enabled_attr) and \ @@ -115,6 +135,7 @@ class BasePlaybookManager: return host host = self.convert_cert_to_file(host, kwargs.get('path_dir')) + host['params'] = self.get_params(automation, method_type) return host @staticmethod diff --git a/apps/assets/automations/methods.py b/apps/assets/automations/methods.py index e1e12fb7f..cad2b1b3c 100644 --- a/apps/assets/automations/methods.py +++ b/apps/assets/automations/methods.py @@ -22,6 +22,15 @@ def check_platform_methods(methods): raise ValueError("Duplicate id: {}".format(_id)) +def generate_serializer(data): + from common.serializers import create_serializer_class + params = data.pop('params', None) + if not params: + return None + serializer_name = data['id'].title().replace('_', '') + 'Serializer' + return create_serializer_class(serializer_name, params) + + def get_platform_automation_methods(path): methods = [] for root, dirs, files in os.walk(path, topdown=False): @@ -34,6 +43,7 @@ def get_platform_automation_methods(path): manifest = yaml.safe_load(f) check_platform_method(manifest, path) manifest['dir'] = os.path.dirname(path) + manifest['params_serializer'] = generate_serializer(manifest) methods.append(manifest) check_platform_methods(methods) diff --git a/apps/assets/migrations/0113_auto_20230411_1814.py b/apps/assets/migrations/0113_auto_20230411_1814.py new file mode 100644 index 000000000..58fd2fa92 --- /dev/null +++ b/apps/assets/migrations/0113_auto_20230411_1814.py @@ -0,0 +1,66 @@ +# Generated by Django 3.2.16 on 2023-04-11 10:14 + +from django.db import migrations, models + +from assets.const import AllTypes + + +def migrate_automation_push_account_params(apps, schema_editor): + platform_automation_model = apps.get_model('assets', 'PlatformAutomation') + platform_automation_methods = AllTypes.get_automation_methods() + methods_id_data_map = { + i['id']: None if i['params_serializer'] is None else i['params_serializer']({}).data + for i in platform_automation_methods + if i['method'] == 'push_account' + } + automation_objs = [] + for automation in platform_automation_model.objects.all(): + push_account_method = automation.push_account_method + if not push_account_method: + continue + value = methods_id_data_map.get(push_account_method) + if value is None: + continue + automation.push_account_params = value + automation_objs.append(automation) + platform_automation_model.objects.bulk_update(automation_objs, ['push_account_params']) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0112_auto_20230404_1631'), + ] + + operations = [ + migrations.AddField( + model_name='platformautomation', + name='change_secret_params', + field=models.JSONField(default=dict, verbose_name='Change secret params'), + ), + migrations.AddField( + model_name='platformautomation', + name='gather_accounts_params', + field=models.JSONField(default=dict, verbose_name='Gather facts params'), + ), + migrations.AddField( + model_name='platformautomation', + name='gather_facts_params', + field=models.JSONField(default=dict, verbose_name='Gather facts params'), + ), + migrations.AddField( + model_name='platformautomation', + name='ping_params', + field=models.JSONField(default=dict, verbose_name='Ping params'), + ), + migrations.AddField( + model_name='platformautomation', + name='push_account_params', + field=models.JSONField(default=dict, verbose_name='Push account params'), + ), + migrations.AddField( + model_name='platformautomation', + name='verify_account_params', + field=models.JSONField(default=dict, verbose_name='Verify account params'), + ), + migrations.RunPython(migrate_automation_push_account_params), + ] diff --git a/apps/assets/migrations/0114_baseautomation_params.py b/apps/assets/migrations/0114_baseautomation_params.py new file mode 100644 index 000000000..2e6d87f8e --- /dev/null +++ b/apps/assets/migrations/0114_baseautomation_params.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2023-04-13 10:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0113_auto_20230411_1814'), + ] + + operations = [ + migrations.AddField( + model_name='baseautomation', + name='params', + field=models.JSONField(default=dict, verbose_name='Params'), + ), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 16045f3f3..9c81dd0e9 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # - import json import logging from collections import defaultdict from django.db import models +from django.forms import model_to_dict from django.utils.translation import ugettext_lazy as _ from assets import const @@ -181,15 +181,8 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): } if not automation: return auto_config - auto_config.update({ - 'ping_enabled': automation.ping_enabled, - 'ansible_enabled': automation.ansible_enabled, - 'push_account_enabled': automation.push_account_enabled, - 'gather_facts_enabled': automation.gather_facts_enabled, - 'change_secret_enabled': automation.change_secret_enabled, - 'verify_account_enabled': automation.verify_account_enabled, - 'gather_accounts_enabled': automation.gather_accounts_enabled, - }) + + auto_config.update(model_to_dict(automation)) return auto_config def get_target_ip(self): diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 9ddeb5bf2..e41092fbf 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -19,6 +19,7 @@ class BaseAutomation(PeriodTaskModelMixin, JMSOrgBaseModel): assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) type = models.CharField(max_length=16, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + params = models.JSONField(default=dict, verbose_name=_("Params")) def __str__(self): return self.name + '@' + str(self.created_by) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 2eb786498..4927ce793 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -38,25 +38,40 @@ class PlatformProtocol(models.Model): class PlatformAutomation(models.Model): ansible_enabled = models.BooleanField(default=False, verbose_name=_("Enabled")) ansible_config = models.JSONField(default=dict, verbose_name=_("Ansible config")) + ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) + ping_params = models.JSONField(default=dict, verbose_name=_("Ping params")) + gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) - gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + gather_facts_method = models.TextField( + max_length=32, blank=True, null=True, verbose_name=_("Gather facts method") + ) + gather_facts_params = models.JSONField(default=dict, verbose_name=_("Gather facts params")) + change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change secret enabled")) change_secret_method = models.TextField( max_length=32, blank=True, null=True, verbose_name=_("Change secret method") ) + change_secret_params = models.JSONField(default=dict, verbose_name=_("Change secret params")) + push_account_enabled = models.BooleanField(default=False, verbose_name=_("Push account enabled")) push_account_method = models.TextField( max_length=32, blank=True, null=True, verbose_name=_("Push account method") ) + push_account_params = models.JSONField(default=dict, verbose_name=_("Push account params")) + verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) verify_account_method = models.TextField( - max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + max_length=32, blank=True, null=True, verbose_name=_("Verify account method") + ) + verify_account_params = models.JSONField(default=dict, verbose_name=_("Verify account params")) + gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) gather_accounts_method = models.TextField( max_length=32, blank=True, null=True, verbose_name=_("Gather facts method") ) + gather_accounts_params = models.JSONField(default=dict, verbose_name=_("Gather facts params")) class Platform(JMSBaseModel): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index c5d645a46..c542cc20a 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -51,12 +51,12 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): fields = [ "id", "ansible_enabled", "ansible_config", - "ping_enabled", "ping_method", - "push_account_enabled", "push_account_method", - "gather_facts_enabled", "gather_facts_method", - "change_secret_enabled", "change_secret_method", - "verify_account_enabled", "verify_account_method", - "gather_accounts_enabled", "gather_accounts_method", + "ping_enabled", "ping_method", "ping_params", + "push_account_enabled", "push_account_method", "push_account_params", + "gather_facts_enabled", "gather_facts_method", "gather_facts_params", + "change_secret_enabled", "change_secret_method", "change_secret_params", + "verify_account_enabled", "verify_account_method", "verify_account_params", + "gather_accounts_enabled", "gather_accounts_method", "gather_accounts_params", ] extra_kwargs = { # 启用资产探测 diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 174dccef1..6b5f469d0 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -46,6 +46,7 @@ urlpatterns = [ path('nodes//tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'), path('gateways//test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), + path('platform-automation-methods/', api.PlatformAutomationMethodsApi.as_view(), name='platform-automation-methods'), ] urlpatterns += router.urls diff --git a/apps/common/serializers/__init__.py b/apps/common/serializers/__init__.py index 7ecadafc7..6d79241fc 100644 --- a/apps/common/serializers/__init__.py +++ b/apps/common/serializers/__init__.py @@ -1,2 +1,3 @@ from .common import * +from .dynamic import * from .mixin import * diff --git a/apps/common/serializers/dynamic.py b/apps/common/serializers/dynamic.py index 6ad47fcd8..9ab26b9bb 100644 --- a/apps/common/serializers/dynamic.py +++ b/apps/common/serializers/dynamic.py @@ -1,7 +1,7 @@ from rest_framework import serializers example_info = [ - {"name": "name", "label": "姓名", "required": False, "default": "老广", "type": "str"}, + {"name": "name", "label": "姓名", "required": False, "default": "广州老广", "type": "str"}, {"name": "age", "label": "年龄", "required": False, "default": 18, "type": "int"}, ]