Merge pull request #10731 from jumpserver/dev

v3.4.0
pull/10734/head
Jiangjie.Bai 2023-06-15 14:16:39 +08:00 committed by GitHub
commit 0a1b379dcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
240 changed files with 4192 additions and 2133 deletions

View File

@ -1,4 +1,4 @@
FROM python:3.9-slim as stage-build
FROM python:3.9-slim-buster as stage-build
ARG TARGETARCH
ARG VERSION
@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim
FROM python:3.9-slim-buster
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
@ -27,6 +27,7 @@ ARG DEPENDENCIES=" \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
freerdp2-dev \
libaio-dev"
ARG TOOLS=" \

View File

@ -1,4 +1,4 @@
FROM python:3.9-slim as stage-build
FROM python:3.9-slim-buster as stage-build
ARG TARGETARCH
ARG VERSION
@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim
FROM python:3.9-slim-buster
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
@ -28,6 +28,7 @@ ARG DEPENDENCIES=" \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
freerdp2-dev \
libaio-dev"
ARG TOOLS=" \

View File

@ -22,7 +22,7 @@ __all__ = [
class AccountViewSet(OrgBulkModelViewSet):
model = Account
search_fields = ('username', 'asset__address', 'name')
search_fields = ('username', 'name', 'asset__name', 'asset__address')
filterset_class = AccountFilterSet
serializer_classes = {
'default': serializers.AccountSerializer,

View File

@ -26,6 +26,7 @@
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
ignore_errors: true
when: ping_info is succeeded
register: change_info
@ -35,6 +36,3 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
when:
- ping_info is succeeded
- change_info is succeeded

View File

@ -15,5 +15,6 @@ params:
i18n:
SSH account change secret:
zh: SSH 账号改密
ja: SSH アカウントのパスワード変更
zh: 使用 SSH 命令行自定义改密
ja: SSH コマンドライン方式でカスタムパスワード変更
en: Custom password change by SSH command line

View File

@ -38,8 +38,8 @@
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded
register: change_info
- name: Verify password
mongodb_ping:
@ -53,6 +53,3 @@
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

View File

@ -7,5 +7,6 @@ method: change_secret
i18n:
MongoDB account change secret:
zh: MongoDB 账号改密
ja: MongoDB アカウントのパスワード変更
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号改密
ja: Ansible mongodb モジュールを使用して MongoDB アカウントのパスワード変更
en: Using Ansible module mongodb to change MongoDB account secret

View File

@ -28,8 +28,8 @@
password: "{{ account.secret }}"
host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded
register: change_info
- name: Verify password
community.mysql.mysql_info:
@ -38,6 +38,3 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
filter: version
when:
- db_info is succeeded
- change_info is succeeded

View File

@ -8,5 +8,6 @@ method: change_secret
i18n:
MySQL account change secret:
zh: MySQL 账号改密
ja: MySQL アカウントのパスワード変更
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号改密
ja: Ansible mysql モジュールを使用して MySQL アカウントのパスワード変更
en: Using Ansible module mysql to change MySQL account secret

View File

@ -29,8 +29,8 @@
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded
register: change_info
- name: Verify password
oracle_ping:
@ -39,6 +39,3 @@
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

View File

@ -29,8 +29,8 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
register: change_info
- name: Verify password
community.postgresql.postgresql_ping:
@ -39,8 +39,3 @@
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

View File

@ -41,8 +41,8 @@
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
register: change_info
- name: Add SQLServer user
community.general.mssql_script:
@ -52,8 +52,8 @@
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
register: change_info
- name: Verify password
community.general.mssql_script:
@ -64,6 +64,3 @@
name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
when:
- db_info is succeeded
- change_info is succeeded

View File

@ -9,6 +9,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed

View File

@ -7,5 +7,6 @@ method: change_secret
i18n:
AIX account change secret:
zh: AIX 账号改密
ja: AIX アカウントのパスワード変更
zh: 使用 Ansible 模块 user 执行账号改密 (DES)
ja: Ansible user モジュールを使用してアカウントのパスワード変更 (DES)
en: Using Ansible module user to change account secret (DES)

View File

@ -9,6 +9,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed

View File

@ -8,5 +8,6 @@ method: change_secret
i18n:
Posix account change secret:
zh: Posix 账号改密
ja: Posix アカウントのパスワード変更
zh: 使用 Ansible 模块 user 执行账号改密 (SHA512)
ja: Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)
en: Using Ansible module user to change account secret (SHA512)

View File

@ -21,6 +21,7 @@
groups: "{{ user_info.groups[0].name }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection

View File

@ -8,5 +8,6 @@ type:
i18n:
Windows account change secret:
zh: Windows 账号改密
ja: Windows アカウントのパスワード変更
zh: 使用 Ansible 模块 win_user 执行 Windows 账号改密
ja: Ansible win_user モジュールを使用して Windows アカウントのパスワード変更
en: Using Ansible module win_user to change Windows account secret

View File

@ -1,3 +1,5 @@
import re
from django.utils import timezone
__all__ = ['GatherAccountsFilter']
@ -27,18 +29,25 @@ class GatherAccountsFilter:
@staticmethod
def posix_filter(info):
username_pattern = re.compile(r'^(\S+)')
ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
login_time_pattern = re.compile(r'\w{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}')
result = {}
for line in info:
data = line.split('@')
if len(data) == 1:
result[line] = {}
usernames = username_pattern.findall(line)
username = ''.join(usernames)
if username:
result[username] = {}
else:
continue
if len(data) != 3:
continue
username, address, dt = data
date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z')
result[username] = {'address': address, 'date': date}
ip_addrs = ip_pattern.findall(line)
ip_addr = ''.join(ip_addrs)
if ip_addr:
result[username].update({'address': ip_addr})
login_times = login_time_pattern.findall(line)
if login_times:
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
result[username].update({'date': date})
return result
@staticmethod

View File

@ -5,7 +5,7 @@
ansible.builtin.shell:
cmd: >
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }')
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $0 }')
if [ -n "$k" ]; then
echo $k
else

View File

@ -8,5 +8,6 @@ method: gather_accounts
i18n:
Posix account gather:
zh: Posix 账号收集
ja: Posix アカウントの収集
zh: 使用命令 getent passwd 收集 Posix 资产账号
ja: コマンド getent を使用してアセットアカウントを収集する
en: Using command getent to gather accounts

View File

@ -8,5 +8,6 @@ type:
i18n:
Windows account gather:
zh: Windows 账号收集
ja: Windows アカウントの収集
zh: 使用命令 net user 收集 Windows 账号
ja: コマンド net user を使用して Windows アカウントを収集する
en: Using command net user to gather accounts

View File

@ -38,8 +38,8 @@
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded
register: change_info
- name: Verify password
mongodb_ping:
@ -53,6 +53,3 @@
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

View File

@ -7,5 +7,6 @@ method: push_account
i18n:
MongoDB account push:
zh: MongoDB 账号推送
ja: MongoDB アカウントのプッシュ
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号推送
ja: Ansible mongodb モジュールを使用してアカウントをプッシュする
en: Using Ansible module mongodb to push account

View File

@ -28,8 +28,8 @@
password: "{{ account.secret }}"
host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded
register: change_info
- name: Verify password
community.mysql.mysql_info:
@ -38,6 +38,3 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
filter: version
when:
- db_info is succeeded
- change_info is succeeded

View File

@ -8,5 +8,6 @@ method: push_account
i18n:
MySQL account push:
zh: MySQL 账号推送
ja: MySQL アカウントのプッシュ
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号推送
ja: Ansible mysql モジュールを使用してアカウントをプッシュする
en: Using Ansible module mysql to push account

View File

@ -29,8 +29,8 @@
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: db_info is succeeded
register: change_info
- name: Verify password
oracle_ping:
@ -39,6 +39,3 @@
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

View File

@ -7,5 +7,6 @@ method: push_account
i18n:
Oracle account push:
zh: Oracle 账号推送
ja: Oracle アカウントのプッシュ
zh: 使用 Python 模块 oracledb 执行 Oracle 账号推送
ja: Python oracledb モジュールを使用してアカウントをプッシュする
en: Using Python module oracledb to push account

View File

@ -29,8 +29,8 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
register: change_info
- name: Verify password
community.postgresql.postgresql_ping:
@ -42,5 +42,3 @@
when:
- result is succeeded
- change_info is succeeded
register: result
failed_when: not result.is_available

View File

@ -7,5 +7,6 @@ method: push_account
i18n:
PostgreSQL account push:
zh: PostgreSQL 账号推送
ja: PostgreSQL アカウントのプッシュ
zh: 使用 Ansible 模块 postgresql 执行 PostgreSQL 账号推送
ja: Ansible postgresql モジュールを使用してアカウントをプッシュする
en: Using Ansible module postgresql to push account

View File

@ -41,6 +41,7 @@
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
register: change_info
@ -52,6 +53,7 @@
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
register: change_info
@ -64,6 +66,3 @@
name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
when:
- db_info is succeeded
- change_info is succeeded

View File

@ -7,5 +7,6 @@ method: push_account
i18n:
SQLServer account push:
zh: SQLServer 账号推送
ja: SQLServer アカウントのプッシュ
zh: 使用 Ansible 模块 mssql 执行 SQLServer 账号推送
ja: Ansible mssql モジュールを使用してアカウントをプッシュする
en: Using Ansible module mssql to push account

View File

@ -8,7 +8,7 @@
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
home: "{{ '/home/' + account.username }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
@ -18,20 +18,6 @@
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 }}"
@ -43,6 +29,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: remove jumpserver ssh key

View File

@ -16,6 +16,12 @@ params:
label: 'Shell'
default: '/bin/bash'
- name: home
type: str
label: '家目录'
default: ''
help_text: '默认家目录 /home/系统用户名: /home/username'
- name: groups
type: str
label: '用户组'
@ -24,6 +30,7 @@ params:
i18n:
Aix account push:
zh: Aix 账号推送
ja: Aix アカウントのプッシュ
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
en: Using Ansible module user to push account (DES)

View File

@ -8,7 +8,7 @@
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
home: "{{ '/home/' + account.username }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
@ -18,20 +18,6 @@
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 }}"
@ -43,6 +29,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: remove jumpserver ssh key

View File

@ -18,6 +18,12 @@ params:
default: '/bin/bash'
help_text: ''
- name: home
type: str
label: '家目录'
default: ''
help_text: '默认家目录 /home/系统用户名: /home/username'
- name: groups
type: str
label: '用户组'
@ -26,5 +32,6 @@ params:
i18n:
Posix account push:
zh: Posix 账号推送
ja: Posix アカウントのプッシュ
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
en: Using Ansible module user to push account (sha512)

View File

@ -17,6 +17,7 @@
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection

View File

@ -14,5 +14,6 @@ params:
i18n:
Windows account push:
zh: Windows 账号推送
ja: Windows アカウントのプッシュ
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送
ja: Ansible win_user モジュールを使用して Windows アカウントをプッシュする
en: Using Ansible module win_user to push account

View File

@ -1,13 +0,0 @@
id: verify_account_by_ssh
name: "{{ 'SSH account verify' | trans }}"
category:
- device
- host
type:
- all
method: verify_account
i18n:
SSH account verify:
zh: SSH 账号验证
ja: SSH アカウントの検証

View File

@ -10,5 +10,5 @@
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 }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"

View File

@ -0,0 +1,13 @@
id: verify_account_by_rdp
name: "{{ 'Windows rdp account verify' | trans }}"
category:
- host
type:
- windows
method: verify_account
i18n:
Windows rdp account verify:
zh: 使用 Python 模块 pyfreerdp 验证账号
ja: Python モジュール pyfreerdp を使用してアカウントを検証する
en: Using Python module pyfreerdp to verify account

View File

@ -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: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"

View File

@ -0,0 +1,14 @@
id: verify_account_by_ssh
name: "{{ 'SSH account verify' | trans }}"
category:
- device
- host
type:
- all
method: verify_account
i18n:
SSH account verify:
zh: 使用 Python 模块 paramiko 验证账号
ja: Python モジュール paramiko を使用してアカウントを検証する
en: Using Python module paramiko to verify account

View File

@ -7,5 +7,6 @@ method: verify_account
i18n:
MongoDB account verify:
zh: MongoDB 账号验证
ja: MongoDB アカウントの検証
zh: 使用 Ansible 模块 mongodb 验证账号
ja: Ansible mongodb モジュールを使用してアカウントを検証する
en: Using Ansible module mongodb to verify account

View File

@ -8,5 +8,7 @@ method: verify_account
i18n:
MySQL account verify:
zh: MySQL 账号验证
ja: MySQL アカウントの検証
zh: 使用 Ansible 模块 mysql 验证账号
ja: Ansible mysql モジュールを使用してアカウントを検証する
en: Using Ansible module mysql to verify account

View File

@ -7,5 +7,6 @@ method: verify_account
i18n:
Oracle account verify:
zh: Oracle 账号验证
ja: Oracle アカウントの検証
zh: 使用 Python 模块 oracledb 验证账号
ja: Python モジュール oracledb を使用してアカウントを検証する
en: Using Python module oracledb to verify account

View File

@ -7,5 +7,6 @@ method: verify_account
i18n:
PostgreSQL account verify:
zh: PostgreSQL 账号验证
ja: PostgreSQL アカウントの検証
zh: 使用 Ansible 模块 postgresql 验证账号
ja: Ansible postgresql モジュールを使用してアカウントを検証する
en: Using Ansible module postgresql to verify account

View File

@ -7,5 +7,6 @@ method: verify_account
i18n:
SQLServer account verify:
zh: SQLServer 账号验证
ja: SQLServer アカウントの検証
zh: 使用 Ansible 模块 mssql 验证账号
ja: Ansible mssql モジュールを使用してアカウントを検証する
en: Using Ansible module mssql to verify account

View File

@ -8,5 +8,6 @@ method: verify_account
i18n:
Posix account verify:
zh: Posix 账号验证
ja: Posix アカウントの検証
zh: 使用 Ansible 模块 ping 验证账号
ja: Ansible ping モジュールを使用してアカウントを検証する
en: Using Ansible module ping to verify account

View File

@ -7,6 +7,7 @@ type:
- windows
i18n:
Windows account verify:
zh: Windows 账号验证
ja: Windows アカウントの検証
Windows account verify:
zh: 使用 Ansible 模块 win_ping 验证账号
ja: Ansible win_ping モジュールを使用してアカウントを検証する
en: Using Ansible module win_ping to verify account

View File

@ -5,6 +5,7 @@ from django.db import IntegrityError
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.generics import get_object_or_404
from rest_framework.validators import UniqueTogetherValidator
from accounts.const import SecretType, Source, AccountInvalidPolicy
@ -22,8 +23,8 @@ logger = get_logger(__name__)
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
template = serializers.PrimaryKeyRelatedField(
queryset=AccountTemplate.objects,
required=False, label=_("Template"), write_only=True
queryset=AccountTemplate.objects, required=False,
label=_("Template"), write_only=True, allow_null=True
)
push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True
@ -33,7 +34,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
)
on_invalid = LabeledChoiceField(
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
write_only=True, label=_('Exist policy')
write_only=True, allow_null=True, label=_('Exist policy'),
)
_template = None
clean_auth_fields: callable
@ -60,10 +61,6 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
self.from_template_if_need(data)
self.set_uniq_name_if_need(data, asset)
def to_internal_value(self, data):
self.from_template_if_need(data)
return super().to_internal_value(data)
def set_uniq_name_if_need(self, initial_data, asset):
name = initial_data.get('name')
if name is not None:
@ -105,6 +102,15 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
continue
attrs[name] = value
initial_data.update(attrs)
initial_data.update({
'source': Source.TEMPLATE,
'source_id': str(template.id)
})
asset_id = initial_data.get('asset')
if isinstance(asset_id, list) or not asset_id:
return
asset = get_object_or_404(Asset, pk=asset_id)
initial_data['su_from'] = template.get_su_from_account(asset)
@staticmethod
def push_account_if_need(instance, push_now, params, stat):
@ -149,19 +155,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
else:
raise serializers.ValidationError('Account already exists')
def generate_source_data(self, validated_data):
template = self._template
if template is None:
return
validated_data['source'] = Source.TEMPLATE
validated_data['source_id'] = str(template.id)
def create(self, validated_data):
push_now = validated_data.pop('push_now', None)
params = validated_data.pop('params', None)
self.clean_auth_fields(validated_data)
self.generate_source_data(validated_data)
instance, stat = self.do_create(validated_data)
self.push_account_if_need(instance, push_now, params, stat)
return instance
@ -201,8 +198,11 @@ class AccountAssetSerializer(serializers.ModelSerializer):
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
asset = AccountAssetSerializer(label=_('Asset'))
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
source = LabeledChoiceField(
choices=Source.choices, label=_("Source"), required=False,
allow_null=True, default=Source.LOCAL
)
su_from = ObjectRelatedField(
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
label=_('Su from'), attrs=('id', 'name', 'username')
@ -215,11 +215,12 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
'source', 'source_id', 'connectivity',
] + AccountCreateUpdateSerializerMixin.Meta.fields
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
'source', 'source_id', 'connectivity'
'connectivity'
]
extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False},
'source_id': {'required': False, 'allow_null': True},
}
@classmethod
@ -253,11 +254,14 @@ class AssetAccountBulkSerializer(
fields = [
'name', 'username', 'secret', 'secret_type', 'passphrase',
'privileged', 'is_active', 'comment', 'template',
'on_invalid', 'push_now', 'assets', 'su_from_username'
'on_invalid', 'push_now', 'assets', 'su_from_username',
'source', 'source_id',
]
extra_kwargs = {
'name': {'required': False},
'secret_type': {'required': False},
'source': {'required': False, 'allow_null': True},
'source_id': {'required': False, 'allow_null': True},
}
def set_initial_value(self):
@ -397,7 +401,6 @@ class AssetAccountBulkSerializer(
def create(self, validated_data):
push_now = validated_data.pop('push_now', False)
self.generate_source_data(validated_data)
results = self.perform_bulk_create(validated_data)
self.push_accounts_if_need(results, push_now)
for res in results:

View File

@ -23,8 +23,12 @@ class AccountTemplateSerializer(BaseAccountSerializer):
def sync_accounts_secret(self, instance, diff):
if not self._is_sync_account or 'secret' not in diff:
return
accounts = Account.objects.filter(source_id=instance.id)
query_data = {
'source_id': instance.id,
'username': instance.username,
'secret_type': instance.secret_type
}
accounts = Account.objects.filter(**query_data)
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
def validate(self, attrs):
@ -38,7 +42,10 @@ class AccountTemplateSerializer(BaseAccountSerializer):
if getattr(instance, k, None) != v
}
instance = super().update(instance, validated_data)
self.sync_accounts_secret(instance, diff)
if {'username', 'secret_type'} & set(diff.keys()):
Account.objects.filter(source_id=instance.id).update(source_id=None)
else:
self.sync_accounts_secret(instance, diff)
return instance

View File

@ -1,4 +1,5 @@
from .command_acl import *
from .connect_method import *
from .login_acl import *
from .login_asset_acl import *
from .login_asset_check import *

View File

@ -1,6 +1,8 @@
from rest_framework.decorators import action
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet
from .common import ACLUserAssetFilterMixin
from .. import models, serializers
__all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet']
@ -13,10 +15,16 @@ class CommandGroupViewSet(OrgBulkModelViewSet):
serializer_class = serializers.CommandGroupSerializer
class CommandACLFilter(ACLUserAssetFilterMixin):
class Meta:
model = models.CommandFilterACL
fields = ['name', ]
class CommandFilterACLViewSet(OrgBulkModelViewSet):
model = models.CommandFilterACL
filterset_fields = ('name',)
search_fields = filterset_fields
filterset_class = CommandACLFilter
search_fields = ['name']
serializer_class = serializers.CommandFilterACLSerializer
rbac_perms = {
'command_review': 'tickets.add_superticket'

45
apps/acls/api/common.py Normal file
View File

@ -0,0 +1,45 @@
from django.db.models import Q
from django_filters import rest_framework as drf_filters
from common.drf.filters import BaseFilterSet
from common.utils import is_uuid
class ACLUserFilterMixin(BaseFilterSet):
users = drf_filters.CharFilter(method='filter_user')
@staticmethod
def filter_user(queryset, name, value):
from users.models import User
if not value:
return queryset
if is_uuid(value):
user = User.objects.filter(id=value).first()
else:
q = Q(name=value) | Q(username=value)
user = User.objects.filter(q).first()
if not user:
return queryset.none()
q = queryset.model.users.get_filter_q(user)
return queryset.filter(q).distinct()
class ACLUserAssetFilterMixin(ACLUserFilterMixin):
assets = drf_filters.CharFilter(method='filter_asset')
@staticmethod
def filter_asset(queryset, name, value):
from assets.models import Asset
if not value:
return queryset
if is_uuid(value):
asset = Asset.objects.filter(id=value).first()
else:
q = Q(name=value) | Q(address=value)
asset = Asset.objects.filter(q).first()
if not asset:
return queryset.none()
q = queryset.model.assets.get_filter_q(asset)
return queryset.filter(q).distinct()

View File

@ -0,0 +1,29 @@
from django_filters import rest_framework as drf_filters
from common.api import JMSBulkModelViewSet
from orgs.utils import tmp_to_root_org
from .common import ACLUserFilterMixin
from .. import serializers
from ..models import ConnectMethodACL
__all__ = ['ConnectMethodACLViewSet']
class ConnectMethodFilter(ACLUserFilterMixin):
methods = drf_filters.CharFilter(field_name="methods__contains", lookup_expr='exact')
class Meta:
model = ConnectMethodACL
fields = ['name', ]
class ConnectMethodACLViewSet(JMSBulkModelViewSet):
queryset = ConnectMethodACL.objects.all()
filterset_class = ConnectMethodFilter
search_fields = ('name',)
serializer_class = serializers.ConnectMethodACLSerializer
def filter_queryset(self, queryset):
with tmp_to_root_org():
return super().filter_queryset(queryset)

View File

@ -1,14 +1,26 @@
from common.api import JMSBulkModelViewSet
from ..models import LoginACL
from orgs.utils import tmp_to_root_org
from .common import ACLUserFilterMixin
from .. import serializers
from ..filters import LoginAclFilter
from ..models import LoginACL
__all__ = ['LoginACLViewSet']
class LoginACLFilter(ACLUserFilterMixin):
class Meta:
model = LoginACL
fields = ('name', 'action')
class LoginACLViewSet(JMSBulkModelViewSet):
queryset = LoginACL.objects.all()
filterset_class = LoginAclFilter
filterset_class = LoginACLFilter
search_fields = ('name',)
serializer_class = serializers.LoginACLSerializer
def filter_queryset(self, queryset):
with tmp_to_root_org():
return super().filter_queryset(queryset)

View File

@ -1,12 +1,18 @@
from orgs.mixins.api import OrgBulkModelViewSet
from .common import ACLUserAssetFilterMixin
from .. import models, serializers
__all__ = ['LoginAssetACLViewSet']
class LoginAssetACLFilter(ACLUserAssetFilterMixin):
class Meta:
model = models.LoginAssetACL
fields = ['name', ]
class LoginAssetACLViewSet(OrgBulkModelViewSet):
model = models.LoginAssetACL
filterset_fields = ('name', )
search_fields = filterset_fields
filterset_class = LoginAssetACLFilter
search_fields = ['name']
serializer_class = serializers.LoginAssetACLSerializer

View File

@ -30,14 +30,21 @@ class LoginAssetCheckAPI(CreateAPIView):
return serializer
def check_review(self):
user = self.serializer.user
asset = self.serializer.asset
# 用户满足的 acls
queryset = LoginAssetACL.objects.all()
q = LoginAssetACL.users.get_filter_q(LoginAssetACL, 'users', user)
queryset = queryset.filter(q)
q = LoginAssetACL.assets.get_filter_q(LoginAssetACL, 'assets', asset)
queryset = queryset.filter(q)
account_username = self.serializer.validated_data.get('account_username')
queryset = queryset.filter(accounts__contains=account_username)
with tmp_to_org(self.serializer.asset.org):
kwargs = {
'user': self.serializer.user,
'asset': self.serializer.asset,
'account_username': self.serializer.validated_data.get('account_username'),
'action': LoginAssetACL.ActionChoices.review
}
acl = LoginAssetACL.filter_queryset(**kwargs).valid().first()
acl = queryset.valid().first()
if acl:
need_review = True
response_data = self._get_response_data_of_need_review(acl)

View File

@ -1,15 +0,0 @@
from django_filters import rest_framework as filters
from common.drf.filters import BaseFilterSet
from acls.models import LoginACL
class LoginAclFilter(BaseFilterSet):
user = filters.UUIDFilter(field_name='user_id')
user_display = filters.CharFilter(field_name='user__name')
class Meta:
model = LoginACL
fields = (
'name', 'user', 'user_display', 'action'
)

View File

@ -1,14 +1,14 @@
# Generated by Django 3.1 on 2021-03-11 09:53
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
import django.core.validators
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
@ -24,37 +24,51 @@ class Migration(migrations.Migration):
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
validators=[django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100)],
verbose_name='Priority')),
('is_active', models.BooleanField(default=True, verbose_name='Active')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('ip_group', models.JSONField(default=list, verbose_name='Login IP')),
('action', models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow')], default='reject', max_length=64, verbose_name='Action')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_acls', to=settings.AUTH_USER_MODEL, verbose_name='User')),
('action',
models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow')], default='reject', max_length=64,
verbose_name='Action')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_acls',
to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'ordering': ('priority', '-date_updated', 'name'),
'ordering': ('priority', '-is_active', 'name'),
},
),
migrations.CreateModel(
name='LoginAssetACL',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
validators=[django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100)],
verbose_name='Priority')),
('is_active', models.BooleanField(default=True, verbose_name='Active')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('users', models.JSONField(verbose_name='User')),
('system_users', models.JSONField(verbose_name='System User')),
('assets', models.JSONField(verbose_name='Asset')),
('action', models.CharField(choices=[('login_confirm', 'Login confirm')], default='login_confirm', max_length=64, verbose_name='Action')),
('reviewers', models.ManyToManyField(blank=True, related_name='review_login_asset_acls', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
('action',
models.CharField(choices=[('login_confirm', 'Login confirm')], default='login_confirm', max_length=64,
verbose_name='Action')),
('reviewers',
models.ManyToManyField(blank=True, related_name='review_login_asset_acls', to=settings.AUTH_USER_MODEL,
verbose_name='Reviewers')),
],
options={
'ordering': ('priority', '-date_updated', 'name'),
'ordering': ('priority', '-is_active', 'name'),
'unique_together': {('name', 'org_id')},
},
),

View File

@ -2,7 +2,6 @@
import django
from django.conf import settings
from django.db import migrations, models, transaction
from acls.models import LoginACL
LOGIN_CONFIRM_ZH = '登录复核'
LOGIN_CONFIRM_EN = 'Login confirm'
@ -90,10 +89,10 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='loginacl',
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
),
migrations.AlterModelOptions(
name='loginassetacl',
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login asset acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login asset acl'},
),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('acls', '0002_auto_20210926_1047'),
]
@ -12,10 +11,10 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='loginacl',
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
),
migrations.AlterModelOptions(
name='loginassetacl',
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login asset acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login asset acl'},
),
]

View File

@ -63,7 +63,7 @@ class Migration(migrations.Migration):
],
options={
'verbose_name': 'Command acl',
'ordering': ('priority', '-date_updated', 'name'),
'ordering': ('priority', '-is_active', 'name'),
'unique_together': {('name', 'org_id')},
},
),

View File

@ -20,14 +20,14 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='commandfilteracl',
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Command acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Command acl'},
),
migrations.AlterModelOptions(
name='loginacl',
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
),
migrations.AlterModelOptions(
name='loginassetacl',
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login asset acl'},
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login asset acl'},
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.2.17 on 2023-04-25 09:04
from django.db import migrations, models
import common.db.fields
class Migration(migrations.Migration):
dependencies = [
('acls', '0010_alter_commandfilteracl_command_groups'),
]
operations = [
migrations.AddField(
model_name='commandfilteracl',
name='new_accounts',
field=models.JSONField(default=list, verbose_name='Accounts'),
),
migrations.AddField(
model_name='commandfilteracl',
name='new_assets',
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets'),
),
migrations.AddField(
model_name='commandfilteracl',
name='new_users',
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
),
migrations.AddField(
model_name='loginassetacl',
name='new_accounts',
field=models.JSONField(default=list, verbose_name='Accounts')
),
migrations.AddField(
model_name='loginassetacl',
name='new_assets',
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets'),
),
migrations.AddField(
model_name='loginassetacl',
name='new_users',
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 3.2.17 on 2023-04-26 03:11
from django.db import migrations
def migrate_base_acl_users_assets_accounts(apps, *args):
cmd_acl_model = apps.get_model('acls', 'CommandFilterACL')
login_asset_acl_model = apps.get_model('acls', 'LoginAssetACL')
for model in [cmd_acl_model, login_asset_acl_model]:
for obj in model.objects.all():
user_names = (obj.users or {}).get('username_group', [])
obj.new_users = {
"type": "attrs",
"attrs": [{"name": "username", "value": user_names, "match": "in"}]
}
asset_names = (obj.assets or {}).get('name_group', [])
asset_attrs = []
if asset_names:
asset_attrs.append({"name": "name", "value": asset_names, "match": "in"})
asset_address = (obj.assets or {}).get('address_group', [])
if asset_address:
asset_attrs.append({"name": "address", "value": asset_address, "match": "ip_in"})
obj.new_assets = {"type": "attrs", "attrs": asset_attrs}
account_usernames = (obj.accounts or {}).get('username_group', [])
if '*' in account_usernames:
account_usernames = ['@ALL']
obj.new_accounts = account_usernames
obj.save()
class Migration(migrations.Migration):
dependencies = [
('acls', '0011_auto_20230425_1704'),
]
operations = [
migrations.RunPython(migrate_base_acl_users_assets_accounts)
]

View File

@ -0,0 +1,66 @@
# Generated by Django 3.2.17 on 2023-04-26 09:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('acls', '0012_auto_20230426_1111'),
]
operations = [
migrations.RemoveField(
model_name='commandfilteracl',
name='accounts',
),
migrations.RemoveField(
model_name='commandfilteracl',
name='assets',
),
migrations.RemoveField(
model_name='commandfilteracl',
name='users',
),
migrations.RemoveField(
model_name='loginassetacl',
name='accounts',
),
migrations.RemoveField(
model_name='loginassetacl',
name='assets',
),
migrations.RemoveField(
model_name='loginassetacl',
name='users',
),
migrations.RenameField(
model_name='commandfilteracl',
old_name='new_accounts',
new_name='accounts',
),
migrations.RenameField(
model_name='commandfilteracl',
old_name='new_assets',
new_name='assets',
),
migrations.RenameField(
model_name='commandfilteracl',
old_name='new_users',
new_name='users',
),
migrations.RenameField(
model_name='loginassetacl',
old_name='new_accounts',
new_name='accounts',
),
migrations.RenameField(
model_name='loginassetacl',
old_name='new_assets',
new_name='assets',
),
migrations.RenameField(
model_name='loginassetacl',
old_name='new_users',
new_name='users',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-05-26 09:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('acls', '0013_auto_20230426_1759'),
]
operations = [
migrations.AddField(
model_name='loginassetacl',
name='rules',
field=models.JSONField(default=dict, verbose_name='Rule'),
),
]

View File

@ -0,0 +1,46 @@
# Generated by Django 3.2.17 on 2023-06-06 06:23
import uuid
import django.core.validators
from django.conf import settings
from django.db import migrations, models
import common.db.fields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('acls', '0014_loginassetacl_rules'),
]
operations = [
migrations.CreateModel(
name='ConnectMethodACL',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
validators=[django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100)],
verbose_name='Priority')),
('action', models.CharField(default='reject', max_length=64, verbose_name='Action')),
('is_active', models.BooleanField(default=True, verbose_name='Active')),
('users', common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users')),
('connect_methods', models.JSONField(default=list, verbose_name='Connect methods')),
(
'reviewers',
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
],
options={
'ordering': ('priority', '-is_active', 'name'),
'abstract': False,
},
),
]

View File

@ -0,0 +1,45 @@
# Generated by Django 3.2.17 on 2023-06-06 10:57
from collections import defaultdict
from django.db import migrations, models
import common.db.fields
def migrate_users_login_acls(apps, schema_editor):
login_acl_model = apps.get_model('acls', 'LoginACL')
name_used = defaultdict(int)
for login_acl in login_acl_model.objects.all():
name = login_acl.name
if name_used[name] > 0:
login_acl.name += "_{}".format(name_used[name])
name_used[name] += 1
login_acl.users = {
"type": "ids", "ids": [str(login_acl.user_id)]
}
login_acl.save()
class Migration(migrations.Migration):
dependencies = [
('acls', '0015_connectmethodacl'),
]
operations = [
migrations.AddField(
model_name='loginacl',
name='users',
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
),
migrations.RunPython(migrate_users_login_acls),
migrations.RemoveField(
model_name='loginacl',
name='user',
),
migrations.AlterField(
model_name='loginacl',
name='name',
field=models.CharField(max_length=128, unique=True, verbose_name='Name'),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 3.2.19 on 2023-06-13 07:49
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('acls', '0016_auto_20230606_1857'),
]
operations = [
migrations.AlterModelOptions(
name='connectmethodacl',
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Connect method acl'},
),
]

View File

@ -1,3 +1,4 @@
from .command_acl import *
from .connect_method import *
from .login_acl import *
from .login_asset_acl import *
from .command_acl import *

View File

@ -1,20 +1,20 @@
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from common.db.fields import JSONManyToManyField
from common.db.models import JMSBaseModel
from common.utils import contains_ip
from common.utils.time_period import contains_time_period
from orgs.mixins.models import OrgModelMixin, OrgManager
__all__ = [
'ACLManager',
'BaseACL',
'BaseACLQuerySet',
'UserAssetAccountBaseACL',
'UserAssetAccountACLQuerySet'
'BaseACL', 'UserBaseACL', 'UserAssetAccountBaseACL',
]
from orgs.utils import tmp_to_root_org
from orgs.utils import tmp_to_org
class ActionChoices(models.TextChoices):
reject = 'reject', _('Reject')
@ -36,43 +36,8 @@ class BaseACLQuerySet(models.QuerySet):
return self.inactive()
class UserAssetAccountACLQuerySet(BaseACLQuerySet):
def filter_user(self, username):
q = Q(users__username_group__contains=username) | \
Q(users__username_group__contains='*')
return self.filter(q)
def filter_asset(self, name=None, address=None):
queryset = self.filter()
if name:
q = Q(assets__name_group__contains=name) | \
Q(assets__name_group__contains='*')
queryset = queryset.filter(q)
if address:
ids = [
q.id for q in queryset
if contains_ip(address, q.assets.get('address_group', []))
]
queryset = queryset.filter(id__in=ids)
return queryset
def filter_account(self, username):
q = Q(accounts__username_group__contains=username) | \
Q(accounts__username_group__contains='*')
return self.filter(q)
class ACLManager(models.Manager):
def valid(self):
return self.get_queryset().valid()
class OrgACLManager(OrgManager, ACLManager):
pass
class BaseACL(JMSBaseModel):
name = models.CharField(max_length=128, verbose_name=_('Name'))
name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True)
priority = models.IntegerField(
default=50, verbose_name=_("Priority"),
help_text=_("1-100, the lower the value will be match first"),
@ -83,46 +48,85 @@ class BaseACL(JMSBaseModel):
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
ActionChoices = ActionChoices
objects = ACLManager.from_queryset(BaseACLQuerySet)()
objects = BaseACLQuerySet.as_manager()
class Meta:
ordering = ('priority', 'date_updated', 'name')
ordering = ('priority', '-is_active', 'name')
abstract = True
def is_action(self, action):
return self.action == action
@classmethod
def get_user_acls(cls, user):
return cls.objects.none()
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
# username_group
users = models.JSONField(verbose_name=_('User'))
# name_group, address_group
assets = models.JSONField(verbose_name=_('Asset'))
# username_group
accounts = models.JSONField(verbose_name=_('Account'))
@classmethod
def get_match_rule_acls(cls, user, ip, acl_qs=None):
if acl_qs is None:
acl_qs = cls.get_user_acls(user)
if not acl_qs:
return
objects = OrgACLManager.from_queryset(UserAssetAccountACLQuerySet)()
for acl in acl_qs:
if acl.is_action(ActionChoices.review) and not acl.reviewers.exists():
continue
ip_group = acl.rules.get('ip_group')
time_periods = acl.rules.get('time_period')
is_contain_ip = contains_ip(ip, ip_group) if ip_group else True
is_contain_time_period = contains_time_period(time_periods) if time_periods else True
if is_contain_ip and is_contain_time_period:
# 满足条件,则返回
return acl
return None
class UserBaseACL(BaseACL):
users = JSONManyToManyField('users.User', default=dict, verbose_name=_('Users'))
class Meta(BaseACL.Meta):
unique_together = ('name', 'org_id')
abstract = True
@classmethod
def get_user_acls(cls, user):
queryset = cls.objects.all()
with tmp_to_root_org():
q = cls.users.get_filter_q(user)
queryset = queryset.filter(q)
return queryset.filter(is_active=True).distinct()
class UserAssetAccountBaseACL(OrgModelMixin, UserBaseACL):
name = models.CharField(max_length=128, verbose_name=_('Name'))
assets = JSONManyToManyField('assets.Asset', default=dict, verbose_name=_('Assets'))
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
objects = OrgManager.from_queryset(BaseACLQuerySet)()
class Meta(UserBaseACL.Meta):
unique_together = [('name', 'org_id')]
abstract = True
@classmethod
def filter_queryset(cls, user=None, asset=None, account=None, account_username=None, **kwargs):
queryset = cls.objects.all()
org_id = None
if user:
queryset = queryset.filter_user(user.username)
if account:
org_id = account.org_id
queryset = queryset.filter_account(account.username)
if account_username:
queryset = queryset.filter_account(username=account_username)
q = cls.users.get_filter_q(user)
queryset = queryset.filter(q)
if asset:
org_id = asset.org_id
queryset = queryset.filter_asset(asset.name, asset.address)
if org_id:
kwargs['org_id'] = org_id
with tmp_to_org(org_id):
q = cls.assets.get_filter_q(asset)
queryset = queryset.filter(q)
if account and not account_username:
account_username = account.username
if account_username:
q = models.Q(accounts__contains=account_username) | \
models.Q(accounts__contains='*') | \
models.Q(accounts__contains='@ALL')
queryset = queryset.filter(q)
if kwargs:
queryset = queryset.filter(**kwargs)
return queryset
return queryset.valid().distinct()

View File

@ -0,0 +1,14 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from .base import UserBaseACL
__all__ = ['ConnectMethodACL']
class ConnectMethodACL(UserBaseACL):
connect_methods = models.JSONField(default=list, verbose_name=_('Connect methods'))
class Meta(UserBaseACL.Meta):
verbose_name = _('Connect method acl')
abstract = False

View File

@ -2,20 +2,15 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.utils import get_request_ip, get_ip_city
from common.utils.ip import contains_ip
from common.utils.time_period import contains_time_period
from common.utils.timezone import local_now_display
from .base import BaseACL
from .base import UserBaseACL
class LoginACL(BaseACL):
user = models.ForeignKey(
'users.User', on_delete=models.CASCADE, related_name='login_acls', verbose_name=_('User')
)
class LoginACL(UserBaseACL):
# 规则, ip_group, time_period
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
class Meta(BaseACL.Meta):
class Meta(UserBaseACL.Meta):
verbose_name = _('Login acl')
abstract = False
@ -25,40 +20,18 @@ class LoginACL(BaseACL):
def is_action(self, action):
return self.action == action
@classmethod
def filter_acl(cls, user):
return user.login_acls.all().valid().distinct()
@staticmethod
def match(user, ip):
acl_qs = LoginACL.filter_acl(user)
if not acl_qs:
return
for acl in acl_qs:
if acl.is_action(LoginACL.ActionChoices.review) and \
not acl.reviewers.exists():
continue
ip_group = acl.rules.get('ip_group')
time_periods = acl.rules.get('time_period')
is_contain_ip = contains_ip(ip, ip_group)
is_contain_time_period = contains_time_period(time_periods)
if is_contain_ip and is_contain_time_period:
# 满足条件,则返回
return acl
def create_confirm_ticket(self, request):
def create_confirm_ticket(self, request, user):
from tickets import const
from tickets.models import ApplyLoginTicket
from orgs.models import Organization
title = _('Login confirm') + ' {}'.format(self.user)
title = _('Login confirm') + ' {}'.format(user)
login_ip = get_request_ip(request) if request else ''
login_ip = login_ip or '0.0.0.0'
login_city = get_ip_city(login_ip)
login_datetime = local_now_display()
data = {
'title': title,
'applicant': self.user,
'applicant': user,
'apply_login_ip': login_ip,
'org_id': Organization.ROOT_ID,
'apply_login_city': login_city,

View File

@ -1,10 +1,12 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .base import UserAssetAccountBaseACL
class LoginAssetACL(UserAssetAccountBaseACL):
# 规则, ip_group, time_period
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
class Meta(UserAssetAccountBaseACL.Meta):
verbose_name = _('Login asset acl')

View File

@ -1,4 +1,5 @@
from .command_acl import *
from .connect_method import *
from .login_acl import *
from .login_asset_acl import *
from .login_asset_check import *
from .command_acl import *

View File

@ -1,11 +1,10 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from acls.models.base import ActionChoices
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from acls.models.base import ActionChoices, BaseACL
from common.serializers.fields import JSONManyToManyField, LabeledChoiceField
from jumpserver.utils import has_valid_xpack_license
from orgs.models import Organization
from users.models import User
common_help_text = _(
"With * indicating a match all. "
@ -21,7 +20,7 @@ class ACLUsersSerializer(serializers.Serializer):
)
class ACLAssestsSerializer(serializers.Serializer):
class ACLAssetsSerializer(serializers.Serializer):
address_group_help_text = _(
"With * indicating a match all. "
"Such as: "
@ -71,43 +70,16 @@ class ActionAclSerializer(serializers.Serializer):
action._choices = choices
class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Serializer):
users = ACLUsersSerializer(label=_('User'))
assets = ACLAssestsSerializer(label=_('Asset'))
accounts = ACLAccountsSerializer(label=_('Account'))
users_username_group = serializers.ListField(
source='users.username_group', read_only=True, child=serializers.CharField(),
label=_('User (username)')
)
assets_name_group = serializers.ListField(
source='assets.name_group', read_only=True, child=serializers.CharField(),
label=_('Asset (name)')
)
assets_address_group = serializers.ListField(
source='assets.address_group', read_only=True, child=serializers.CharField(),
label=_('Asset (address)')
)
accounts_username_group = serializers.ListField(
source='accounts.username_group', read_only=True, child=serializers.CharField(),
label=_('Account (username)')
)
reviewers = ObjectRelatedField(
queryset=User.objects, many=True, required=False, label=_('Reviewers')
)
reviewers_amount = serializers.IntegerField(
read_only=True, source="reviewers.count", label=_('Reviewers amount')
)
class BaserACLSerializer(ActionAclSerializer, serializers.Serializer):
class Meta:
model = BaseACL
fields_mini = ["id", "name"]
fields_small = fields_mini + [
'users_username_group', 'assets_address_group', 'assets_name_group',
'accounts_username_group',
"users", "accounts", "assets", "is_active",
"date_created", "date_updated", "priority",
"action", "comment", "created_by", "org_id",
"is_active", "priority", "action",
"date_created", "date_updated",
"comment", "created_by", "org_id",
]
fields_m2m = ["reviewers", "reviewers_amount"]
fields_m2m = ["reviewers", ]
fields = fields_small + fields_m2m
extra_kwargs = {
"priority": {"default": 50},
@ -133,3 +105,18 @@ class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Se
)
raise serializers.ValidationError(error)
return valid_reviewers
class BaserUserACLSerializer(BaserACLSerializer):
users = JSONManyToManyField(label=_('User'))
class Meta(BaserACLSerializer.Meta):
fields = BaserACLSerializer.Meta.fields + ['users']
class BaseUserAssetAccountACLSerializer(BaserUserACLSerializer):
assets = JSONManyToManyField(label=_('Asset'))
accounts = serializers.ListField(label=_('Account'))
class Meta(BaserUserACLSerializer.Meta):
fields = BaserUserACLSerializer.Meta.fields + ['assets', 'accounts']

View File

@ -1,13 +1,13 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from terminal.models import Session
from acls.models import CommandGroup, CommandFilterACL
from common.utils import lazyproperty, get_object_or_none
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
from orgs.utils import tmp_to_root_org
from common.utils import lazyproperty, get_object_or_none
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
from orgs.utils import tmp_to_root_org
from terminal.models import Session
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer", "CommandReviewSerializer"]
@ -27,13 +27,10 @@ class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
command_groups = ObjectRelatedField(
queryset=CommandGroup.objects, many=True, required=False, label=_('Command group')
)
command_groups_amount = serializers.IntegerField(
source='command_groups.count', read_only=True, label=_('Command group amount')
)
class Meta(BaseSerializer.Meta):
model = CommandFilterACL
fields = BaseSerializer.Meta.fields + ['command_groups', 'command_groups_amount']
fields = BaseSerializer.Meta.fields + ['command_groups']
class CommandReviewSerializer(serializers.Serializer):

View File

@ -0,0 +1,23 @@
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
from ..models import ConnectMethodACL
__all__ = ["ConnectMethodACLSerializer"]
class ConnectMethodACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
class Meta(BaseSerializer.Meta):
model = ConnectMethodACL
fields = [
i for i in BaseSerializer.Meta.fields + ['connect_methods']
if i not in ['assets', 'accounts']
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
field_action = self.fields.get('action')
if not field_action:
return
# 仅支持拒绝
for k in ['review', 'accept']:
field_action._choices.pop(k, None)

View File

@ -1,47 +1,22 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers
from common.serializers import BulkModelSerializer, MethodSerializer
from common.serializers.fields import ObjectRelatedField
from users.models import User
from .base import ActionAclSerializer
from common.serializers import MethodSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaserUserACLSerializer
from .rules import RuleSerializer
from ..models import LoginACL
__all__ = [
"LoginACLSerializer",
]
__all__ = ["LoginACLSerializer"]
common_help_text = _(
"With * indicating a match all. "
)
common_help_text = _("With * indicating a match all. ")
class LoginACLSerializer(ActionAclSerializer, BulkModelSerializer):
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
reviewers = ObjectRelatedField(
queryset=User.objects, label=_("Reviewers"), many=True, required=False
)
reviewers_amount = serializers.IntegerField(
read_only=True, source="reviewers.count", label=_("Reviewers amount")
)
class LoginACLSerializer(BaserUserACLSerializer, BulkOrgResourceModelSerializer):
rules = MethodSerializer(label=_('Rule'))
class Meta:
class Meta(BaserUserACLSerializer.Meta):
model = LoginACL
fields_mini = ["id", "name"]
fields_small = fields_mini + [
"priority", "user", "rules", "action",
"is_active", "date_created", "date_updated",
"comment", "created_by",
]
fields_fk = ["user"]
fields_m2m = ["reviewers", "reviewers_amount"]
fields = fields_small + fields_fk + fields_m2m
extra_kwargs = {
"priority": {"default": 50},
"is_active": {"default": True},
}
fields = BaserUserACLSerializer.Meta.fields + ['rules', ]
def get_rules_serializer(self):
return RuleSerializer()

View File

@ -1,11 +1,20 @@
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from django.utils.translation import gettext_lazy as _
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
from common.serializers import MethodSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
from .rules import RuleSerializer
from ..models import LoginAssetACL
__all__ = ["LoginAssetACLSerializer"]
class LoginAssetACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
rules = MethodSerializer(label=_('Rule'))
class Meta(BaseSerializer.Meta):
model = LoginAssetACL
fields = BaseSerializer.Meta.fields + ['rules']
def get_rules_serializer(self):
return RuleSerializer()

View File

@ -10,6 +10,7 @@ router.register(r'login-acls', api.LoginACLViewSet, 'login-acl')
router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl')
router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl')
router.register(r'command-groups', api.CommandGroupViewSet, 'command-group')
router.register(r'connect-method-acls', api.ConnectMethodACLViewSet, 'connect-method-acl')
urlpatterns = [
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),

View File

@ -15,7 +15,7 @@ from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBack
from assets.models import Asset, Gateway, Platform
from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual
from common.api import SuggestionMixin
from common.drf.filters import BaseFilterSet
from common.drf.filters import BaseFilterSet, AttrRulesFilterBackend
from common.utils import get_logger, is_uuid
from orgs.mixins import generics
from orgs.mixins.api import OrgBulkModelViewSet
@ -35,6 +35,7 @@ class AssetFilterSet(BaseFilterSet):
domain = django_filters.CharFilter(method='filter_domain')
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
protocols = django_filters.CharFilter(method='filter_protocols')
domain_enabled = django_filters.BooleanFilter(
field_name="platform__domain_enabled", lookup_expr="exact"
)
@ -78,6 +79,11 @@ class AssetFilterSet(BaseFilterSet):
else:
return queryset.filter(domain__name__contains=value)
@staticmethod
def filter_protocols(queryset, name, value):
value = value.split(',')
return queryset.filter(protocols__name__in=value)
@staticmethod
def filter_labels(queryset, name, value):
if ':' in value:
@ -95,7 +101,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
"""
model = Asset
filterset_class = AssetFilterSet
search_fields = ("name", "address")
search_fields = ("name", "address", "comment")
ordering_fields = ('name', 'connectivity', 'platform', 'date_updated')
serializer_classes = (
("default", serializers.AssetSerializer),
@ -110,7 +116,10 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
("spec_info", "assets.view_asset"),
("gathered_info", "assets.view_asset"),
)
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
extra_filter_backends = [
LabelFilterBackend, IpInFilterBackend,
NodeFilterBackend, AttrRulesFilterBackend
]
def get_serializer_class(self):
cls = super().get_serializer_class()

View File

@ -2,7 +2,7 @@ from typing import List
from rest_framework.request import Request
from assets.models import Node, PlatformProtocol
from assets.models import Node, PlatformProtocol, Protocol
from assets.utils import get_node_from_request, is_query_node_all_assets
from common.utils import lazyproperty, timeit
@ -78,7 +78,10 @@ class SerializeToTreeNodeMixin:
get_pid = lambda asset: getattr(asset, 'parent_key', '')
else:
get_pid = lambda asset: node_key
ssh_asset_ids = [
str(i) for i in
Protocol.objects.filter(name='ssh').values_list('asset_id', flat=True)
]
data = [
{
'id': str(asset.id),
@ -96,7 +99,8 @@ class SerializeToTreeNodeMixin:
'data': {
'platform_type': asset.platform.type,
'org_name': asset.org_name,
'sftp': asset.platform_id in sftp_enabled_platform,
'sftp': (asset.platform_id in sftp_enabled_platform) \
and (str(asset.id) in ssh_asset_ids),
'name': asset.name,
'address': asset.address
},

View File

@ -3,10 +3,12 @@
from django.db.models import Q
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from django.utils.translation import gettext_lazy as _
from assets.locks import NodeAddChildrenLock
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from common.exceptions import JMSException
from orgs.mixins import generics
from orgs.utils import current_org
from .mixin import SerializeToTreeNodeMixin
@ -41,7 +43,11 @@ class NodeChildrenApi(generics.ListCreateAPIView):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
if value:
children = self.instance.get_children()
if children.filter(value=value).exists():
raise JMSException(_('The same level node name cannot be the same'))
else:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value

View File

@ -6,6 +6,6 @@ type:
method: gather_facts
i18n:
Gather facts from MongoDB:
zh: 从 MongoDB 获取信息
en: Gather facts from MongoDB
ja: MongoDBから事実を取得する
zh: 使用 Ansible 模块 mongodb 获取 mongodb 信息
en: Gather facts from MongoDB using mongodb module
ja: mongodbモジュールを使用して MongoDBから情報を収集する

View File

@ -7,6 +7,6 @@ type:
method: gather_facts
i18n:
Gather facts from MySQL:
zh: 从 MySQL 获取信息
en: Gather facts from MySQL
ja: MySQLから事実を取得する
zh: 使用 Ansible 模块 mysql 从 MySQL server 获取信息
en: Gather facts from MySQL server using mysql module
ja: mysqlモジュールを使用して MySQL serverから情報を収集する

View File

@ -6,6 +6,6 @@ type:
method: gather_facts
i18n:
Gather facts from Oracle:
zh: 从 Oracle 获取信息
en: Gather facts from Oracle
ja: Oracleから事実を取得する
zh: 使用 oracledb 模块获取 Oracle 信息
en: Gather facts from Oracle using oracledb module
ja: oracledbモジュールを使用して Oracleから情報を収集する

View File

@ -6,6 +6,6 @@ type:
method: gather_facts
i18n:
Gather facts for PostgreSQL:
zh: 从 PostgreSQL 获取信息
en: Gather facts for PostgreSQL
ja: PostgreSQLから事実を取得する
zh: 使用 Ansible 模块 postgresql 获取 PostgreSQL 信息
en: Gather facts for PostgreSQL using postgresql module
ja: postgresqlモジュールを使用して PostgreSQLから情報を収集する

View File

@ -7,6 +7,6 @@ type:
method: gather_facts
i18n:
Gather posix facts:
zh: 从 Posix 主机获取信息
en: Gather posix facts
ja: Posixから事実を取得する
zh: 使用 Ansible 指令 gather_facts 从主机获取设备信息
en: Gather facts from asset using gather_facts
ja: gather_factsを使用してPosixから情報を収集する

View File

@ -7,6 +7,6 @@ type:
- windows
i18n:
Gather facts windows:
zh: 从 Windows 获取信息
en: Gather facts windows
ja: Windowsから事実を取得する
zh: 使用 Ansible 指令 gather_facts 从 Windows 获取设备信息
en: Gather facts from Windows using gather_facts
ja: gather_factsを使用してWindowsから情報を収集する

View File

@ -39,7 +39,7 @@ def get_platform_automation_methods(path):
if not path.endswith('manifest.yml'):
continue
with open(path, 'r') as f:
with open(path, 'r', encoding='utf8') as f:
manifest = yaml_load_with_i18n(f)
check_platform_method(manifest, path)
manifest['dir'] = os.path.dirname(path)

View File

@ -1,8 +1,13 @@
id: ping_by_ssh
name: Ping by SSH
name: "{{ 'Ping by paramiko' | trans }}"
category:
- device
- host
type:
- all
method: ping
i18n:
Ping by paramiko:
zh: 使用 Python 模块 paramiko 测试主机可连接性
en: Ping by paramiko module
ja: Paramikoモジュールを使用してホストにPingする

View File

@ -6,6 +6,6 @@ type:
method: ping
i18n:
Ping MongoDB:
zh: 测试 MongoDB 可连接性
en: Ping MongoDB
ja: MongoDBにPingする
zh: 使用 Ansible 模块 mongodb 来测试 MongoDB 可连接性
en: Use ansible mongodb module to test MongoDB
ja: Ansible mongodbモジュールを使用してテストする MongoDB

View File

@ -7,6 +7,6 @@ type:
method: ping
i18n:
Ping MySQL:
zh: 测试 MySQL 可连接性
en: Ping MySQL
ja: MySQLにPingする
zh: 使用 Ansible 模块 mysql 来测试 MySQL 可连接性
en: Use ansible mysql module to test MySQL
ja: Ansible mysqlモジュールを使用してテストする MySQL

View File

@ -6,6 +6,6 @@ type:
method: ping
i18n:
Ping Oracle:
zh: 测试 Oracle 可连接性
en: Ping Oracle
ja: OracleにPingする
zh: 使用 python oracledb 模块来测试 Oracle 可连接性
en: Use python oracledb module to test Oracle
ja: Python oracledbモジュールを使用してテストする Oracle

View File

@ -6,6 +6,6 @@ type:
method: ping
i18n:
Ping PostgreSQL:
zh: 测试 PostgreSQL 连接性
en: Ping PostgreSQL
ja: PostgreSQLにPingする
zh: 使用 Ansible 模块 postgresql 来测试 PostgreSQL 连接性
en: Ping PostgreSQL using ansible postgresql module
ja: ansible postgresql モジュールを使用して PostgreSQL に ping を送信する

View File

@ -6,6 +6,6 @@ type:
method: ping
i18n:
Ping SQLServer:
zh: 测试 SQLServer 连接性
en: Ping SQLServer
ja: SQLServerにPingする
zh: 使用 Ansible 模块 mssql 来测试 SQLServer 连接性
en: Ping SQLServer using ansible mssql module
ja: ansible mssql モジュールを使用して SQLServer に ping を送信する

Some files were not shown because too many files have changed in this diff Show More