push_system_user.py 10.2 KB
Newer Older
baltery's avatar
baltery 已提交
1 2
# ~*~ coding: utf-8 ~*~

baltery's avatar
baltery 已提交
3
from itertools import groupby
baltery's avatar
baltery 已提交
4
from celery import shared_task
5
from common.db.utils import get_object_if_need, get_objects
baltery's avatar
baltery 已提交
6
from django.utils.translation import ugettext as _
7
from django.db.models import Empty
baltery's avatar
baltery 已提交
8 9

from common.utils import encrypt_password, get_logger
10
from assets.models import SystemUser, Asset, AuthBook
11
from orgs.utils import org_aware_func, tmp_to_root_org
baltery's avatar
baltery 已提交
12
from . import const
baltery's avatar
baltery 已提交
13
from .utils import clean_ansible_task_hosts, group_asset_by_platform
baltery's avatar
baltery 已提交
14 15 16 17 18 19 20 21 22


logger = get_logger(__file__)
__all__ = [
    'push_system_user_util', 'push_system_user_to_assets',
    'push_system_user_to_assets_manual', 'push_system_user_a_asset_manual',
]


23 24 25 26 27 28 29 30 31 32 33
def _split_by_comma(raw: str):
    try:
        return [i.strip() for i in raw.split(',')]
    except AttributeError:
        return []


def _dump_args(args: dict):
    return ' '.join([f'{k}={v}' for k, v in args.items() if v is not Empty])


baltery's avatar
baltery 已提交
34
def get_push_unixlike_system_user_tasks(system_user, username=None):
35 36
    comment = system_user.name

baltery's avatar
baltery 已提交
37 38
    if username is None:
        username = system_user.username
39 40 41 42 43 44 45

    if system_user.username_same_with_user:
        from users.models import User
        user = User.objects.filter(username=username).only('name', 'username').first()
        if user:
            comment = f'{system_user.name}[{str(user)}]'

baltery's avatar
baltery 已提交
46 47 48
    password = system_user.password
    public_key = system_user.public_key

49 50 51 52 53 54 55 56 57 58
    groups = _split_by_comma(system_user.system_groups)

    if groups:
        groups = '"%s"' % ','.join(groups)

    add_user_args = {
        'name': username,
        'shell': system_user.shell or Empty,
        'state': 'present',
        'home': system_user.home or Empty,
59 60
        'groups': groups or Empty,
        'comment': comment
61 62
    }

baltery's avatar
baltery 已提交
63 64
    tasks = [
        {
baltery's avatar
baltery 已提交
65
            'name': 'Add user {}'.format(username),
baltery's avatar
baltery 已提交
66 67
            'action': {
                'module': 'user',
68
                'args': _dump_args(add_user_args),
baltery's avatar
baltery 已提交
69 70 71
            }
        },
        {
baltery's avatar
baltery 已提交
72
            'name': 'Add group {}'.format(username),
baltery's avatar
baltery 已提交
73 74
            'action': {
                'module': 'group',
baltery's avatar
baltery 已提交
75
                'args': 'name={} state=present'.format(username),
baltery's avatar
baltery 已提交
76 77 78
            }
        }
    ]
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    if not system_user.home:
        tasks.extend([
            {
                'name': 'Check home dir exists',
                'action': {
                    'module': 'stat',
                    'args': 'path=/home/{}'.format(username)
                },
                'register': 'home_existed'
            },
            {
                'name': "Set home dir permission",
                'action': {
                    'module': 'file',
                    'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
                },
                'when': 'home_existed.stat.exists == true'
            }
        ])
baltery's avatar
baltery 已提交
98
    if password:
baltery's avatar
baltery 已提交
99
        tasks.append({
baltery's avatar
baltery 已提交
100
            'name': 'Set {} password'.format(username),
baltery's avatar
baltery 已提交
101 102 103
            'action': {
                'module': 'user',
                'args': 'name={} shell={} state=present password={}'.format(
baltery's avatar
baltery 已提交
104 105
                    username, system_user.shell,
                    encrypt_password(password, salt="K3mIlKK"),
baltery's avatar
baltery 已提交
106 107 108
                ),
            }
        })
baltery's avatar
baltery 已提交
109
    if public_key:
baltery's avatar
baltery 已提交
110
        tasks.append({
baltery's avatar
baltery 已提交
111
            'name': 'Set {} authorized key'.format(username),
baltery's avatar
baltery 已提交
112 113 114
            'action': {
                'module': 'authorized_key',
                'args': "user={} state=present key='{}'".format(
baltery's avatar
baltery 已提交
115
                    username, public_key
baltery's avatar
baltery 已提交
116 117 118 119 120 121 122 123 124 125 126
                )
            }
        })
    if system_user.sudo:
        sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
        sudo_list = sudo.split('\n')
        sudo_tmp = []
        for s in sudo_list:
            sudo_tmp.append(s.strip(','))
        sudo = ','.join(sudo_tmp)
        tasks.append({
baltery's avatar
baltery 已提交
127
            'name': 'Set {} sudo setting'.format(username),
baltery's avatar
baltery 已提交
128 129 130 131
            'action': {
                'module': 'lineinfile',
                'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
                        "line='{0} ALL=(ALL) NOPASSWD: {1}' "
baltery's avatar
baltery 已提交
132
                        "validate='visudo -cf %s'".format(username, sudo)
baltery's avatar
baltery 已提交
133 134 135 136 137 138
            }
        })

    return tasks


baltery's avatar
baltery 已提交
139 140 141 142
def get_push_windows_system_user_tasks(system_user, username=None):
    if username is None:
        username = system_user.username
    password = system_user.password
143 144 145 146 147
    groups = {'Users', 'Remote Desktop Users'}
    if system_user.system_groups:
        groups.update(_split_by_comma(system_user.system_groups))
    groups = ','.join(groups)

baltery's avatar
baltery 已提交
148
    tasks = []
baltery's avatar
baltery 已提交
149
    if not password:
150
        logger.error("Error: no password found")
baltery's avatar
baltery 已提交
151
        return tasks
baltery's avatar
baltery 已提交
152 153
    task = {
        'name': 'Add user {}'.format(username),
baltery's avatar
baltery 已提交
154 155 156 157 158 159 160 161 162
        'action': {
            'module': 'win_user',
            'args': 'fullname={} '
                    'name={} '
                    'password={} '
                    'state=present '
                    'update_password=always '
                    'password_expired=no '
                    'password_never_expires=yes '
163
                    'groups="{}" '
baltery's avatar
baltery 已提交
164
                    'groups_action=add '
165
                    ''.format(username, username, password, groups),
baltery's avatar
baltery 已提交
166
        }
baltery's avatar
baltery 已提交
167 168
    }
    tasks.append(task)
baltery's avatar
baltery 已提交
169 170 171
    return tasks


baltery's avatar
baltery 已提交
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
def get_push_system_user_tasks(system_user, platform="unixlike", username=None):
    """
    :param system_user:
    :param platform:
    :param username: 当动态时,近推送某个
    :return:
    """
    get_task_map = {
        "unixlike": get_push_unixlike_system_user_tasks,
        "windows": get_push_windows_system_user_tasks,
    }
    get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks)
    if not system_user.username_same_with_user:
        return get_tasks(system_user)
    tasks = []
    # 仅推送这个username
    if username is not None:
        tasks.extend(get_tasks(system_user, username))
        return tasks
    users = system_user.users.all().values_list('username', flat=True)
    print(_("System user is dynamic: {}").format(list(users)))
    for _username in users:
        tasks.extend(get_tasks(system_user, _username))
baltery's avatar
baltery 已提交
195 196 197
    return tasks


baltery's avatar
baltery 已提交
198 199
@org_aware_func("system_user")
def push_system_user_util(system_user, assets, task_name, username=None):
baltery's avatar
baltery 已提交
200
    from ops.utils import update_or_create_ansible_task
201 202
    assets = clean_ansible_task_hosts(assets, system_user=system_user)
    if not assets:
baltery's avatar
baltery 已提交
203 204
        return {}

205 206
    assets_sorted = sorted(assets, key=group_asset_by_platform)
    platform_hosts = groupby(assets_sorted, key=group_asset_by_platform)
baltery's avatar
baltery 已提交
207

baltery's avatar
baltery 已提交
208 209 210
    def run_task(_tasks, _hosts):
        if not _tasks:
            return
baltery's avatar
baltery 已提交
211
        task, created = update_or_create_ansible_task(
baltery's avatar
baltery 已提交
212
            task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
baltery's avatar
baltery 已提交
213 214 215 216
            options=const.TASK_OPTIONS, run_as_admin=True,
        )
        task.run()

217 218 219 220 221 222 223 224 225 226 227 228 229 230
    if system_user.username_same_with_user:
        if username is None:
            # 动态系统用户,但是没有指定 username
            usernames = list(system_user.users.all().values_list('username', flat=True).distinct())
        else:
            usernames = [username]
    else:
        # 非动态系统用户指定 username 无效
        assert username is None, 'Only Dynamic user can assign `username`'
        usernames = [system_user.username]

    for platform, _assets in platform_hosts:
        _assets = list(_assets)
        if not _assets:
baltery's avatar
baltery 已提交
231 232
            continue
        print(_("Start push system user for platform: [{}]").format(platform))
233
        print(_("Hosts count: {}").format(len(_assets)))
baltery's avatar
baltery 已提交
234

235
        id_asset_map = {_asset.id: _asset for _asset in _assets}
236
        asset_ids = id_asset_map.keys()
237 238 239
        no_special_auth = []
        special_auth_set = set()

240
        auth_books = AuthBook.objects.filter(username__in=usernames, asset_id__in=asset_ids)
241 242 243

        for auth_book in auth_books:
            special_auth_set.add((auth_book.username, auth_book.asset_id))
baltery's avatar
baltery 已提交
244

245 246
        for _username in usernames:
            no_special_assets = []
247
            for asset_id in asset_ids:
248 249 250 251 252 253 254 255 256 257 258 259 260 261
                if (_username, asset_id) not in special_auth_set:
                    no_special_assets.append(id_asset_map[asset_id])
            if no_special_assets:
                no_special_auth.append((_username, no_special_assets))

        for _username, no_special_assets in no_special_auth:
            tasks = get_push_system_user_tasks(system_user, platform, username=_username)
            run_task(tasks, no_special_assets)

        for auth_book in auth_books:
            system_user._merge_auth(auth_book)
            tasks = get_push_system_user_tasks(system_user, platform, username=auth_book.username)
            asset = id_asset_map[auth_book.asset_id]
            run_task(tasks, [asset])
baltery's avatar
baltery 已提交
262

baltery's avatar
baltery 已提交
263 264

@shared_task(queue="ansible")
265
@tmp_to_root_org()
266
def push_system_user_to_assets_manual(system_user, username=None):
267 268 269
    """
    将系统用户推送到与它关联的所有资产上
    """
X
xinwen 已提交
270
    system_user = get_object_if_need(SystemUser, system_user)
baltery's avatar
baltery 已提交
271
    assets = system_user.get_related_assets()
baltery's avatar
baltery 已提交
272
    task_name = _("Push system users to assets: {}").format(system_user.name)
273
    return push_system_user_util(system_user, assets, task_name=task_name, username=username)
baltery's avatar
baltery 已提交
274 275 276


@shared_task(queue="ansible")
277
@tmp_to_root_org()
baltery's avatar
baltery 已提交
278
def push_system_user_a_asset_manual(system_user, asset, username=None):
279 280 281
    """
    将系统用户推送到一个资产上
    """
baltery's avatar
baltery 已提交
282 283 284 285
    if username is None:
        username = system_user.username
    task_name = _("Push system users to asset: {}({}) => {}").format(
        system_user.name, username, asset
baltery's avatar
baltery 已提交
286
    )
baltery's avatar
baltery 已提交
287
    return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
baltery's avatar
baltery 已提交
288 289 290


@shared_task(queue="ansible")
291
@tmp_to_root_org()
292
def push_system_user_to_assets(system_user_id, asset_ids, username=None):
293 294 295
    """
    推送系统用户到指定的若干资产上
    """
296
    system_user = SystemUser.objects.get(id=system_user_id)
297
    assets = get_objects(Asset, asset_ids)
baltery's avatar
baltery 已提交
298
    task_name = _("Push system users to assets: {}").format(system_user.name)
299

300
    return push_system_user_util(system_user, assets, task_name, username=username)
baltery's avatar
baltery 已提交
301 302 303 304 305 306 307

# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
#     for system_user in SystemUser.objects.all():
baltery's avatar
baltery 已提交
308
#         push_system_user_related_nodes(system_user)