This shows you the differences between two versions of the page.
scgc:laboratoare:07 [2020/04/06 14:45] darius.mihai [3. [20p] Resource Dependency] |
scgc:laboratoare:07 [2021/10/27 14:09] (current) maria.mihailescu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laboratory 07. Configuration Management: Puppet, Ansible ====== | + | ====== Configuration Management: Puppet, Ansible ====== |
===== Lab Setup ===== | ===== Lab Setup ===== | ||
- | * We will be using a virtual machine in the [[http://cloud.curs.pub.ro/|faculty's cloud]]. | + | * We will be using a virtual machine in the [[http://cloud.grid.pub.ro/|faculty's cloud]]. |
- | * When creating a virtual machine follow the steps in this [[https://cloud.curs.pub.ro/about/tutorial-for-students/|tutorial]]. | + | |
* When creating a virtual machine in the Launch Instance window: | * When creating a virtual machine in the Launch Instance window: | ||
- | * For **Availability zone**, choose **CAMPUS**, **CI** or **hp** | ||
* Select **Boot from image** in **Instance Boot Source** section | * Select **Boot from image** in **Instance Boot Source** section | ||
* Select **SCGC Template** in **Image Name** section | * Select **SCGC Template** in **Image Name** section | ||
Line 31: | Line 29: | ||
===== Tasks ====== | ===== Tasks ====== | ||
- | ==== 1. [20p] Puppet Resources ==== | + | ==== 1. [10p] Puppet Resources ==== |
Puppet is a configuration management tool. In order to describe the necessary configurations, Puppet uses its own declarative language. Puppet can manage both Linux and Windows systems. | Puppet is a configuration management tool. In order to describe the necessary configurations, Puppet uses its own declarative language. Puppet can manage both Linux and Windows systems. | ||
Line 150: | Line 148: | ||
Chech the **/etc/passwd** file to see if the user was actually removed. | Chech the **/etc/passwd** file to see if the user was actually removed. | ||
- | ==== 2. [20p] Puppet Manifests ==== | + | ==== 2. [10p] Puppet Manifests ==== |
Even though we can create, modify or remove resources from the command line, using **puppet resource** commands, this is not a scalable approach and not appropriate for complex scenarios. | Even though we can create, modify or remove resources from the command line, using **puppet resource** commands, this is not a scalable approach and not appropriate for complex scenarios. | ||
Line 238: | Line 236: | ||
<note important>If it doesn't already exist, the key pair for the **student** user must be generated beforehand. | <note important>If it doesn't already exist, the key pair for the **student** user must be generated beforehand. | ||
- | Then, run the command ''ssh-add ~/.ssh.id_rsa''</note> | + | Then, run the command ''ssh-add ~/.ssh/id_rsa''</note> |
<note>Use the Puppet documentation for the resource type [[https://puppet.com/docs/puppet/5.5/types/ssh_authorized_key.html|ssh_authorized_key]].</note> | <note>Use the Puppet documentation for the resource type [[https://puppet.com/docs/puppet/5.5/types/ssh_authorized_key.html|ssh_authorized_key]].</note> | ||
Line 297: | Line 295: | ||
ensure => file, | ensure => file, | ||
mode => '0600', | mode => '0600', | ||
- | source => '/root/examples/sshd_config', | + | source => '/root/config-files/sshd_config', |
} | } | ||
service { 'sshd': | service { 'sshd': | ||
Line 439: | Line 437: | ||
} | } | ||
</code> | </code> | ||
+ | |||
+ | <note important> | ||
+ | Puppet has a modular implementation, and some functionality is provided through classes, some of which may be provided by certain modules. To use the ''%%str2bool%%'' function, you must install the ''%%puppet-module-puppetlabs-stdlib%%'' module using the ''%%apt%%'' package manager. | ||
+ | </note> | ||
+ | |||
Apply the manifest and notice the state of the NTP service. | Apply the manifest and notice the state of the NTP service. | ||
Line 461: | Line 464: | ||
</note> | </note> | ||
- | ==== 6. [BONUS - 20p] Ansible Install & Configuration ==== | + | ==== 6. [10p] Ansible Install & Configuration ==== |
- | Ansible is a configuration management and provisioning tool, similar to Puppet. It uses SSH to connect to servers and run the configured Tasks. | + | Ansible is a configuration management and provisioning tool, similar to Puppet. It uses SSH to connect to servers and run the configured tasks. |
- | On the **SAISP VM** we are going to install and configure Ansible. | + | As opposed to Puppet, where each host manages its own data (services, users, files, etc.), and can optionally connect to a remote host to retrieve manifest files for configuration, Ansible is used to push the configuration from a central system to other hosts. An advantage of Ansible is that it does not require a specific service daemon to be installed before being able to configure the hosts. Operation is achieved through Python scripts for remote Linux hosts, or Powershell scripts for remote Windows hosts. |
- | <code> | + | On the **SCGC VM** we are going to install and configure Ansible. |
- | student@saisp:~$ sudo apt-add-repository -y ppa:ansible/ansible | + | |
- | student@saisp:~$ sudo apt-get update | + | <code bash> |
- | student@saisp:~$ sudo apt-get install -y ansible | + | student@scgc:~$ sudo apt update |
+ | student@scgc:~$ sudo apt install -y ansible | ||
+ | # Required to use password authentication. By default, ansible requires authentication through SSH keys | ||
+ | student@scgc:~$ sudo apt install -y sshpass | ||
</code> | </code> | ||
Check that the package was successfully installed by running the command: | Check that the package was successfully installed by running the command: | ||
<code> | <code> | ||
- | student@saisp:~/saisp$ ansible --version | + | student@scgc:~/scgc$ ansible --version |
- | ansible 2.5.0 | + | ansible 2.5.1 |
config file = /etc/ansible/ansible.cfg | config file = /etc/ansible/ansible.cfg | ||
configured module search path = [u'/home/student/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules'] | configured module search path = [u'/home/student/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules'] | ||
ansible python module location = /usr/lib/python2.7/dist-packages/ansible | ansible python module location = /usr/lib/python2.7/dist-packages/ansible | ||
executable location = /usr/bin/ansible | executable location = /usr/bin/ansible | ||
- | python version = 2.7.6 (default, Mar 22 2014, 22:59:56) [GCC 4.8.2] | + | python version = 2.7.17 (default, Nov 7 2019, 10:07:09) [GCC 7.4.0] |
</code> | </code> | ||
Line 486: | Line 492: | ||
Ansible has a default inventory file used to define which servers it will be managing. After installation, there's an example one you can reference at **/etc/ansible/hosts** (initially fully commented out). | Ansible has a default inventory file used to define which servers it will be managing. After installation, there's an example one you can reference at **/etc/ansible/hosts** (initially fully commented out). | ||
- | We are going to add 2 **labels** to the hosts file. One containing the local address, named **thishost** and another one of the previously used VM, named **remote** | + | We are going to add 2 **labels** to the hosts file. One containing the local address, named **thishost** and another one of the previously used VM, named **remote**. |
<code> | <code> | ||
- | student@saisp:~$ cat /etc/ansible/hosts | grep -A 5 thishost | + | student@scgc:~$ cat /etc/ansible/hosts | grep -A 5 thishost |
-- | -- | ||
-- | -- | ||
Line 501: | Line 507: | ||
</code> | </code> | ||
- | === Running Commands === | + | === Testing hosts availability === |
Let's start running Tasks against a server. | Let's start running Tasks against a server. | ||
Running against localhost: | Running against localhost: | ||
<code> | <code> | ||
- | student@saisp:~$ ansible --connection=local thishost -m ping | + | student@scgc:~$ ansible thishost --connection=local -m ping |
127.0.0.1 | SUCCESS => { | 127.0.0.1 | SUCCESS => { | ||
"changed": false, | "changed": false, | ||
Line 515: | Line 521: | ||
Running against the remote VM: | Running against the remote VM: | ||
<code> | <code> | ||
- | student@saisp:~$ ansible --ask-pass --user=root remote -m ping | + | student@scgc:~$ ansible --ask-pass --user=student remote -m ping |
SSH password: | SSH password: | ||
10.0.0.2 | SUCCESS => { | 10.0.0.2 | SUCCESS => { | ||
Line 526: | Line 532: | ||
Let's cover these commands: | Let's cover these commands: | ||
- | * ''remote'', ''local'' - Use the servers defined under this label in the hosts inventory file | + | * ''thishost'', ''remote'' - Use the servers defined under this label in the hosts inventory file. The ''%%all%%'' argument can be used to run the ruleset on all defined hosts. |
- | * ''-m ping'' - Use the "ping" module, which simply runs the ping command and returns the results | + | |
* ''%%--connection=local%%'' - Run commands on the local server, not over SSH | * ''%%--connection=local%%'' - Run commands on the local server, not over SSH | ||
- | * ''%%--ask-pass --user=root%%'' - SSH connection parameters: interactive password input, login as **root** user | + | * ''-m ping'' - Use the "ping" module, which checks if the host can be accessed. Using ''%%ping%%'' with ''%%--connection=local%%'' does not make sense, as the option is used when running commands on the host that is issuing commands. It normally attempts to connect to the host via SSH. |
+ | * ''%%--ask-pass --user=student%%'' - SSH connection parameters: interactive password input, login as **student** user | ||
+ | ==== 7. [10p] Ansible Facter ==== | ||
+ | Ansible has a fact gathering system similar to Puppet. To extract facts about the remote host we can use the ''%%setup%%'' module. The information is returned as Python dictionaries, where values can be strings, arrays, or other dictionaries. | ||
+ | <code> | ||
+ | student@scgc:~$ ansible --ask-pass --user=student remote -m setup | ||
+ | SSH password: | ||
+ | 10.0.0.2 | SUCCESS => { | ||
+ | "ansible_facts": { | ||
+ | "ansible_all_ipv4_addresses": [ | ||
+ | "10.0.0.2" | ||
+ | ], | ||
+ | "ansible_all_ipv6_addresses": [ | ||
+ | "fe80::5054:ff:fe12:3451" | ||
+ | ], | ||
+ | "ansible_apparmor": { | ||
+ | "status": "enabled" | ||
+ | }, | ||
+ | "ansible_architecture": "x86_64", | ||
+ | "ansible_bios_date": "04/01/2014", | ||
+ | "ansible_bios_version": "1.10.2-1ubuntu1", | ||
+ | ... | ||
+ | </code> | ||
+ | |||
+ | The information in the facter, can be used in playbooks - configuration files written in YAML that act as scripts for ansible. The syntax used to expand all variables - including those created by the facter - is ''%%"{{ variable }}"%%''. For example, ''%%"{{ ansible_facts.hostname }}"%%'' will be expanded to the hostname, as identified by the facter. | ||
+ | |||
+ | |||
+ | ==== 8. [BONUS - 20p] Two-factor Authentication for SSH ==== | ||
+ | |||
+ | We plan to enable the use of two-factor authentication for SSH through the use of Google's Authenticator mobile application. To do this, we need to create a Google Authenticator configuration file on the host. To create one with sensible defaults, you can use the following commands: | ||
+ | <code bash> | ||
+ | student@scgc:~$ sudo apt install libpam-google-authenticator qrencode | ||
+ | student@scgc:~$ echo -e "y\ny\ny\nn\ny" | google-authenticator | ||
+ | </code> | ||
+ | |||
+ | The commands above will create a configuration file for the authenticator, that will generate time-based codes, will update the ''%%~/.google-authenticator%%'' file, disallow multiple users and enable rate limiting. The fourth option (the **n** in the string passed to the ''%%google-authenticator%%'' binary ) disables longer-lasting codes (this option is only useful when the phone and/or the server's time sync protocols are not working properly). For more details, consult DigitalOcean's tutorial on how to set it up [[https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-ubuntu-16-04|here]]. | ||
+ | |||
+ | After running the command, the terminal will display the secret key as both a large QR code, and text. Please open the Google Authenticator app on your phone, and scan the QR code or enter it manually. | ||
+ | |||
+ | We will copy the Google Authenticator's configuration file, the configuration for the SSH daemon, and the PAM configuration file for the SSH service: | ||
+ | <code bash> | ||
+ | student@scgc:~$ mkdir config-files | ||
+ | student@scgc:~$ cd config-files | ||
+ | student@scgc:~/config-files$ cp /home/student/.google_authenticator . | ||
+ | student@scgc:~/config-files$ cp /etc/pam.d/sshd . | ||
+ | student@scgc:~/config-files$ cp /etc/ssh/sshd_config . | ||
+ | </code> | ||
+ | |||
+ | <note warning> | ||
+ | The Authenticator configuration file is sensitive information! It MUST have ''%%0600%%'' permissions (only the user must be able to access it), and it is usually not a good idea to copy it to another server. From a security point of view, it is similar to copying a private SSH key to another server. Make sure you copy the configuration only to servers you trust. | ||
+ | </note> | ||
+ | |||
+ | We will use the files created above as templates to replicate on the server(s). This example will only use the ''%%10.0.0.2%%'' VM as a target machine. We must set up the configuration files to use password + the a One Time Password (OTP) generated by the authenticator. Make sure the configuration files for sshd and PAM look as below: | ||
+ | <code bash> | ||
+ | student@scgc:~/config-files$ grep -B 5 -A 3 'pam_google_authenticator.so' sshd | ||
+ | # PAM configuration for the Secure Shell service | ||
+ | |||
+ | # Standard Un*x authentication. | ||
+ | @include common-auth | ||
+ | # 2-FA authentication with Google Authenticator | ||
+ | auth required pam_google_authenticator.so | ||
+ | |||
+ | # Disallow non-root logins when /etc/nologin exists. | ||
+ | account required pam_nologin.so | ||
+ | </code> | ||
+ | The changes to the PAM configuration file above make using the Google Authenticator module mandatory. It is placed after 'common-auth', so the code will be required **after** entering the password. | ||
+ | |||
+ | <code bash> | ||
+ | student@scgc:~/config-files$ grep -B 5 -A 3 '^ChallengeResponseAuthentication' sshd_config | ||
+ | #PasswordAuthentication yes | ||
+ | #PermitEmptyPasswords no | ||
+ | |||
+ | # Change to yes to enable challenge-response passwords (beware issues with | ||
+ | # some PAM modules and threads) | ||
+ | ChallengeResponseAuthentication yes | ||
+ | AuthenticationMethods publickey keyboard-interactive | ||
+ | |||
+ | # Kerberos options | ||
+ | </code> | ||
+ | The changes to the configuration file above make using the challenge response to allow PAM to use multiple modules with challenge responses (i.e., password and authentication code in our case); the use of keyboard-interactive authentication is mandatory if more than just the password is required. | ||
+ | |||
+ | Ansible can use privilege escalation using the **become** keyword at certain tasks, or all tasks. If the user cannot run sudo with a password, the ''%%ansible_become_password%%'' variable must be set. To do this we will use a vault - a type of file that encrypts strings through a password - to store the password, instead of adding it as plain text to the playbook. To create a vault, use the following command, and write the key-value pair for the password in the file it opens using the default editor: | ||
+ | <code bash> | ||
+ | student@scgc:~/config-files$ ansible-vault create puppet.vault | ||
+ | New Vault password: # Enter vault password | ||
+ | Confirm New Vault password: # Confirm vault password | ||
+ | |||
+ | # In the opened file | ||
+ | ansible_become_password: student | ||
+ | </code> | ||
+ | |||
+ | After closing the vault, you can see that the information in it is encrypted. | ||
+ | |||
+ | To install the SSH daemon on the remote machine, and set it up for use with the Google Authenticator we will use the following playbook, saved as ''%%sshd.yaml%%'': | ||
+ | <code yaml> | ||
+ | --- | ||
+ | - hosts: remote | ||
+ | remote_user: student | ||
+ | |||
+ | tasks: | ||
+ | # include sudo password vault | ||
+ | - name: Set host variables | ||
+ | include_vars: "{{ ansible_facts.hostname }}.vault" | ||
+ | |||
+ | # Install sshd and make sure it is at the latest version using the package | ||
+ | # manager identified by ansible | ||
+ | - name: Ensure sshd is at the latest version | ||
+ | package: | ||
+ | name: openssh-server | ||
+ | state: latest | ||
+ | become: yes | ||
+ | |||
+ | # Install google authenticator module and make sure it is at the latest version | ||
+ | - name: Ensure google-authenticator is at the latest version | ||
+ | package: | ||
+ | name: libpam-google-authenticator | ||
+ | state: latest | ||
+ | become: yes | ||
+ | |||
+ | # Copy the google authenticator configuration file | ||
+ | # The file MUST be located in the user's home directory with permissions 0600 | ||
+ | - name: Copy Google Authenticator config file | ||
+ | copy: | ||
+ | src: /home/student/config-files/.google_authenticator | ||
+ | dest: /home/student/.google_authenticator | ||
+ | mode: 0600 | ||
+ | owner: student | ||
+ | group: student | ||
+ | |||
+ | # Overwrite sshd configuration file. Make sure the challenge response setting | ||
+ | # is enabled, and keyboard-interactive is a valid authentication method | ||
+ | - name: Write the sshd configuration file | ||
+ | template: | ||
+ | src: /home/student/config-files/sshd_config | ||
+ | dest: /etc/ssh/sshd_config | ||
+ | become: yes | ||
+ | notify: | ||
+ | - restart sshd | ||
+ | |||
+ | # Overwrite the PAM configuration file. Make sure that authentication through | ||
+ | # google authenticator is required | ||
+ | - name: Write the PAM configuration file | ||
+ | template: | ||
+ | src: /home/student/config-files/sshd | ||
+ | dest: /etc/pam.d/sshd | ||
+ | become: yes | ||
+ | notify: | ||
+ | - restart sshd | ||
+ | |||
+ | handlers: | ||
+ | # Handlers that are invoked when the configuration files change - | ||
+ | # restart the sshd service | ||
+ | - name: restart sshd | ||
+ | service: | ||
+ | name: sshd | ||
+ | state: restarted | ||
+ | become: yes | ||
+ | </code> | ||
+ | |||
+ | The playbook attempts to include the file named ''%%{{ ansible_facts.hostname }}.vault%%'' - which resolves to ''%%puppet.vault%%'' for the VM. To run it, we use the ''%%ansible-playbook%%'' command, with the ''%%--ask-pass%%'', to ask for the SSH authentication password, and the ''%%--ask-vault-pass%%'' to provide the decryption password for the vault. | ||
+ | <code bash> | ||
+ | student@scgc:~/config-files$ ansible-playbook --ask-vault-pass --ask-pass sshd.yml | ||
+ | SSH password: | ||
+ | Vault password: | ||
+ | |||
+ | PLAY [remote] ********************************************************************* | ||
+ | |||
+ | TASK [Gathering Facts] ************************************************************ | ||
+ | ok: [10.0.0.2] | ||
+ | |||
+ | TASK [Set host variables] ********************************************************* | ||
+ | ok: [10.0.0.2] | ||
+ | |||
+ | TASK [Ensure sshd is at the latest version] *************************************** | ||
+ | ok: [10.0.0.2] | ||
+ | |||
+ | TASK [Ensure google-authenticator is at the latest version] *********************** | ||
+ | changed: [10.0.0.2] | ||
+ | |||
+ | TASK [Copy Google Authenticator config file] ************************************** | ||
+ | changed: [10.0.0.2] | ||
+ | |||
+ | TASK [Write the sshd configuration file] ****************************************** | ||
+ | changed: [10.0.0.2] | ||
+ | |||
+ | TASK [Write the PAM configuration file] ******************************************* | ||
+ | changed: [10.0.0.2] | ||
+ | |||
+ | RUNNING HANDLER [restart sshd] **************************************************** | ||
+ | changed: [10.0.0.2] | ||
+ | | ||
+ | PLAY RECAP ************************************************************************ | ||
+ | 10.0.0.2 : ok=8 changed=5 unreachable=0 failed=0 | ||
+ | </code> | ||
+ | |||
+ | You should now be able to login using the password and the Google Authenticator. | ||
+ | <code bash> | ||
+ | student@scgc:~$ ssh student@10.0.0.2 | ||
+ | Password: | ||
+ | Verification code: | ||
+ | Password: | ||
+ | Verification code: | ||
+ | Linux puppet 4.19.0-8-amd64 #1 SMP Debian 4.19.98-1 (2020-01-26) x86_64 | ||
+ | |||
+ | The programs included with the Debian GNU/Linux system are free software; | ||
+ | the exact distribution terms for each program are described in the | ||
+ | individual files in /usr/share/doc/*/copyright. | ||
+ | |||
+ | Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent | ||
+ | permitted by applicable law. | ||
+ | student@puppet:~$ | ||
+ | </code> | ||
+ | |||
+ | <note important> | ||
+ | After adding two-factor authentication, Ansible will no longer be able to access the VM using password authentication, since the password is read by ansible before actually attempting to access the server, and ''%%sshpass%%'' is not aware it is required. | ||
+ | </note> |