一文读懂 Ansible 不同上下文提权

2023-01-30 15:37:50 浏览数 (2)

写在前面

  • 生活加油,今天和小伙伴们分享一些 Ansible 提权的笔记
  • 博文内容涉及
    • 如何选择Ansible的提权方式
    • 提权策略有哪些
    • 提权策略具体的Demo
  • 食用方式:
    • 需要有 Ansible 基础,了解 Ansible 变量的使用
  • 理解不足小伙伴帮忙指正
  • 近几天有风也有云,就是热了些,你那里呢 ^_^

我所渴求的,無非是將心中脫穎語出的本性付諸生活,為何竟如此艱難呢 ------赫尔曼·黑塞《德米安》


Ansible 控制提权

ansible 版本

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible --version
ansible 2.9.25
  config file = /root/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Aug  4 2017, 00:39:18) [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)]

对于初学ansible的小伙伴来将,提权配置大都是通过配置ansible.cfg的方式来配置提权,通过配置的文件的方式配置的提权,对所有执行的剧本角色有提权,这样的好处是,简单方便,但是有一定的风险,任何命令都通用过root来执行,即任何进程都是具有系统的最高权限,对于黑客来讲,最想得到的即root权限,如果进程被植入了木马病毒之类,控制了进程即拥有root权限。所以任何命令通过root来执行是一件很危险的事。

所以从安全角度考虑,要遵循最小权限原则,即要求系统只授予主体必要的权限,而不要过度授权,这样能有效地减少系统、网络、应用、数据库出错的机会。

所以Linux系统中,一种良好的操作习惯是使用普通账户登录,在执行需要root权限的操作时,再通过sudo命令完成。这样能最大化地降低一些误操作导致的风险;同时普通账户被盗用后,与root帐户被盗用所导致的后果是完全不同的。

在 Ansible 中提供了很多细粒度的提权方式,可以根据需要有选择的提权,通过不同的的提权策略来配置提权。

选择合适的提权方法

在任务执行时,尤其是使用ansible处理一些批量初始化集群节点的情况,大多数需要提权处理,在选择如何控制提权时,在什么位置提权,我们需要考虑以下需求:

  • 要使Playbook尽量保持简单,不因为提权处理,提高剧本的复杂度,对于可变的部分统一管理,提权处理统一约束。
    • 如果太复杂考虑分层。需要提权剧本任务考虑分组分层单独管理,使用组变量来控制提权,或者单独划分ansibler角色处理
    • 如果考虑剧本的复杂、只读性,可以通过配置文件,命令行的方式来提权。
    • 如果相同剧本不同主机需要不同提权,可以通过ansible 连接变量(ansible_*)来控制提权。
  • 以最低特权运行任务以避免意外破坏和由于剧本错误对托管主机的损害。

有时候我们直接使用root用户来连接受管机,以避免特权升级。但是在生产环境,这通常不是一个好的做法;如果任何运行剧本的人都使用root来连接管理主机。这也使得很难确定是哪个运维执行了哪个剧本。容易背锅。但是实验环境或者测试环境我们可以这样使用。

一个好的实践是有选择地控制哪些游戏或任务需要特权升级。例如,如果apache用户可以启动httpd服务器,则不需要以root用户运行。理想情况下,以尽可能简单的方式配置提权,并且应该清楚是否将其用于任务。

提权策略

Ansible Playbook 可以在许多不同的级别上实现提权。常见的提权方法:

  • 配置文件和命令行提权
  • 剧本中提权
  • 块中提权
  • 任务中提权
  • 角色中提权
  • 连接变量配置提权

配置文件和命令行提权

配置文件提权

如果将Ansible配置文件中的 privilege_escalation 部分中的become布尔值设为 yes/True,则 Playbook 中的所有Play 都将默认使用提权。

代码语言:javascript复制
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False

在受管主机上运行时,这些 Play 将会使用当前的become_method的方式来切换当前用户为提权为 become_user用户。

可以看到配置之后提权用户为root

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible all -m command -a id
vms82.liruilongs.github.io | CHANGED | rc=0 >>
uid=0(root) gid=0(root) 组=0(root)

当把become设置为false时,我们观察,并没有被提权,而是使用普通用户liruilong进行连接

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible all -m command -a id
vms82.liruilongs.github.io | CHANGED | rc=0 >>
uid=1003(liruilong) gid=1003(liruilong) 组=1003(liruilong)
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$
通过命令行提权

在使用命令行选项执行Playbook时,也可以覆盖配置文件并指定提权设置。下表比较了配置指令和命令行

选项:

配置文件参数

命令行参数

become

--become / -b

become_method

--become-method=BECOME_METIHOD

become_user

--become-user=BECOME_USER

become_password

--ask-become-pass /-K

如果Ansible配置文件指定 become: false,但是命令行中含-b选项,则Ansible将忽略配置文件,并且默认使用提权。即命令行的方式要高于配置文件提取

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat ansible.cfg
[defaults]
# 主机清单文件,就是要控制的主机列表
inventory=inventory
# 连接受管机器的远程的用户名
remote_user=liruilong
# 角色目录
roles_path=roles
# 设置用户的su 提权
[privilege_escalation]
become=False
#become_method=sudo
#become_user=root
#become_ask_pass=False

不使用命令行

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible all -m command -a id
vms82.liruilongs.github.io | CHANGED | rc=0 >>
uid=1003(liruilong) gid=1003(liruilong) 组=1003(liruilong)

使用提权命令

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible all -m command -a id -b
vms82.liruilongs.github.io | CHANGED | rc=0 >>
uid=0(root) gid=0(root) 组=0(root)

即命令行的提权要高于配置文件的提权

Play 剧本中的提权

如果 Play 中不指定是否使用提权,默认是不提权的,会使用配置文件或命令行中的默认设置。ansible_user_id用于显示当前操作的用户

代码语言:javascript复制
---
- name: Become the user "manager"
  hosts: all
  tasks:
    - name: Show the user used by this play
      debug:
        var: ansible_user_id

可以发现没有提权

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook becomet.yaml

PLAY [Become the user "manager"] ***********************************************************************

TASK [Gathering Facts] *********************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [Show the user used by this play] *****************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "liruilong"
}

PLAY RECAP *********************************************************************************************
vms82.liruilongs.github.io : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

可以明确指定各个 Play 是否使用提权。通过剧本中become: true的方式

代码语言:javascript复制
- name: Become the user "manager"
  hosts: webservers
  become: true
  tasks:
  - name: Show the user used by this play
    debug:
      var: ansible_user_id

默认不提权,配置之后可以实现提权,即剧本的提权要高于默认配置

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become.yaml

PLAY [Become the user "manager"] ***********************************************************************

TASK [Gathering Facts] *********************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [Show the user used by this play] *****************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "root"
}

PLAY RECAP *********************************************************************************************
vms82.liruilongs.github.io : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

任务中的提权

也可以只为 Play 中的一个任务打开或关闭提权

代码语言:javascript复制
---
- name: Become the user "manager"
  hosts: all
  become: false
  tasks:
    - name: tasks sudo 1
      become: true
      yum:
        name: httpd
        state: installed
    - name: tasks sudo 2
      yum:
        name: nginx
        state: installed

任务二没有提权,提示我们需要root来执行命令

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become.yaml

PLAY [Become the user "manager"] ***************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [tasks sudo 1] ****************************************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [tasks sudo 2] ****************************************************************************************************
fatal: [vms82.liruilongs.github.io]: FAILED! => {"changed": false, "changes": {"installed": ["nginx"]}, "msg": "You need to be root to perform this command.n", "rc": 1, "results": ["Loaded plugins: fastestmirrorn"]}

PLAY RECAP *************************************************************************************************************
vms82.liruilongs.github.io : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

block块中的提权

如果Play中有一部分任务需要(或不需要)提权,可以在 block 上设置 become。这里需要注意一下,在block中提权的话,对于提权参数只能放到任务的末尾,不能放到任务的第一个位置。

下面的的写法就不对,会报语法错误

代码语言:javascript复制
---
- name: Become the user "manager"
  hosts: all
  become: false
  tasks:
   - block:
     become: true
     - name: tasks sudo 1
       become: false
       yum:
         name: tomcat
         state: installed
     - name: tasks sudo 2
       yum:
         name: nginx
         state: installed

即下面的这种写法是正确的

代码语言:javascript复制
---
- name: Become the user "manager"
  hosts: all
  become: false
  tasks:
   - block:
     - name: tasks sudo 1
       become: false
       yum:
         name: tomcat
         state: installed
     - name: tasks sudo 2
       yum:
         name: nginx
         state: installed
     become: true

我们来具看一下,安装tomcat需要root权限,虽然我们在block中提权了,但是在任务中设置不提权,所以会被覆盖。

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become-block.yaml

PLAY [Become the user "manager"] *********************************************************************
TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [tasks sudo 1] **********************************************************************************
fatal: [vms82.liruilongs.github.io]: FAILED! => {"changed": false, "changes": {"installed": ["tomcat"]}, "msg": "You need to be root to perform this command.n", "rc": 1, "results": ["Loaded plugins: fastestmirrorn"]}

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$

修改yaml文件之后,我们在来看一下,默认情况下,当block中设置了提权,那么默认情况下,block 块内的任务都是提权状态

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat become-block.yaml
---
- name: Become the user "manager"
  hosts: all
  become: false
  tasks:
   - block:
     - name: tasks sudo 1
       become: trueb
       yum:
         name: tomcat
         state: installed
     - name: tasks sudo 2
       yum:
         name: nginx
         state: installed
       become: false
     become: true

可以看到 Tomcat 已经安装成功,但是nginx安装失败,提示需要root权限,因为我们对yum模块设置了不提权become: false,即对于block中的提权,任务中具体模块提权要高于block的提权

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become-block.yaml

PLAY [Become the user "manager"] *********************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [tasks sudo 1] **********************************************************************************
changed: [vms82.liruilongs.github.io]

TASK [tasks sudo 2] **********************************************************************************
fatal: [vms82.liruilongs.github.io]: FAILED! => {"changed": false, "changes": {"installed": ["nginx"]}, "msg": "You need to be root to perform this command.n", "rc": 1, "results": ["Loaded plugins: fastestmirrorn"]}

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$

角色中的提权

角色可以通过两种基本方式来执行提权:

  • 针对角色本身,在其内部或针对其任务设置提权变量。这里不多讲,方式太多啦,在角色中可以通过变量或者直接的task目录下你的main.yaml 文件中进行提权

角色任务剧本,创建一个用户

代码语言:javascript复制
---
# tasks file for become_demo
- name: become roles  Demo
  debug:
    var: ansible_user_id
- user:
    name: liruilong1
    state: present
~   

调用角色剧本

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat become_roles_demo.yaml
---
- hosts: all
  roles:
    - role: become_demo

创建用户需要root权限,所以执行报错

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become_roles_demo.yaml

PLAY [all] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [become_demo : become roles  Demo] **************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "liruilong"
}

TASK [become_demo : user] ****************************************************************************
fatal: [vms82.liruilongs.github.io]: FAILED! => {"changed": false, "cmd": "/sbin/useradd -m liruilong1", "msg": "[Errno 13] Permission denied", "rc": 13}

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

修改角色任务执行的文件,添加提权

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat roles/become_demo/tasks/main.yml
---
# tasks file for become_demo
- name: become roles  Demo
  debug:
    var: ansible_user_id
- user:
    name: liruilong1
    state: present
  become: true

可以正常提权创建用户。

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become_roles_demo.yaml

PLAY [all] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [become_demo : become roles  Demo] **************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "liruilong"
}

TASK [become_demo : user] ****************************************************************************
changed: [vms82.liruilongs.github.io]

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • 我们也可以在 Ansible 配置Playbook 中指定此信息。
代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat roles/become_demo/tasks/main.yml
---
# tasks file for become_demo
- name: become roles  Demo
  debug:
    var: ansible_user_id
- user:
    name: liruilong2
    state: present

这里我么修改调用角色剧本文件,提权处理

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat become_roles_demo.yaml
---
- hosts: all
  roles:
    - role: become_demo
      become: true

用户创建成功

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become_roles_demo.yaml

PLAY [all] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [become_demo : become roles  Demo] **************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "liruilong"
}

TASK [become_demo : user] ****************************************************************************
changed: [vms82.liruilongs.github.io]

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

我们最后在受管机器上看一下

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible all -m shell -a "grep liruilong  /etc/passwd "
vms82.liruilongs.github.io | CHANGED | rc=0 >>
liruilong:x:1003:1003::/home/liruilong:/bin/bash
liruilong1:x:1006:1006::/home/liruilong1:/bin/bash
liruilong2:x:1007:1007::/home/liruilong2:/bin/bash
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$

使用变量进行提权

当然我们还可以使用变量来配置提权。这些变量可以作为清单变量应用到组或各个主机上。

下表将 Playbook配置指令与连接变量名称进行比较:

所谓连接变量,即ansible在连接受管机的时候会对连接相关的变量赋值。默认情况下有默认值,我们也可以主动修改

配置文件参数

连接变量参数

become

ansible_become

become_method

ansible_become_method

become_user

ansible_become_user

become_password

ansible_become_pass

变量的定义方式可以有很多,感兴趣小伙伴可以看看我之前的博文,我们来简单的看几个

主机组级别中设置连接变量:
代码语言:javascript复制
webservers: 
  hosts: 
    servera.lab.example.com:
    serverb.lab.example.com: 
  vars: 
     ansible_become: true

来看一个Demo,添加all组变量ansible_become: true

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$echo "ansible_become: true" > inventory/group_vars/all
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$vim roles/become_demo/tasks/main.yml

角色行为为删除刚才创建的用户

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat  roles/become_demo/tasks/main.yml
---
# tasks file for become_demo
- name: become roles  Demo
  debug:
    var: ansible_user_id
- user:
    name: liruilong2
    state: absent

角色删除成功,即通过连接变量实现提权

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become_roles_demo.yaml

PLAY [all] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [become_demo : become roles  Demo] **************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "root"
}

TASK [become_demo : user] ****************************************************************************
changed: [vms82.liruilongs.github.io]

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

主机级别中设置连接变量:
代码语言:javascript复制
webservers: 
  hosts: 
    servera.lab.example.com: 
      ansible_become: true 
    serverb.lab.example.com:

同样的方式,这里我们设置组变量为不提权,主机变量为提权。

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$echo "ansible_become: false" > inventory/group_vars/all
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$echo "ansible_become: true" >  inventory/host_vars/vms82.liruilongs.github.io.yaml

角色行为为删除用户

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat roles/become_demo/tasks/main.yml
---
# tasks file for become_demo
- name: become roles  Demo
  debug:
    var: ansible_user_id
- user:
    name: liruilong1
    state: absent

用户被删除成功,即主机变量优先级要大于组变量

代码语言:javascript复制
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible-playbook become_roles_demo.yaml

PLAY [all] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [vms82.liruilongs.github.io]

TASK [become_demo : become roles  Demo] **************************************************************
ok: [vms82.liruilongs.github.io] => {
    "ansible_user_id": "root"
}

TASK [become_demo : user] ****************************************************************************
changed: [vms82.liruilongs.github.io]

PLAY RECAP *******************************************************************************************
vms82.liruilongs.github.io : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

当然我们也可以在Playbook中设置连接变量

代码语言:javascript复制
- name: Example play using connection variables
  hosts: webservers
  vars:
    ansibte_become: true
  tasks:
    - name: Play will use privilege escalation even if inventory says no
      yum:
        name: httpd
        state: installed

参考博文书籍

《Red Hat Ansible Engine 2.8 DO447》

《白帽子讲Web安全》

https://docs.ansible.com/ansible/latest/user_guide/become.html

https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html

0 人点赞