Merge branch 'pam' of github.com:jumpserver/jumpserver into pam

pull/14517/head
ibuler 2024-11-12 16:01:31 +08:00
commit a750fbb785
31 changed files with 245 additions and 46 deletions

View File

@ -41,7 +41,7 @@
commands: "{{ params.commands }}"
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
ignore_errors: true
when: ping_info is succeeded
when: ping_info is succeeded and check_conn_after_change
register: change_info
delegate_to: localhost
@ -59,3 +59,4 @@
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
delegate_to: localhost
when: check_conn_after_change

View File

@ -53,3 +53,4 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when: check_conn_after_change

View File

@ -54,3 +54,4 @@
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
when: check_conn_after_change

View File

@ -40,3 +40,4 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}"
when: check_conn_after_change

View File

@ -56,3 +56,4 @@
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ 'verify-full' if check_ca else 'require' if check_ssl else 'prefer' }}"
when: check_conn_after_change

View File

@ -64,3 +64,4 @@
name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
when: check_conn_after_change

View File

@ -100,7 +100,7 @@
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@ -111,5 +111,5 @@
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
when: account.secret_type == "ssh_key" and check_conn_after_change
delegate_to: localhost

View File

@ -100,7 +100,7 @@
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@ -111,5 +111,5 @@
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
when: account.secret_type == "ssh_key" and check_conn_after_change
delegate_to: localhost

View File

@ -4,10 +4,6 @@
- name: Test privileged account
ansible.windows.win_ping:
# - name: Print variables
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Change password
ansible.windows.win_user:
fullname: "{{ account.username}}"
@ -28,4 +24,4 @@
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change

View File

@ -4,10 +4,6 @@
- name: Test privileged account
ansible.windows.win_ping:
# - name: Print variables
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Change password
ansible.windows.win_user:
fullname: "{{ account.username}}"
@ -31,5 +27,5 @@
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
gateway_args: "{{ jms_gateway | default(None) }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change
delegate_to: localhost

View File

@ -93,6 +93,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
if host.get('error'):
return host
host['check_conn_after_change'] = self.execution.snapshot.get('check_conn_after_change', True)
accounts = self.get_accounts(account)
error_msg = _("No pending accounts found")
if not accounts:

View File

@ -0,0 +1,62 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
ansible_become: false
tasks:
- name: Test privileged account (paramiko)
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 }}"
become: "{{ jms_custom_become | default(False) }}"
become_method: "{{ jms_custom_become_method | default('su') }}"
become_user: "{{ jms_custom_become_user | default('') }}"
become_password: "{{ jms_custom_become_password | default('') }}"
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
register: ping_info
delegate_to: localhost
- name: Change asset password (paramiko)
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 }}"
become: "{{ jms_custom_become | default(False) }}"
become_method: "{{ jms_custom_become_method | default('su') }}"
become_user: "{{ jms_custom_become_user | default('') }}"
become_password: "{{ jms_custom_become_password | default('') }}"
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
name: "{{ account.username }}"
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 and check_conn_after_change
register: change_info
delegate_to: localhost
- name: Verify password (paramiko)
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
delegate_to: localhost
when: check_conn_after_change

View File

@ -0,0 +1,32 @@
id: push_account_by_ssh
name: "{{ 'SSH account push' | trans }}"
category:
- device
- host
type:
- all
method: push_account
protocol: ssh
priority: 50
params:
- name: commands
type: list
label: "{{ 'Params commands label' | trans }}"
default: [ '' ]
help_text: "{{ 'Params commands help text' | trans }}"
i18n:
SSH account push:
zh: '使用 SSH 命令行自定义推送'
ja: 'SSHコマンドラインを使用してプッシュをカスタマイズする'
en: 'Custom push using SSH command line'
Params commands help text:
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />&#123;ユーザー名&#125;、&#123;パスワード&#125;、&#123;login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.&#123;login_password&#125;<br />3 .ターミナルの設定<br / >4. ユーザー名 &#123;ユーザー名&#125; 権限 0 パスワード &#123;パスワード&#125; <br />5. 終了'
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use &#123;username&#125;, &#123;password&#125;, &#123;login_password&# 125; format, which will be replaced when executing the task. <br />For example, to change the password of a Cisco host, you generally need to configure five commands:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br / >4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
Params commands label:
zh: '自定义命令'
ja: 'カスタムコマンド'
en: 'Custom command'

View File

@ -53,3 +53,4 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when: check_conn_after_change

View File

@ -54,3 +54,4 @@
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
when: check_conn_after_change

View File

@ -40,3 +40,4 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}"
when: check_conn_after_change

View File

@ -60,5 +60,6 @@
when:
- result is succeeded
- change_info is succeeded
- check_conn_after_change
register: result
failed_when: not result.is_available

View File

@ -66,3 +66,4 @@
name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
when: check_conn_after_change

View File

@ -100,7 +100,7 @@
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@ -111,6 +111,6 @@
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
when: account.secret_type == "ssh_key" and check_conn_after_change
delegate_to: localhost

View File

@ -100,7 +100,7 @@
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@ -111,6 +111,6 @@
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
when: account.secret_type == "ssh_key" and check_conn_after_change
delegate_to: localhost

View File

@ -4,10 +4,6 @@
- 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}}"
@ -28,4 +24,4 @@
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change

View File

@ -4,10 +4,6 @@
- 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}}"
@ -31,5 +27,5 @@
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
gateway_args: "{{ jms_gateway | default(None) }}"
when: account.secret_type == "password"
when: account.secret_type == "password" and check_conn_after_change
delegate_to: localhost

View File

@ -15,17 +15,17 @@ class Migration(migrations.Migration):
name="risk",
field=models.CharField(
choices=[
("zombie", "Long time no login"),
("ghost", "Not managed"),
("long_time_password", "Long time no change"),
("weak_password", "Weak password"),
("group_changed", "Group change"),
("sudo_changed", "Sudo changed"),
("account_deleted", "Account delete"),
("password_expired", "Password expired"),
("no_admin_account", "No admin account"),
("password_error", "Password error"),
("others", "Others"),
('zombie', 'Long time no login'),
('ghost', 'Not managed'),
('long_time_password', 'Long time no change'),
('weak_password', 'Weak password'),
('password_error', 'Password error'),
('password_expired', 'Password expired'),
('group_changed', 'Group change'),
('sudo_changed', 'Sudo changed'),
('account_deleted', 'Account delete'),
('no_admin_account', 'No admin account'),
('others', 'Others')
],
max_length=128,
verbose_name="Risk",

View File

@ -0,0 +1,23 @@
# Generated by Django 4.1.13 on 2024-10-21 09:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_alter_accountrisk_risk'),
]
operations = [
migrations.AddField(
model_name='changesecretautomation',
name='check_conn_after_change',
field=models.BooleanField(default=True, verbose_name='Check connection after change'),
),
migrations.AddField(
model_name='pushaccountautomation',
name='check_conn_after_change',
field=models.BooleanField(default=True, verbose_name='Check connection after change'),
),
]

View File

@ -50,8 +50,14 @@ class AutomationExecution(AssetAutomationExecution):
class ChangeSecretMixin(SecretWithRandomMixin):
ssh_key_change_strategy = models.CharField(
choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.set_jms, verbose_name=_('SSH key change strategy')
choices=SSHKeyStrategy.choices,
max_length=16,
default=SSHKeyStrategy.set_jms,
verbose_name=_('SSH key change strategy')
)
check_conn_after_change = models.BooleanField(
default=True,
verbose_name=_('Check connection after change')
)
get_all_assets: callable # get all assets
@ -81,5 +87,6 @@ class ChangeSecretMixin(SecretWithRandomMixin):
'password_rules': self.password_rules,
'secret_strategy': self.secret_strategy,
'ssh_key_change_strategy': self.ssh_key_change_strategy,
'check_conn_after_change': self.check_conn_after_change,
})
return attr_json

View File

@ -52,8 +52,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
'secret_type', 'secret_strategy', 'secret', 'password_rules',
'ssh_key_change_strategy', 'passphrase', 'params',
'recipients',
'ssh_key_change_strategy', 'passphrase', 'recipients', 'params', 'check_conn_after_change'
]
extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{
'accounts': {'required': True, 'help_text': _('Please enter your account username')},

View File

@ -29,14 +29,14 @@ from terminal.models import EndpointRule, Endpoint
from users.const import FileNameConflictResolution
from users.const import RDPSmartSize, RDPColorQuality
from users.models import Preference
from ..models import ConnectionToken, date_expired_default
from ..models import ConnectionToken, AdminConnectionToken, date_expired_default
from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer,
ConnectionTokenReusableSerializer, ConnectTokenVirtualAppOptionSerializer
)
__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet', 'AdminConnectionTokenViewSet']
logger = get_logger(__name__)
@ -558,3 +558,14 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
else:
logger.error('Release applet account error: {}'.format(lock_key))
return Response({'error': 'not found or expired'}, status=400)
class AdminConnectionTokenViewSet(SuperConnectionTokenViewSet):
def check_permissions(self, request):
user = request.user
if not user.is_superuser:
self.permission_denied(request)
def get_queryset(self):
return AdminConnectionToken.objects.all()

View File

@ -37,3 +37,9 @@ class MFAType(TextChoices):
SMS = MFASms.name, MFASms.display_name
Radius = MFARadius.name, MFARadius.display_name
Custom = MFACustom.name, MFACustom.display_name
class ConnectionTokenType(TextChoices):
ADMIN = 'admin', 'Admin'
SUPER = 'super', 'Super'
USER = 'user', 'User'

View File

@ -0,0 +1,30 @@
# Generated by Django 4.1.13 on 2024-11-11 11:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0003_sshkey'),
]
operations = [
migrations.AddField(
model_name='connectiontoken',
name='type',
field=models.CharField(choices=[('admin', 'Admin'), ('super', 'Super'), ('user', 'User')], default='user', max_length=16, verbose_name='Type'),
),
migrations.CreateModel(
name='AdminConnectionToken',
fields=[
],
options={
'verbose_name': 'Admin connection token',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('authentication.connectiontoken',),
),
]

View File

@ -12,6 +12,7 @@ from rest_framework.exceptions import PermissionDenied
from accounts.models import VirtualAccount
from assets.const import Protocol
from assets.const.host import GATEWAY_NAME
from authentication.const import ConnectionTokenType
from common.db.fields import EncryptTextField
from common.exceptions import JMSException
from common.utils import lazyproperty, pretty_string, bulk_get
@ -26,6 +27,8 @@ def date_expired_default():
class ConnectionToken(JMSOrgBaseModel):
_type = ConnectionTokenType.USER
value = models.CharField(max_length=64, default='', verbose_name=_("Value"))
user = models.ForeignKey(
'users.User', on_delete=models.SET_NULL, null=True, blank=True,
@ -52,6 +55,11 @@ class ConnectionToken(JMSOrgBaseModel):
)
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
type = models.CharField(
max_length=16, choices=ConnectionTokenType.choices,
default=ConnectionTokenType.USER, verbose_name=_('Type')
)
class Meta:
ordering = ('-date_expired',)
permissions = [
@ -60,6 +68,10 @@ class ConnectionToken(JMSOrgBaseModel):
]
verbose_name = _('Connection token')
def save(self, *args, **kwargs):
self.type = self._meta.model._type
return super().save(*args, **kwargs)
@property
def is_expired(self):
return self.date_expired < timezone.now()
@ -268,9 +280,28 @@ class ConnectionToken(JMSOrgBaseModel):
class SuperConnectionToken(ConnectionToken):
_type = ConnectionTokenType.SUPER
class Meta:
proxy = True
permissions = [
('view_superconnectiontokensecret', _('Can view super connection token secret'))
]
verbose_name = _("Super connection token")
class AdminConnectionTokenManager(models.Manager):
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(type=ConnectionTokenType.ADMIN)
return queryset
class AdminConnectionToken(ConnectionToken):
_type = ConnectionTokenType.ADMIN
objects = AdminConnectionTokenManager()
class Meta:
proxy = True
verbose_name = _("Admin connection token")

View File

@ -13,6 +13,7 @@ router.register('sso', api.SSOViewSet, 'sso')
router.register('temp-tokens', api.TempTokenViewSet, 'temp-token')
router.register('connection-token', api.ConnectionTokenViewSet, 'connection-token')
router.register('super-connection-token', api.SuperConnectionTokenViewSet, 'super-connection-token')
router.register('admin-connection-token', api.AdminConnectionTokenViewSet, 'admin-connection-token')
router.register('confirm', api.UserConfirmationViewSet, 'confirm')
router.register('ssh-key', api.SSHkeyViewSet, 'ssh-key')