diff --git a/apps/accounts/automations/change_secret/custom/ssh/main.yml b/apps/accounts/automations/change_secret/custom/ssh/main.yml
new file mode 100644
index 000000000..027133496
--- /dev/null
+++ b/apps/accounts/automations/change_secret/custom/ssh/main.yml
@@ -0,0 +1,40 @@
+- hosts: custom
+  gather_facts: no
+  vars:
+    ansible_connection: local
+
+  tasks:
+    - name: Test privileged account
+      ssh_ping:
+        login_host: "{{ jms_asset.address }}"
+        login_port: "{{ jms_asset.port }}"
+        login_user: "{{ jms_account.username }}"
+        login_password: "{{ jms_account.secret }}"
+        login_secret_type: "{{ jms_account.secret_type }}"
+        login_private_key_path: "{{ jms_account.private_key_path }}"
+      register: ping_info
+
+    - name: Change asset password
+      custom_command:
+        login_user: "{{ jms_account.username }}"
+        login_password: "{{ jms_account.secret }}"
+        login_host: "{{ jms_asset.address }}"
+        login_port: "{{ jms_asset.port }}"
+        login_secret_type: "{{ jms_account.secret_type }}"
+        login_private_key_path: "{{ jms_account.private_key_path }}"
+        name: "{{ account.username }}"
+        password: "{{ account.secret }}"
+        commands: "{{ params.commands }}"
+        first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
+      when: ping_info is succeeded
+      register: change_info
+
+    - name: Verify password
+      ssh_ping:
+        login_user: "{{ account.username }}"
+        login_password: "{{ account.secret }}"
+        login_host: "{{ jms_asset.address }}"
+        login_port: "{{ jms_asset.port }}"
+      when:
+        - ping_info is succeeded
+        - change_info is succeeded
diff --git a/apps/accounts/automations/change_secret/custom/ssh/manifest.yml b/apps/accounts/automations/change_secret/custom/ssh/manifest.yml
new file mode 100644
index 000000000..2796c32c4
--- /dev/null
+++ b/apps/accounts/automations/change_secret/custom/ssh/manifest.yml
@@ -0,0 +1,14 @@
+id: change_secret_by_ssh
+name: Change secret by SSH
+category:
+  - device
+  - host
+type:
+  - all
+method: change_secret
+params:
+  - name: commands
+    type: list
+    label: '自定义命令'
+    default: ['']
+    help_text: '自定义命令中如需包含账号的 username 和 password 字段,请使用 {username}、{password}格式,执行任务时会进行替换 。
比如针对 Linux 主机进行改密,一般需要配置三条命令:
1.passwd {username} 
2.{password} 
3.{password}'
diff --git a/apps/accounts/automations/verify_account/custom/main.yml b/apps/accounts/automations/verify_account/custom/main.yml
new file mode 100644
index 000000000..6ad8cd98b
--- /dev/null
+++ b/apps/accounts/automations/verify_account/custom/main.yml
@@ -0,0 +1,14 @@
+- hosts: custom
+  gather_facts: no
+  vars:
+    ansible_connection: local
+
+  tasks:
+    - name: Verify account
+      ssh_ping:
+        login_host: "{{ jms_asset.address }}"
+        login_port: "{{ jms_asset.port }}"
+        login_user: "{{ account.username }}"
+        login_password: "{{ account.secret }}"
+        login_secret_type: "{{ jms_account.secret_type }}"
+        login_private_key_path: "{{ jms_account.private_key_path }}"
diff --git a/apps/accounts/automations/verify_account/custom/manifest.yml b/apps/accounts/automations/verify_account/custom/manifest.yml
new file mode 100644
index 000000000..51c5fedb1
--- /dev/null
+++ b/apps/accounts/automations/verify_account/custom/manifest.yml
@@ -0,0 +1,8 @@
+id: verify_account_by_ssh
+name: Verify account by SSH
+category:
+  - device
+  - host
+type:
+  - all
+method: verify_account
diff --git a/apps/accounts/serializers/automations/change_secret.py b/apps/accounts/serializers/automations/change_secret.py
index eba6cd88d..94a7dc428 100644
--- a/apps/accounts/serializers/automations/change_secret.py
+++ b/apps/accounts/serializers/automations/change_secret.py
@@ -58,6 +58,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
                 "Currently only mail sending is supported"
             )},
         }}
+
     @property
     def model_type(self):
         return AutomationTypes.change_secret
diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py
index 9207bb33e..ae9740347 100644
--- a/apps/assets/automations/base/manager.py
+++ b/apps/assets/automations/base/manager.py
@@ -54,7 +54,9 @@ class BasePlaybookManager:
         if serializer is None:
             return {}
 
-        data = self.params.get(method_id, {})
+        data = self.params.get(method_id)
+        if not data:
+            data = automation_params.get(method_id, {})
         params = serializer(data).data
         return {
             field_name: automation_params.get(field_name, '')
diff --git a/apps/assets/automations/ping/custom/main.yml b/apps/assets/automations/ping/custom/main.yml
new file mode 100644
index 000000000..50911847b
--- /dev/null
+++ b/apps/assets/automations/ping/custom/main.yml
@@ -0,0 +1,14 @@
+- hosts: custom
+  gather_facts: no
+  vars:
+    ansible_connection: local
+
+  tasks:
+    - name: Test asset connection
+      ssh_ping:
+        login_user: "{{ jms_account.username }}"
+        login_password: "{{ jms_account.secret }}"
+        login_host: "{{ jms_asset.address }}"
+        login_port: "{{ jms_asset.port }}"
+        login_secret_type: "{{ jms_account.secret_type }}"
+        login_private_key_path: "{{ jms_account.private_key_path }}"
diff --git a/apps/assets/automations/ping/custom/manifest.yml b/apps/assets/automations/ping/custom/manifest.yml
new file mode 100644
index 000000000..a67cca17d
--- /dev/null
+++ b/apps/assets/automations/ping/custom/manifest.yml
@@ -0,0 +1,8 @@
+id: ping_by_ssh
+name: Ping by SSH
+category:
+  - device
+  - host
+type:
+  - all
+method: ping
diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py
index 0f166d0ad..d5d0d8d64 100644
--- a/apps/assets/automations/ping/manager.py
+++ b/apps/assets/automations/ping/manager.py
@@ -14,8 +14,10 @@ class PingManager(BasePlaybookManager):
     def method_type(cls):
         return AutomationTypes.ping
 
-    def host_callback(self, host, asset=None, account=None, **kwargs):
-        super().host_callback(host, asset=asset, account=account, **kwargs)
+    def host_callback(self, host, asset=None, account=None, automation=None, **kwargs):
+        super().host_callback(
+            host, asset=asset, account=account, automation=automation, **kwargs
+        )
         self.host_asset_and_account_mapper[host['name']] = (asset, account)
         return host
 
diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py
index 5e8f8f879..9dfd07ca0 100644
--- a/apps/assets/const/device.py
+++ b/apps/assets/const/device.py
@@ -32,15 +32,16 @@ class DeviceTypes(BaseType):
     def _get_automation_constrains(cls) -> dict:
         return {
             '*': {
-                'ansible_enabled': False,
+                'ansible_enabled': True,
                 'ansible_config': {
                     'ansible_connection': 'local',
+                    'first_conn_delay_time': 0.5,
                 },
-                'ping_enabled': False,
+                'ping_enabled': True,
                 'gather_facts_enabled': False,
                 'gather_accounts_enabled': False,
-                'verify_account_enabled': False,
-                'change_secret_enabled': False,
+                'verify_account_enabled': True,
+                'change_secret_enabled': True,
                 'push_account_enabled': False
             }
         }
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index 2695851bb..fc124b210 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -150,7 +150,8 @@ class JMSInventory:
             },
             'jms_account': {
                 'id': str(account.id), 'username': account.username,
-                'secret': account.secret, 'secret_type': account.secret_type
+                'secret': account.secret, 'secret_type': account.secret_type,
+                'private_key_path': account.private_key_path
             } if account else None
         }
 
diff --git a/apps/ops/ansible/modules/custom_command.py b/apps/ops/ansible/modules/custom_command.py
new file mode 100644
index 000000000..4205e7088
--- /dev/null
+++ b/apps/ops/ansible/modules/custom_command.py
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+---
+module: custom_command
+short_description: Adds or removes a user with custom commands by ssh
+description:
+    - You can add or edit users using ssh with custom commands.
+
+options:
+  protocol:
+    default: ssh
+    choices: [ssh]
+    description:
+      - C(ssh) The remote asset is connected using ssh.
+    type: str
+  name:
+    description:
+      - The name of the user to add or remove.
+    required: true
+    aliases: [user]
+    type: str
+  password:
+    description:
+      - The password to use for the user.
+    type: str
+    aliases: [pass]
+  commands:
+    description:
+      - Custom change password commands.
+    type: list
+    required: true
+  first_conn_delay_time:
+    description:
+      - Delay for executing the command after SSH connection(unit: s)
+    type: float
+    required: false
+'''
+
+EXAMPLES = '''
+- name: Create user with name 'jms' and password '123456'.
+  custom_command:
+    login_host: "localhost"
+    login_port: 22
+    login_user: "admin"
+    login_password: "123456"
+    name: "jms"
+    password: "123456"
+    commands: ['passwd {username}', '{password}', '{password}']
+'''
+
+RETURN = '''
+name:
+    description: The name of the user to add.
+    returned: success
+    type: str
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ops.ansible.modules_utils.custom_common import (
+    SSHClient, ssh_common_argument_spec
+)
+
+
+def get_commands(module):
+    username = module.params['name']
+    password = module.params['password']
+    commands = module.params['commands'] or []
+    for index, command in enumerate(commands):
+        commands[index] = command.format(
+            username=username, password=password
+        )
+    return commands
+
+# =========================================
+# Module execution.
+#
+
+
+def main():
+    argument_spec = ssh_common_argument_spec()
+    argument_spec.update(
+        name=dict(required=True, aliases=['user']),
+        password=dict(aliases=['pass'], no_log=True),
+        commands=dict(type='list', required=False),
+        first_conn_delay_time=dict(
+            type='float', required=False, default=0.5
+        ),
+    )
+    module = AnsibleModule(argument_spec=argument_spec)
+
+    ssh_client = SSHClient(module)
+    commands = get_commands(module)
+    if not commands:
+        module.fail_json(
+            msg='No command found, please go to the platform details to add'
+        )
+    err = ssh_client.execute(commands)
+    if err:
+        module.fail_json(
+            msg='There was a problem executing the command: %s' % err
+        )
+
+    user = module.params['name']
+    module.exit_json(changed=True, user=user)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/ops/ansible/modules/oracle_user.py b/apps/ops/ansible/modules/oracle_user.py
index 9e23fe70c..c1d485e40 100644
--- a/apps/ops/ansible/modules/oracle_user.py
+++ b/apps/ops/ansible/modules/oracle_user.py
@@ -69,7 +69,7 @@ EXAMPLES = '''
   oracle_user:
     hostname: "remote server"
     login_database: "helowin"
-    login_username: "system"
+    login_user: "system"
     login_password: "123456"
     name: "jms"
     password: "123456"
@@ -78,7 +78,7 @@ EXAMPLES = '''
   oracle_user:
     hostname: "remote server"
     login_database: "helowin"
-    login_username: "system"
+    login_user: "system"
     login_password: "123456"
     name: "jms"
     state: "absent"
diff --git a/apps/ops/ansible/modules/ssh_ping.py b/apps/ops/ansible/modules/ssh_ping.py
new file mode 100644
index 000000000..15a30eb0e
--- /dev/null
+++ b/apps/ops/ansible/modules/ssh_ping.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+---
+module: custom_ssh_ping
+short_description: Use ssh to probe whether an asset is connectable
+description:
+    - Use ssh to probe whether an asset is connectable
+'''
+
+EXAMPLES = '''
+- name: >
+    Ping asset server.
+  custom_ssh_ping:
+    login_host: 127.0.0.1
+    login_port: 22
+    login_user: jms
+    login_password: password
+'''
+
+RETURN = '''
+is_available:
+  description: MongoDB server availability.
+  returned: always
+  type: bool
+  sample: true
+conn_err_msg:
+  description: Connection error message.
+  returned: always
+  type: str
+  sample: ''
+'''
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ops.ansible.modules_utils.custom_common import (
+    SSHClient, ssh_common_argument_spec
+)
+
+
+# =========================================
+# Module execution.
+#
+
+
+def main():
+    options = ssh_common_argument_spec()
+    module = AnsibleModule(argument_spec=options, supports_check_mode=True,)
+
+    result = {
+        'changed': False, 'is_available': True
+    }
+    client = SSHClient(module)
+    err = client.connect()
+    if err:
+        module.fail_json(msg='Unable to connect to asset: %s' % err)
+        result['is_available'] = False
+
+    return module.exit_json(**result)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/ops/ansible/modules_utils/custom_common.py b/apps/ops/ansible/modules_utils/custom_common.py
new file mode 100644
index 000000000..07c2b6648
--- /dev/null
+++ b/apps/ops/ansible/modules_utils/custom_common.py
@@ -0,0 +1,68 @@
+import time
+
+import paramiko
+
+from paramiko.ssh_exception import SSHException, NoValidConnectionsError
+
+
+def ssh_common_argument_spec():
+    options = dict(
+        login_host=dict(type='str', required=False, default='localhost'),
+        login_port=dict(type='int', required=False, default=22),
+        login_user=dict(type='str', required=False, default='root'),
+        login_password=dict(type='str', required=False, no_log=True),
+        login_secret_type=dict(type='str', required=False, default='password'),
+        login_private_key_path=dict(type='str', required=False, no_log=True),
+    )
+    return options
+
+
+class SSHClient:
+    def __init__(self, module):
+        self.module = module
+        self.is_connect = False
+        self.client = paramiko.SSHClient()
+        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+
+    def get_connect_params(self):
+        params = {
+            'allow_agent': False, 'look_for_keys': False,
+            'hostname': self.module.params['login_host'],
+            'port': self.module.params['login_port'],
+            'username': self.module.params['login_user'],
+        }
+        secret_type = self.module.params['login_secret_type']
+        if secret_type == 'ssh_key':
+            params['key_filename'] = self.module.params['login_private_key_path']
+        else:
+            params['password'] = self.module.params['login_password']
+        return params
+
+    def connect(self):
+        try:
+            self.client.connect(**self.get_connect_params())
+        except (SSHException, NoValidConnectionsError) as err:
+            err_msg = str(err)
+        else:
+            self.is_connect = True
+            err_msg = ''
+        return err_msg
+
+    def execute(self, commands):
+        if not self.is_connect:
+            self.connect()
+
+        channel = self.client.invoke_shell()
+        # 读取首次登陆终端返回的消息
+        channel.recv(2048)
+        # 网络设备一般登录有延迟,等终端有返回后再执行命令
+        delay_time = self.module.params['first_conn_delay_time']
+        time.sleep(delay_time)
+        err_msg = ''
+        try:
+            for command in commands:
+                channel.send(command + '\n')
+                time.sleep(0.3)
+        except SSHException as e:
+            err_msg = str(e)
+        return err_msg