본문 바로가기

DevOps/Ansible

[A101] 7. Ansible 시스템 보안 설정

  • 이 글은 CloudNet@ 팀 gasida님의 스터디 A101 1기 내용 및 실습으로 작성된 글입니다.

✅  사전준비로 유저 생성 및 패스워드 설정

  • 프로젝트 디렉터리 생성 및 플레이북 생성과 ansible.cfg, inventory를 생성한다.
# 디렉토리 생성
mkdir ~/my-ansible/chapter_09.1
cd ~/my-ansible/chapter_09.1

# ansible.cfg, inventory 파일 작성
cat <<EOT > ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ubuntu
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT

cat <<EOT> inventory
tnode1
tnode2
tnode3
EOT
  • 사용자 계정 정보가 정의된 변수 파일을 생성
  • ~/my-ansible/chapter_09.1/vars/users.yml
mkdir vars
cat <<EOT > vars/users.yml
user_info:
  - userid: "ansible"
    userpw: "ansiblePw1"
  - userid: "stack"
    userpw: "stackPw1"
EOT
  • 사용자 계정을 생성하는 플레이북을 작성 : 모든 호스트에 동일하게 생성하며 vault로 작성된 변수 파일을 읽어 사용함
  • ~/my-ansible/chapter_09.1/create_user.yml
---

- hosts: all

  # vault로 사용자 계정 관련 변수가 정의된 파일을 임포트하여 사용
  vars_files:
    - vars/users.yml

  tasks:
  # loop 문을 사용하여 user_info의 userid와 userpw 사용
  - name: Create user
    ansible.builtin.user:
      name: "{{ item.userid }}"
      password: "{{ item.userpw | password_hash('sha512', 'mysecret') }}"
      state: present
      shell: /bin/bash
    loop: "{{ user_info }}"

 

  • 실행 및 확인
#
ansible-playbook create_user.yml

# 계정 생성 확인
ansible -m shell -a "tail -n 3 /etc/passwd" all

tnode2 | CHANGED | rc=0 >>
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
ansible:x:1001:1001::/home/ansible:/bin/bash
stack:x:1002:1002::/home/stack:/bin/bash
tnode1 | CHANGED | rc=0 >>
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
ansible:x:1001:1001::/home/ansible:/bin/bash
stack:x:1002:1002::/home/stack:/bin/bash
tnode3 | CHANGED | rc=0 >>
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
ansible:x:1001:1001::/home/ansible:/bin/bash
stack:x:1002:1002::/home/stack:/bin/bash

 

✅  보안  설정 자동화  

1. 패스워드 변경 주기 설정하기

  • 패스워드 변경 주기를 설정할 대상 호스트는 인벤토리를 통해 설정한다.
  • 패스워드 변경 주기를 설정할 사용자 계정 정보와 최대 변경일은 변수를 통해 별도의 파일로 정의한다.
  • 패스워드 변경 주기 설정은 ansible.builtin.user 모듈을 이용한다.

  • 플레이북 설계시 사용자 계정과 최대 변경일을 변수로 설정하기 위해 별도 변수파일을 생성하여 관리 (vars_maxdays.yml)
  • 메인 플레이북 set_chage_password.yml 파일에는 변경 주기를 설정할 태스트가 포함
#
mkdir ~/my-ansible/chapter_11.1
cd ~/my-ansible/chapter_11.1

# ansible.cfg, inventory 파일 작성
cat <<EOT> ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ubuntu
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT

cat <<EOT> inventory
[tnode]
tnode1
tnode2
tnode3
EOT
  • 플레이북 설계시 사용자 계정과 최대 변경일을 변수로 설정하기 위해 별도 변수파일을 생성하여 관리 (vars_maxdays.yml)
---

Userinfo:
  - username: ansible
    maxdays: 90
  - username: stack
    maxdays: 90
  • 메인 플레이북 set_chage_password.yml 파일에는 변경 주기를 설정할 태스트가 포함
---

- hosts: tnode
  vars_files: vars_maxdays.yml
  
  tasks:
    - name: Change Password Maxdays
      ansible.builtin.user:
        name: "{{ item.username }}"
        password_expire_max: "{{ item.maxdays }}"
      loop: "{{ Userinfo }}"
 

chage_사용법 [AllThatLinux!]

$ chage --help Usage: chage [options] LOGIN Options: -d, --lastday LAST_DAY set date of last password change to LAST_DAY -E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE -h, --help display this help message and exit -I, --inactive IN

atl.kr

  • change의 오타인줄 알았는데 아니였다..ㅎ
# 문법 체크
ansible-playbook --syntax-check set_chage_password.yml

# 시뮬레이션
ansible-playbook --check set_chage_password.yml

# 확인 : chage -l ansible
man chage
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo chage -l ansible; echo; done
Password expires                                        : never
...
Maximum number of days between password change          : 99999

# 실행
ansible-playbook set_chage_password.yml

# 확인 : chage -l ansible
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo chage -l ansible; echo; done
>> tnode1 <<
Last password change                                    : Jan 10, 2024
Password expires                                        : Apr 09, 2024
...
Maximum number of days between password change          : 90
  • 확인 
ubuntu@server:~/my-ansible/chapter_11.1$ for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo chage -l ansible; echo; done
>> tnode1 <<
Last password change                                    : Feb 11, 2024
Password expires                                        : May 11, 2024
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 90
Number of days of warning before password expires       : 7

>> tnode2 <<
Last password change                                    : Feb 11, 2024
Password expires                                        : May 11, 2024
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 90
Number of days of warning before password expires       : 7

>> tnode3 <<
Last password change                                    : Feb 11, 2024
Password expires                                        : May 11, 2024
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 90
Number of days of warning before password expires       : 7

 

2. 패스워드 생성 법칙 적용하기

  • 패스워드 생성 법칙 적용을 위해서는 pwquality.conf 라는 pam 설정 파일을 이용해야 하며, 리눅스 서버에 libpam-pwquality 패키지가 있어야 함.
  • 패스워드 변경 주기는 /etc/securiy/pwquality.conf 파일로 설정한다.
  • /etc/securiy/pwquality.conf 파일을 설정하기 전에 원본 파일을 백업받는다.
  • pwquality.conf 파일은 사용자가 커스텀으로 설정하는 파라미터들로 구성되어 있으며, Jinja2 템플릿 방식으로 구현한다.
  • 파라미터들은 별도의 변수 설정 파일에서 정의한 파라미터 값으로 사용하고, 파라미터 정의 여부를 체크하여 pwquality.conf 파일의 내용을 구성한다.

  • 프로젝트 디렉터리 생성 및 ansible.cfg, inventory 파일 작성
  • ~/my-ansible/chapter_11.2/..
#
mkdir ~/my-ansible/chapter_11.2
cd ~/my-ansible/chapter_11.2

# ansible.cfg, inventory 파일 작성
cat <<EOT> ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ubuntu
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT

cat <<EOT> inventory
[tnode]
tnode1
tnode2
tnode3
EOT
  • 변수 값 설정
  • ~/**my-ansible/**chapter_11.2/vars_pw_rule.yml
---

minlen: 8
dcredit: -1
ucredit: -1
lcredit: -1
ocredit: -1
enforce_for_root: false

 

✅ Jinja2 템플릿 파일 pwquality.conf.j2 작성

  • {% ~ %} 사이에 제어문 구문 위치
  • {% if **minlen** is **defined** %} 구문 : minlen 이라는 변수가 정의되면 아래 문장을 삽입하라’ 는 의미
  • 아래 템플릿에는 {% if **변수** is **defined** %} ~ {% **endif** %} 구문을 사용하여 파라미터와 관련된 변수가 선언되면 해당 파라미터를 삽입
  • ~/**my-ansible/**chapter_11.2/pwquality.conf.j2
# Created by ansible

{% if minlen is defined %}
# Minimum acceptable size for the new password
minlen = {{ minlen }}
{% endif %}

{% if dcredit is defined %}
# The maximum credit for having digits in the new password
dcredit = {{ dcredit }}
{% endif %}

{% if ucredit is defined %}
# The maximum credit for having uppercase characters in the new password
ucredit = {{ ucredit }}
{% endif %}

{% if lcredit is defined %}
# The maximum credit for having lowercase characters in the new password
lcredit = {{ lcredit }}
{% endif %}

{% if ocredit is defined %}
# The maximum credit for having other characters in the new password
ocredit = {{ ocredit }}
{% endif %}

{% if minclass is defined %}
# The minimum number of required classes of characters for the new password
minclass = {{ minclass }}
{% endif %}

{% if maxrepeat is defined %}
# The maximum number of allowed consecutive same characters in the new password
maxrepeat = {{ maxrepeat}}
{% endif %}

{% if maxclassrepeat is defined %}
# The maximum number of allowed consecutive characters of the same class in the new password
maxclassrepeat = {{ maxclassreapt }}
{% endif %}

{% if retry is defined %}
# Prompt user at most N times before returning with error
retry = {{ retry }}
{% endif %}

{% if enforce_for_root is defined %}
# Enforces pwquality checks on the root user password.
enforce_for_root
{% endif %}
  • 메인 플레이북 작성 
  • ~/my-ansible/chapter_11.2/set_password_rule.yml
---

- hosts: tnode
  vars_files: vars_pw_rule.yml

  tasks:
    - name: Install libpam-pwquality
      ansible.builtin.apt:
        name: libpam-pwquality
        state: present
      when: ansible_facts.os_family == "Debian"

    - name: Backup pwquality.conf
      ansible.builtin.copy:
        src: /etc/security/pwquality.conf
        dest: /etc/security/pwquality.conf.bak
        remote_src: yes

    - name: Copy pwquality.conf.j2 at /etc/security
      ansible.builtin.template:
        src: pwquality.conf.j2
        dest: /etc/security/pwquality.conf
        mode: '0644'
  • 플레이북 실행 및 확인
# 문법 체크
ansible-playbook --syntax-check set_password_rule.yml

# 시뮬레이션
ansible-playbook --check set_password_rule.yml

# 실행 전 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo cat /etc/security/pwquality.conf; echo; done

ubuntu@server:~/my-ansible/chapter_11.2$ for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo cat /etc/security/pwquality.conf; echo; done
>> tnode1 <<
cat: /etc/security/pwquality.conf: No such file or directory

>> tnode2 <<
cat: /etc/security/pwquality.conf: No such file or directory

>> tnode3 <<
cat: /etc/security/pwquality.conf: No such file or directory
  • 파일이 없음을 확인
  • 패스워드 변경 규칙에 맞게 노드에 접속하여 불충분 패스워드를 일부러 만들어 테스트함 
# 실행
ansible-playbook set_password_rule.yml

# 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo ls -l /etc/security; echo; done
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo cat /etc/security/pwquality.conf; echo; done

total 60
-rw-r--r-- 1 root root 4564 Mar 24  2022 access.conf
-rw-r--r-- 1 root root 1793 Jul  1  2020 capability.conf
-rw-r--r-- 1 root root 2234 Mar 24  2022 faillock.conf
-rw-r--r-- 1 root root 3635 Mar 24  2022 group.conf
-rw-r--r-- 1 root root 2161 Mar 24  2022 limits.conf
drwxr-xr-x 2 root root 4096 Mar 24  2022 limits.d
-rw-r--r-- 1 root root 1637 Mar 24  2022 namespace.conf
drwxr-xr-x 2 root root 4096 Mar 24  2022 namespace.d
-rwxr-xr-x 1 root root 1016 Mar 24  2022 namespace.init
-rw------- 1 root root    0 Feb  8 02:47 opasswd
-rw-r--r-- 1 root root 2971 Mar 24  2022 pam_env.conf
-rw-r--r-- 1 root root  487 Feb 11 17:54 pwquality.conf
-rw-r--r-- 1 root root 2674 Jan 14  2022 pwquality.conf.bak
-rw-r--r-- 1 root root  419 Mar 24  2022 sepermit.conf
-rw-r--r-- 1 root root 2179 Mar 24  2022 time.conf



# 확인 : ansible 계정에 패스워드를 조건이 불충분하도록 입력 시도
ssh tnode2
-----------------
sudo su - ansible
whoami
pwd
passwd ansible
Changing password for ansible.
Current password: ansiblePw1
New password: qwer
BAD PASSWORD: The password contains less than 1 digits
New password: qwer1234
BAD PASSWORD: The password contains less than 1 uppercase letters
New password: Qwer1234
BAD PASSWORD: The password contains less than 1 non-alphanumeric characters
passwd: Have exhausted maximum number of retries for service
passwd: password unchanged

 

3. 디렉터리 및 파일 접근 권한 변경

  • 리눅스 보안 중에 꼭 확인해야 하는 항목이 바로 Sticky bit 설정 파일World Writable 설정 파일입니다.
  • Sticky bit 설정 파일은 리눅스에서 파일 소유자그룹 소유자만 해당 파일을 읽고 쓰고 삭제할 수 있도록 권한을 부여한 것을 의미합니다.
  • 파일 소유자에게 권한을 부여하면 SUID, 파일 그룹에게 권한을 부여하면 SGID, 다른 사람에게 권한을 부여하면 Sticky bit 라고 합니다.
  • Sticky bit가 적용된 파일 목록 중 보안을 위해 이를 적용하면 안 되는 파일 목록들이 있습니다.
  • Sticky bit가 적용된 파일의 권한을 수정할 때는 적용되면 안 되는 파일인지 반드시 먼저 확인합니다 → 보안 관련 가이드 문서 참고
  • World Writable 파일모든 사용자에게 파일을 읽고 쓸 수 있는 권한이 부여된 파일을 의미합니다.

  • 프로젝트 디렉터리 생성 및 ansible.cfg, inventory 파일 작성
  • ~/my-ansible/chapter_11.3/..
#
mkdir ~/my-ansible/chapter_11.3
cd ~/my-ansible/chapter_11.3

# ansible.cfg, inventory 파일 작성
cat <<EOT> ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ubuntu
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT

cat <<EOT> inventory
[tnode]
tnode1
tnode2
tnode3
EOT
  • 태스크 파일 작성 : 검색 시 grep 으로 필터링(-e 정규식 패턴, -v 매칭되지 않는 경우)
  • ~/my-ansible/chapter_11.3/set_sticky_writable_files.yml
---

- hosts: tnode

  tasks:
  - name: Find Sticky bit files
    ansible.builtin.shell: |
      find / -xdev -perm -04000 -o -perm -02000 -o -perm 01000 \
      | grep -e 'dump$' \
             -e 'lp*-lpd$' \ 
             -e 'newgrp$' \
             -e 'restore$' \
             -e 'at$' \
             -e 'traceroute$' | xargs ls
    register: sfile_list

  - name: Find World Writable files
    ansible.builtin.shell: |
      find / -xdev -perm -2 -ls \
      | grep -v 'l..........' | awk '{print $NF}'
    register: wfile_list

  - name: Print Sticky bit files
    ansible.builtin.debug:
      msg: "{{ sfile_list.stdout_lines }}"

  - name: Print World Writable files
    ansible.builtin.debug:
      msg: "{{ wfile_list.stdout_lines }}"

  - name: Set Sticky bit files
    ansible.builtin.file:
      path: "{{ item }}"
      mode: "u-s,g-s,o-s"
    loop: "{{ sfile_list.stdout_lines }}"

  - name: Set World Writable files
    ansible.builtin.file:
      path: "{{ item }}"
      mode: "o-w"
    loop: "{{ wfile_list.stdout_lines }}"
  • 플레이북 실행
# 문법 체크
ansible-playbook --syntax-check set_sticky_writable_files.yml

# 시뮬레이션
ansible-playbook --check set_sticky_writable_files.yml

# (예시) 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo ls -al /usr/bin/newgrp; echo; done  # -rwsr-xr-x
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo ls -al /var/crash; echo; done       # drwxrwxrwt

# 실행
ansible-playbook set_sticky_writable_files.yml

# (예시) 변경 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo ls -al /usr/bin/newgrp; echo; done  # -rwxr-xr-x
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i sudo ls -al /var/crash; echo; done       # drwxrwxr-t

# 한번 더 실행
ansible-playbook set_sticky_writable_files.yml
  •  -rwsr-xr-x ---> -rwxr-xr-x
  •  drwxrwxrwt ---> # drwxrwxr-t