{"id":3648,"date":"2020-04-26T16:05:29","date_gmt":"2020-04-26T14:05:29","guid":{"rendered":"http:\/\/miro.borodziuk.eu\/?p=3648"},"modified":"2020-05-26T00:01:36","modified_gmt":"2020-05-25T22:01:36","slug":"ansible-review","status":"publish","type":"post","link":"http:\/\/miro.borodziuk.eu\/index.php\/2020\/04\/26\/ansible-review\/","title":{"rendered":"Ansible Review"},"content":{"rendered":"<p><!--more--><\/p>\n<p><span style=\"color: #3366ff;\">Install and Configure an Ansible Control Node<\/span><\/p>\n<p>This objective is made up of the following items:<\/p>\n<ul>\n<li>Install required packages.<\/li>\n<li>Create a static host inventory file (covered in other lessons but also shown again).<\/li>\n<li>Create a configuration file (covered in other lessons, and not shown in this lesson).<\/li>\n<li>Configure privilege escalation on managed nodes.<\/li>\n<li>Validate a working configuration using ad-hoc Ansible commands.<\/li>\n<li>We will create a user for Ansible called ansible.<\/li>\n<li>User on managed nodes will need to have passwordless sudo setup.<\/li>\n<li>User ansible will need to be able to ssh into nodes without needing password.<\/li>\n<\/ul>\n<p>Ansible on CentOS requires epel-relase. Let&#8217;s install required packages:<\/p>\n<pre class=\"lang:sh decode:true\">[root@controlnode \/]# yum install -y epel-release\r\n[root@controlnode \/]# yum -y install ansible<\/pre>\n<p>Creating ansible user:<\/p>\n<pre class=\"lang:sh decode:true \">[root@controlnode \/]# useradd ansible\r\n[root@controlnode \/]# passwd ansible<\/pre>\n<p>Allow ansible user to run any command::<\/p>\n<pre class=\"lang:sh decode:true \">[root@controlnode \/]# visudo\r\n## The COMMANDS section may have other options added to it.\r\n##\r\n## Allow root to run any commands anywhere\r\nroot    ALL=(ALL) ALL\r\nansible ALL=(ALL) NOPASSWD: ALL<\/pre>\n<p>Editing hosts file:<\/p>\n<pre class=\"lang:sh decode:true \">[root@controlnode \/]# cat \/etc\/hosts\r\n127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4\r\n::1 localhost localhost.localdomain localhost6 localhost6.localdomain6\r\n172.30.9.55 controlnode controlnode.example.com\r\n172.30.9.56 managedhost1 managedhost1.example.com\r\n172.30.9.57 managedhost2 managedhost2..example.com<\/pre>\n<p>Editing inventory file:<\/p>\n<pre class=\"lang:sh decode:true \">[root@controlnode \/]# vi \/etc\/ansible\/hosts\r\n\r\n# Here's another example of host ranges, this time there are no\r\n# leading 0s:\r\n\r\n## db-[99:101]-node.example.com\r\nlocalhost\r\nmanagedhost1\r\nmanagedhost2<\/pre>\n<p>Checking connectivity to <em>managedhost1<\/em>:<\/p>\n<pre class=\"lang:sh decode:true \">[root@controlnode \/]# ping managedhost1\r\nPING managedhost1 (172.30.9.56) 56(84) bytes of data.\r\n64 bytes from managedhost1 (172.30.9.56): icmp_seq=1 ttl=64 time=0.458 ms\r\n64 bytes from managedhost1 (172.30.9.56): icmp_seq=2 ttl=64 time=0.340 ms\r\n64 bytes from managedhost1 (172.30.9.56): icmp_seq=3 ttl=64 time=0.344 ms\r\n^C\r\n--- managedhost1 ping statistics ---\r\n3 packets transmitted, 3 received, 0% packet loss, time 1999ms\r\nrtt min\/avg\/max\/mdev = 0.340\/0.380\/0.458\/0.059 ms<\/pre>\n<p>Connecting to <em>managedhost1<\/em>:<\/p>\n<pre class=\"lang:sh decode:true\">[root@controlnode \/]# ssh managedhost1\r\nThe authenticity of host 'managedhost1 (172.30.9.56)' can't be established.\r\nECDSA key fingerprint is d8:44:a4:a2:a8:b4:c9:54:22:19:69:42:2c:07:a6:35.\r\nAre you sure you want to continue connecting (yes\/no)? yes\r\nWarning: Permanently added 'managedhost1,172.30.9.56' (ECDSA) to the list of known hosts.\r\nroot@managedhost1's password:\r\nLast login: Mon May 25 15:36:10 2020\r\n[root@managedhost1 ~]#<\/pre>\n<p>Creating ansible user on <em>managedhost1<\/em>:<\/p>\n<pre class=\"lang:sh decode:true\">[root@managedhost1 ~]# useradd ansible\r\n[root@managedhost1 ~]# passwd ansible\r\nZmienianie has\u0142a u\u017cytkownika ansible.\r\nNowe has\u0142o :\r\nB\u0141\u0118DNE HAS\u0141O: Has\u0142o jest kr\u00f3tsze ni\u017c 8 znak\u00f3w\r\nProsz\u0119 ponownie poda\u0107 nowe has\u0142o :\r\npasswd: zaktualizowanie wszystkich token\u00f3w uwierzytelniania si\u0119 powiod\u0142o.<\/pre>\n<p>Allowing ansible user to run any command on <em>managedhost1<\/em>:<\/p>\n<pre class=\"lang:sh decode:true \">[root@managedhost1 ~]# visudo\r\n\r\n\r\n## The COMMANDS section may have other options added to it.\r\n##\r\n## Allow root to run any commands anywhere\r\nroot ALL=(ALL) ALL\r\nansible ALL=(ALL) NOPASSWD: ALL<\/pre>\n<p>Logging out from <em>managedhost1<\/em>:<\/p>\n<pre class=\"lang:sh decode:true \">[root@managedhost1 ~]# exit\r\nlogout\r\nConnection to managedhost1 closed.<\/pre>\n<p>Doing the same on managedhost2:<\/p>\n<pre class=\"lang:sh decode:true\">[root@controlnode \/]# ping managedhost2\r\nPING managedhost2 (172.30.9.57) 56(84) bytes of data.\r\n64 bytes from managedhost2 (172.30.9.57): icmp_seq=1 ttl=64 time=0.456 ms\r\n64 bytes from managedhost2 (172.30.9.57): icmp_seq=2 ttl=64 time=0.388 ms\r\n64 bytes from managedhost2 (172.30.9.57): icmp_seq=3 ttl=64 time=0.198 ms\r\n^C\r\n--- managedhost2 ping statistics ---\r\n3 packets transmitted, 3 received, 0% packet loss, time 2000ms\r\nrtt min\/avg\/max\/mdev = 0.198\/0.347\/0.456\/0.110 ms\r\n\r\n[root@controlnode \/]# ssh managedhost2\r\nThe authenticity of host 'managedhost2 (172.30.9.57)' can't be established.\r\nECDSA key fingerprint is d8:44:a4:a2:a8:b4:c9:54:22:19:69:42:2c:07:a6:35.\r\nAre you sure you want to continue connecting (yes\/no)? yes\r\nWarning: Permanently added 'managedhost2,172.30.9.57' (ECDSA) to the list of known hosts.\r\nroot@managedhost2's password:\r\nLast login: Mon May 25 15:45:49 2020\r\n\r\n[root@managedhost2 ~]# useradd ansible\r\n[root@managedhost2 ~]# passwd ansible\r\nZmienianie has\u0142a u\u017cytkownika ansible.\r\nNowe has\u0142o :\r\nB\u0141\u0118DNE HAS\u0141O: Has\u0142o jest kr\u00f3tsze ni\u017c 8 znak\u00f3w\r\nProsz\u0119 ponownie poda\u0107 nowe has\u0142o :\r\npasswd: zaktualizowanie wszystkich token\u00f3w uwierzytelniania si\u0119 powiod\u0142o.\r\n\r\n[root@managedhost2 ~]# visudo\r\nThe COMMANDS section may have other options added to it.\r\n##\r\n## Allow root to run any commands anywhere\r\nroot ALL=(ALL) ALL\r\nansible ALL=(ALL) NOPASSWD: ALL\r\n\r\n[root@managedhost2 ~]# exit\r\nlogout\r\nConnection to managedhost2 closed.<\/pre>\n<p>On controlnode switch to ansible user:<\/p>\n<pre class=\"lang:sh decode:true \">[root@controlnode \/]# su - ansible\r\n[ansible@controlnode ~]$<\/pre>\n<p>Create SSH key:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode ~]$ ssh-keygen\r\nGenerating public\/private rsa key pair.\r\nEnter file in which to save the key (\/home\/ansible\/.ssh\/id_rsa):\r\nCreated directory '\/home\/ansible\/.ssh'.\r\nEnter passphrase (empty for no passphrase):\r\nEnter same passphrase again:\r\nYour identification has been saved in \/home\/ansible\/.ssh\/id_rsa.\r\nYour public key has been saved in \/home\/ansible\/.ssh\/id_rsa.pub.\r\nThe key fingerprint is:\r\ne4:5b:eb:13:ca:b6:3a:be:a7:c9:48:b5:79:63:44:9b ansible@controlnode.example.com\r\nThe key's randomart image is:\r\n+--[ RSA 2048]----+\r\n| |\r\n| |\r\n| o |\r\n| + o |\r\n| . E . |\r\n| . + o.. |\r\n| . o.=... |\r\n| . o.+=o. |\r\n| ..BB.... |\r\n+-----------------+<\/pre>\n<p>Copy key to localhost and <em>managedhosts<\/em>:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode ~]$ ssh-copy-id localhost\r\nThe authenticity of host 'localhost (::1)' can't be established.\r\nECDSA key fingerprint is d8:44:a4:a2:a8:b4:c9:54:22:19:69:42:2c:07:a6:35.\r\nAre you sure you want to continue connecting (yes\/no)? yes\r\n\/bin\/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed\r\n\/bin\/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys\r\nansible@localhost's password:\r\nNumber of key(s) added: 1\r\nNow try logging into the machine, with: \"ssh 'localhost'\"\r\nand check to make sure that only the key(s) you wanted were added.\r\n\r\n[ansible@controlnode ~]$ ssh-copy-id managedhost1\r\nThe authenticity of host 'managedhost1 (172.30.9.56)' can't be established.\r\nECDSA key fingerprint is d8:44:a4:a2:a8:b4:c9:54:22:19:69:42:2c:07:a6:35.\r\nAre you sure you want to continue connecting (yes\/no)? yes\r\n\/bin\/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed\r\n\/bin\/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys\r\nansible@managedhost1's password:\r\nPermission denied, please try again.\r\nansible@managedhost1's password:\r\nNumber of key(s) added: 1\r\nNow try logging into the machine, with: \"ssh 'managedhost1'\"\r\nand check to make sure that only the key(s) you wanted were added.\r\n\r\n[ansible@controlnode ~]$ ssh-copy-id managedhost2\r\nThe authenticity of host 'managedhost2 (172.30.9.57)' can't be established.\r\nECDSA key fingerprint is d8:44:a4:a2:a8:b4:c9:54:22:19:69:42:2c:07:a6:35.\r\nAre you sure you want to continue connecting (yes\/no)? yes\r\n\/bin\/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed\r\n\/bin\/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys\r\nansible@managedhost2's password:\r\nNumber of key(s) added: 1\r\nNow try logging into the machine, with: \"ssh 'managedhost2'\"\r\nand check to make sure that only the key(s) you wanted were added.<\/pre>\n<p>Checking SSH conectivity:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode ~]$ ssh localhost\r\nLast login: Mon May 25 17:09:44 2020\r\n[ansible@controlnode ~]$ logout\r\nConnection to localhost closed.\r\n\r\n[ansible@controlnode ~]$ ssh managedhost1\r\nLast login: Mon May 25 17:09:56 2020 from controlnode\r\n[ansible@managedhost1 ~]$ logout\r\nConnection to managedhost1 closed.\r\n\r\n[ansible@controlnode ~]$ ssh managedhost2\r\n[ansible@managedhost2 ~]$ logout\r\nConnection to managedhost2 closed.\r\n<\/pre>\n<p>Let&#8217;s try if we have ansible connection with managedhosts:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode ~]$ ansible all -m ping\r\nlocalhost | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}\r\nmanagedhost1 | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}\r\nmanagedhost2 | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}\r\n\r\n\r\n<\/pre>\n<p>The same with <code>-b<\/code> option (becoming a <em>root<\/em> user):<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode ~]$ ansible all -b -m ping\r\nmanagedhost1 | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}\r\nmanagedhost2 | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}\r\nlocalhost | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Simple Shell Scripts for Running Ad-Hoc Commands<\/span><\/p>\n<p>Why shell scripts?<\/p>\n<ul>\n<li>Shell scripts can be used to hide complexity.<\/li>\n<li>You can use shell scripts easily with Ansible ad-hoc commands.<\/li>\n<li>People not experienced in Ansible can leverage them.<\/li>\n<li>People not skilled in Ansible can create and use them.<\/li>\n<li>There is no need to know YAML and .yml formatting.<\/li>\n<\/ul>\n<p>It is a simple script which installs package given as argument on managedhosts:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode ~]$ pwd\r\n\/home\/ansible\r\n[ansible@controlnode ~]$ mkdir scripts\r\n[ansible@controlnode ~]$ cd scripts\r\n[ansible@controlnode scripts]$ vi install-package.sh\r\n[ansible@controlnode scripts]$ chmod +x install-package.sh\r\n[ansible@controlnode scripts]$ cat install-package.sh\r\n\r\n#!\/bin\/bash\r\n# Note cmd line variable is $1\r\nif [ -n \"$1\" ]; then\r\n  echo \"Package to install is $1\"\r\n  ansible all -b -m yum -a \"name=$1 state=present\"\r\nelse\r\n  echo \"Package to install was not supplied exit\"\r\nfi\r\n\r\n<\/pre>\n<p>Let&#8217;s install <em>elinks<\/em> on managedhosts using the script:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ .\/install-package.sh elinks\r\nPackage to install is elinks\r\n\r\nmanagedhost2 | CHANGED =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": true,\r\n\"changes\": {\r\n\"installed\": [\r\n\"elinks\"\r\n]\r\n},\r\n\"msg\": \"\",\r\n\"rc\": 0,\r\n\"results\": [\r\n\"Loaded plugins: fastestmirror\\nLoading mirror speeds from cached hostfile\\n * base: centos.slaskdatacenter.com\\n * extras: centos.slaskdatacenter.com\\n * updates: centos.slaskdatacenter.com\\nResolving Dependencies\\n--&gt; Running transaction check\\n---&gt; Package elinks.x86_64 0:0.12-0.37.pre6.el7.0.1 will be installed\\n--&gt; Processing Dependency: libnss_compat_ossl.so.0()(64bit) for package: elinks-0.12-0.37.pre6.el7.0.1.x86_64\\n--&gt; Processing Dependency: libmozjs185.so.1.0()(64bit) for package: elinks-0.12-0.37.pre6.el7.0.1.x86_64\\n--&gt; Running transaction check\\n---&gt; Package js.x86_64 1:1.8.5-20.el7 will be installed\\n---&gt; Package nss_compat_ossl.x86_64 0:0.9.6-8.el7 will be installed\\n--&gt; Finished Dependency Resolution\\n\\nDependencies Resolved\\n\\n================================================================================\\n Package Arch Version Repository\\n Size\\n================================================================================\\nInstalling:\\n elinks x86_64 0.12-0.37.pre6.el7.0.1 base 882 k\\nInstalling for dependencies:\\n js x86_64 1:1.8.5-20.el7 base 2.3 M\\n nss_compat_ossl x86_64 0.9.6-8.el7 base 37 k\\n\\nTransaction Summary\\n================================================================================\\nInstall 1 Package (+2 Dependent packages)\\n\\nTotal download size: 3.2 M\\nInstalled size: 9.6 M\\nDownloading packages:\\n--------------------------------------------------------------------------------\\nTotal 12 MB\/s | 3.2 MB 00:00 \\nRunning transaction check\\nRunning transaction test\\nTransaction test succeeded\\nRunning transaction\\n Installing : nss_compat_ossl-0.9.6-8.el7.x86_64 1\/3 \\n Installing : 1:js-1.8.5-20.el7.x86_64 2\/3 \\n Installing : elinks-0.12-0.37.pre6.el7.0.1.x86_64 3\/3 \\n Verifying : elinks-0.12-0.37.pre6.el7.0.1.x86_64 1\/3 \\n Verifying : 1:js-1.8.5-20.el7.x86_64 2\/3 \\n Verifying : nss_compat_ossl-0.9.6-8.el7.x86_64 3\/3 \\n\\nInstalled:\\n elinks.x86_64 0:0.12-0.37.pre6.el7.0.1 \\n\\nDependency Installed:\\n js.x86_64 1:1.8.5-20.el7 nss_compat_ossl.x86_64 0:0.9.6-8.el7 \\n\\nComplete!\\n\"\r\n]\r\n}\r\n\r\nmanagedhost1 | CHANGED =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": true,\r\n\"changes\": {\r\n\"installed\": [\r\n\"elinks\"\r\n]\r\n},\r\n\"msg\": \"\",\r\n\"rc\": 0,\r\n\"results\": [\r\n\"Loaded plugins: fastestmirror\\nLoading mirror speeds from cached hostfile\\n * base: centos2.hti.pl\\n * extras: centos1.hti.pl\\n * updates: centos1.hti.pl\\nResolving Dependencies\\n--&gt; Running transaction check\\n---&gt; Package elinks.x86_64 0:0.12-0.37.pre6.el7.0.1 will be installed\\n--&gt; Processing Dependency: libnss_compat_ossl.so.0()(64bit) for package: elinks-0.12-0.37.pre6.el7.0.1.x86_64\\n--&gt; Processing Dependency: libmozjs185.so.1.0()(64bit) for package: elinks-0.12-0.37.pre6.el7.0.1.x86_64\\n--&gt; Running transaction check\\n---&gt; Package js.x86_64 1:1.8.5-20.el7 will be installed\\n---&gt; Package nss_compat_ossl.x86_64 0:0.9.6-8.el7 will be installed\\n--&gt; Finished Dependency Resolution\\n\\nDependencies Resolved\\n\\n================================================================================\\n Package Arch Version Repository\\n Size\\n================================================================================\\nInstalling:\\n elinks x86_64 0.12-0.37.pre6.el7.0.1 base 882 k\\nInstalling for dependencies:\\n js x86_64 1:1.8.5-20.el7 base 2.3 M\\n nss_compat_ossl x86_64 0.9.6-8.el7 base 37 k\\n\\nTransaction Summary\\n================================================================================\\nInstall 1 Package (+2 Dependent packages)\\n\\nTotal download size: 3.2 M\\nInstalled size: 9.6 M\\nDownloading packages:\\n--------------------------------------------------------------------------------\\nTotal 538 kB\/s | 3.2 MB 00:06 \\nRunning transaction check\\nRunning transaction test\\nTransaction test succeeded\\nRunning transaction\\n Installing : nss_compat_ossl-0.9.6-8.el7.x86_64 1\/3 \\n Installing : 1:js-1.8.5-20.el7.x86_64 2\/3 \\n Installing : elinks-0.12-0.37.pre6.el7.0.1.x86_64 3\/3 \\n Verifying : elinks-0.12-0.37.pre6.el7.0.1.x86_64 1\/3 \\n Verifying : 1:js-1.8.5-20.el7.x86_64 2\/3 \\n Verifying : nss_compat_ossl-0.9.6-8.el7.x86_64 3\/3 \\n\\nInstalled:\\n elinks.x86_64 0:0.12-0.37.pre6.el7.0.1 \\n\\nDependency Installed:\\n js.x86_64 1:1.8.5-20.el7 nss_compat_ossl.x86_64 0:0.9.6-8.el7 \\n\\nComplete!\\n\"\r\n]\r\n}\r\n\r\nlocalhost | CHANGED =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": true,\r\n\"changes\": {\r\n\"installed\": [\r\n\"elinks\"\r\n]\r\n},\r\n\"msg\": \"\",\r\n\"rc\": 0,\r\n\"results\": [\r\n\"Loaded plugins: fastestmirror\\nLoading mirror speeds from cached hostfile\\n * base: centos.slaskdatacenter.com\\n * epel: mirror.vpsnet.com\\n * extras: centos.slaskdatacenter.com\\n * updates: centos.slaskdatacenter.com\\nResolving Dependencies\\n--&gt; Running transaction check\\n---&gt; Package elinks.x86_64 0:0.12-0.37.pre6.el7.0.1 will be installed\\n--&gt; Processing Dependency: libnss_compat_ossl.so.0()(64bit) for package: elinks-0.12-0.37.pre6.el7.0.1.x86_64\\n--&gt; Processing Dependency: libmozjs185.so.1.0()(64bit) for package: elinks-0.12-0.37.pre6.el7.0.1.x86_64\\n--&gt; Running transaction check\\n---&gt; Package js.x86_64 1:1.8.5-20.el7 will be installed\\n---&gt; Package nss_compat_ossl.x86_64 0:0.9.6-8.el7 will be installed\\n--&gt; Finished Dependency Resolution\\n\\nDependencies Resolved\\n\\n================================================================================\\n Package Arch Version Repository\\n Size\\n================================================================================\\nInstalling:\\n elinks x86_64 0.12-0.37.pre6.el7.0.1 base 882 k\\nInstalling for dependencies:\\n js x86_64 1:1.8.5-20.el7 base 2.3 M\\n nss_compat_ossl x86_64 0.9.6-8.el7 base 37 k\\n\\nTransaction Summary\\n================================================================================\\nInstall 1 Package (+2 Dependent packages)\\n\\nTotal download size: 3.2 M\\nInstalled size: 9.6 M\\nDownloading packages:\\n--------------------------------------------------------------------------------\\nTotal 568 kB\/s | 3.2 MB 00:05 \\nRunning transaction check\\nRunning transaction test\\nTransaction test succeeded\\nRunning transaction\\n Installing : nss_compat_ossl-0.9.6-8.el7.x86_64 1\/3 \\n Installing : 1:js-1.8.5-20.el7.x86_64 2\/3 \\n Installing : elinks-0.12-0.37.pre6.el7.0.1.x86_64 3\/3 \\n Verifying : elinks-0.12-0.37.pre6.el7.0.1.x86_64 1\/3 \\n Verifying : 1:js-1.8.5-20.el7.x86_64 2\/3 \\n Verifying : nss_compat_ossl-0.9.6-8.el7.x86_64 3\/3 \\n\\nInstalled:\\n elinks.x86_64 0:0.12-0.37.pre6.el7.0.1 \\n\\nDependency Installed:\\n js.x86_64 1:1.8.5-20.el7 nss_compat_ossl.x86_64 0:0.9.6-8.el7 \\n\\nComplete!\\n\"\r\n]\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Ansible and Firewall Rules<\/span><br \/>\n\u2022 There are Ansible modules that can be used with firewalls.<br \/>\n\u2022 The firewalld module like others, can be used to add or remove rules.<br \/>\n\u2022 Here is the URL for more information: https:\/\/docs.ansible.com\/ansible\/latest\/modules\/firewalld_module.html<br \/>\n\u2022 There is a module for iptables<br \/>\nhttps:\/\/docs.ansible.com\/ansible\/latest\/modules\/iptables_module.html<\/p>\n<p>The simple playbook which install and enable firewalld service::<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat install-firewalld.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: install firewalld\r\n    action: yum name=firewalld state=installed\r\n  - name: Enable firewalld on system reboot\r\n    service: name=firewalld enabled=yes\r\n  - name: Start service firewalld, if not started\r\n    service:\r\n      name: firewalld\r\n      state: started<\/pre>\n<p>Check the syntax of playbook:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ ansible-playbook --syntax-check install-firewalld.yml\r\n\r\nplaybook: install-firewalld.yml<\/pre>\n<p>Run the playbook:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ ansible-playbook install-firewalld.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [install firewalld] **********************************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nTASK [Enable firewalld on system reboot] ******************************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [Start service firewalld, if not started] ************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\n\r\n<\/pre>\n<p>Playbook\u00a0 setup-server.yml install elinks and apache. Make http service enabled.<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat setup-server.yml\r\n---\r\n- hosts: all\r\nuser: ansible\r\nbecome: yes\r\ngather_facts: no\r\ntasks:\r\n- name: install elinks\r\naction: yum name=elinks state=installed\r\n- name: install httpd\r\naction: yum name=httpd state=installed\r\n- name: Enable &amp; start Apache on system reboot\r\nservice:\r\nname=httpd\r\nenabled=yes\r\nstate=started\r\n\r\n[ansible@controlnode scripts]$ ansible-playbook --syntax-check setup-server.yml\r\n\r\nplaybook: setup-server.yml<\/pre>\n<p>Run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook setup-server.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [install elinks] *************************************************************************************************************************\r\nok: [managedhost1]\r\nok: [localhost]\r\nok: [managedhost2]\r\n\r\nTASK [install httpd] **************************************************************************************************************************\r\nchanged: [managedhost2]\r\nchanged: [managedhost1]\r\nchanged: [localhost]\r\n\r\nTASK [Enable &amp; start Apache on system reboot] *************************************************************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\n<\/pre>\n<p>At this moment we can&#8217;t connect to http service:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ curl managedhost1\r\ncurl: (7) Failed connect to managedhost1:80; Brak trasy do hosta<\/pre>\n<p>So let&#8217;s create a firewall-rule.yml playbook:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat firewall-rule.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - firewalld:\r\n      service: http\r\n      permanent: yes\r\n      state: enabled\r\n  - name: Restart service firewalld\r\n    service:\r\n      name: firewalld\r\n      state: restarted<\/pre>\n<p>Check a syntax and run:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check firewall-rule.yml\r\n\r\nplaybook: firewall-rule.yml\r\n[ansible@controlnode scripts]$ ansible-playbook firewall-rule.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [firewalld] ******************************************************************************************************************************\r\nchanged: [managedhost2]\r\nchanged: [managedhost1]\r\nchanged: [localhost]\r\n\r\nTASK [Restart service firewalld] **************************************************************************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Now we can connect to apache service from controlnode to managedhost1:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ curl managedhost1\r\n&lt;!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD XHTML 1.1\/\/EN\" \"http:\/\/www.w3.org\/TR\/xhtml11\/DTD\/xhtml11.dtd\"&gt;&lt;html&gt;&lt;head&gt;\r\n&lt;meta http-equiv=\"content-type\" content=\"text\/html; charset=UTF-8\"&gt;\r\n&lt;title&gt;Apache HTTP Server Test Page powered by CentOS&lt;\/title&gt;\r\n&lt;meta http-equiv=\"Content-Type\" content=\"text\/html; charset=UTF-8\"&gt;<\/pre>\n<p>Also we can connect from <em>managedhost1<\/em> to controlnode:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ssh managedhost1\r\nLast login: Mon May 25 18:20:07 2020 from controlnode\r\n[ansible@managedhost1 ~]$ curl controlnode\r\n&lt;!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD XHTML 1.1\/\/EN\" \"http:\/\/www.w3.org\/TR\/xhtml11\/DTD\/xhtml11.dtd\"&gt;&lt;html&gt;&lt;head&gt;\r\n&lt;meta http-equiv=\"content-type\" content=\"text\/html; charset=UTF-8\"&gt;\r\n&lt;title&gt;Apache HTTP Server Test Page powered by CentOS&lt;\/title&gt;\r\n&lt;meta http-equiv=\"Content-Type\" content=\"text\/html; charset=UTF-8\"&gt;<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Archiving <\/span><\/p>\n<p><em>The Archive Module<\/em><\/p>\n<ul>\n<li>There are Ansible modules that can be used for archive purposes.<\/li>\n<li>It is mentioned as part of the exam&#8217;s EX407 objectives.<\/li>\n<li>Archive Module Documentation page: https:\/\/docs.ansible.com\/ansible\/latest\/modules\/archive_module.html<\/li>\n<li>Assumes the compression source exists on the target.<\/li>\n<li>Does not copy source files from the local system to the target before archiving.<\/li>\n<li>You can delete source files after archiving by using the remove=true option.<\/li>\n<\/ul>\n<p><em>The Unarchive Module<\/em><\/p>\n<ul>\n<li>The opposite module is unarchive<\/li>\n<li>Unarchive Module Documentation page: https:\/\/docs.ansible.com\/ansible\/latest\/modules\/unarchive_module.html<\/li>\n<li>If checksum is required, then use get_url or uri instead.<\/li>\n<li>By default it will copy from the source file to the target before unpacking.<\/li>\n<\/ul>\n<p>Example of playbook with archive module:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat backup-logs.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Compress directory \/var\/log\/ into \/home\/ansible\/logs.zip\r\n    archive:\r\n      path: \/var\/log\r\n      dest: \/home\/ansible\/logs.tar.gz\r\n      owner: ansible\r\n      group: ansible\r\n      format: gz<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check backup-logs.yml\r\n\r\nplaybook: backup-logs.yml\r\n[ansible@controlnode scripts]$ ansible-playbook backup-logs.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [Compress directory \/var\/log\/ into \/home\/ansible\/logs.zip] *******************************************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Now we can check if the playbook\u00a0 create archives on managed hosts:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ll \/home\/ansible\/logs.tar.gz\r\n-rw-r--r--. 1 ansible ansible 440837 05-25 18:37 \/home\/ansible\/logs.tar.gz\r\n\r\n[ansible@controlnode scripts]$ ansible all -a \"\/bin\/ls -l \/home\/ansible\/logs.tar.gz\"\r\nmanagedhost2 | CHANGED | rc=0 &gt;&gt;\r\n-rw-r--r--. 1 ansible ansible 401244 05-25 18:37 \/home\/ansible\/logs.tar.gz\r\nmanagedhost1 | CHANGED | rc=0 &gt;&gt;\r\n-rw-r--r--. 1 ansible ansible 421266 05-25 18:37 \/home\/ansible\/logs.tar.gz\r\nlocalhost | CHANGED | rc=0 &gt;&gt;\r\n-rw-r--r--. 1 ansible ansible 440837 05-25 18:37 \/home\/ansible\/logs.tar.gz<\/pre>\n<p>Now let&#8217;s use the fetch module:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat backup-logs2.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Compress directory \/var\/log\/ into \/home\/ansible\/logs.zip\r\n    archive:\r\n      path: \/var\/log\r\n      dest: \/home\/ansible\/logs.tar.gz\r\n      owner: ansible\r\n      group: ansible\r\n      format: gz\r\n\r\n  - name: Fetch the log files to the local filesystem\r\n    fetch:\r\n      src: \/home\/ansible\/logs.tar.gz\r\n      dest: logbackup-{{ inventory_hostname }}.tar.gz\r\n      flat: yes<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check backup-logs2.yml\r\n\r\nplaybook: backup-logs2.yml\r\n\r\n[ansible@controlnode scripts]$ ansible-playbook backup-logs2.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [Compress directory \/var\/log\/ into \/home\/ansible\/logs.zip] *******************************************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nTASK [Fetch the log files to the local filesystem] ********************************************************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Check if the playbook really fetch the backups annd store them locally:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ll logbackup*\r\n-rw-rw-r--. 1 ansible ansible 444665 05-25 18:56 logbackup-localhost.tar.gz\r\n-rw-rw-r--. 1 ansible ansible 424901 05-25 18:56 logbackup-managedhost1.tar.gz\r\n-rw-rw-r--. 1 ansible ansible 404496 05-25 18:56 logbackup-managedhost2.tar.gz\r\n\r\n[ansible@controlnode scripts]$ tar -xvf logbackup-managedhost1.tar.gz\r\nlog\/ppp\/\r\nlog\/tuned\/\r\nlog\/audit\/\r\nlog\/chrony\/\r\nlog\/anaconda\/\r\nlog\/rhsm\/\r\nlog\/httpd\/\r\nlog\/lastlog\r\nlog\/wtmp\r\nlog\/btmp\r\nlog\/messages\r\nlog\/secure\r\nlog\/maillog\r\nlog\/spooler\r\nlog\/vmware-vmsvc.log\r\nlog\/firewalld\r\nlog\/cron\r\nlog\/dmesg.old\r\nlog\/yum.log\r\nlog\/boot.log\r\nlog\/dmesg\r\nlog\/tuned\/tuned.log\r\nlog\/audit\/audit.log\r\nlog\/anaconda\/anaconda.log\r\nlog\/anaconda\/syslog\r\nlog\/anaconda\/X.log\r\nlog\/anaconda\/program.log\r\nlog\/anaconda\/packaging.log\r\nlog\/anaconda\/storage.log\r\nlog\/anaconda\/ifcfg.log\r\nlog\/anaconda\/ks-script-dEorE0.log\r\nlog\/anaconda\/journal.log\r\nlog\/httpd\/error_log\r\nlog\/httpd\/access_log\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Cron<\/span><\/p>\n<p>The Cron Module<\/p>\n<ul>\n<li>The cron module is used to manage crontab on your nodes,<\/li>\n<li>You can create environment variables as well as named crontab entries.<\/li>\n<li>You can add, update, and delete entries.<\/li>\n<li>You should add a name with th crontab entry so it can be removed easily with a playbook.\n<ul>\n<li>e.g. name: &#8220;Job 0001&#8221;<\/li>\n<\/ul>\n<\/li>\n<li>When managing environment variables, no comment line gets added; however, the module uses the name parameter to find the correct definition line.<\/li>\n<\/ul>\n<p>Extra Parameters<\/p>\n<ul>\n<li>You remove the crontab entry by using <strong><code>state: absent<\/code><\/strong> in a playbook.<\/li>\n<li>The <strong><code>name:<\/code><\/strong> gets matched for a removal.<\/li>\n<li>You can use the boolean <strong><code>disabled<\/code> <\/strong>to comment out an entry (only works if <strong><code>state=present<\/code><\/strong>.<\/li>\n<li>Jobs can be set to run at reboot if required. Use th <strong><code>special_time: reboot<\/code><\/strong> if that is required.<\/li>\n<li>You can add a specific user if you need to set crontab entry for a user (need to become root).<\/li>\n<li>Use <strong><code>insertafter<\/code> <\/strong>or <code><strong>insertbefore<\/strong> <\/code>to add env entry before or after another env entry.<\/li>\n<\/ul>\n<p>The example playbook :<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat cron.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Ensure a job that runs at 5am and 5pm exists. Creates an entry like \"0 5,17 * * * df -h \u00bb \/tmp\/diskspace\"\r\n    cron:\r\n      name: \"Job 0001\"\r\n      minute: \"0\"\r\n      hour: \"5,17\"\r\n      job: \"df -h \u00bb \/tmp\/diskspace\"<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check cron.yml\r\n\r\nplaybook: cron.yml\r\n[ansible@controlnode scripts]$ ansible-playbook cron.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [Ensure a job that runs at 5am and 5pm exists. Creates an entry like \"0 5,17 * * * df -h \u00bb \/tmp\/diskspace\"] ******************************\r\nchanged: [localhost]\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\n<\/pre>\n<p>Check if playbook works. We need list crontab as root so use sudo :<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ sudo crontab -l\r\n#Ansible: Job 0001\r\n0 5,17 * * * df -h \u00bb \/tmp\/diskspace<\/pre>\n<p>Now we modify playbook:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat cron.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Ensure a job that runs at 5am and 5pm exists. Creates an entry like \"0 5,17 * * * df -h \u00bb \/tmp\/diskspace\"\r\n    cron:\r\n      name: \"Job 0001\"\r\n      minute: \"0\"\r\n      hour: \"5,17\"\r\n      job: \"df -h \u00bb \/tmp\/diskspace\"\r\n\r\n  - name: Creates an entry like \"PATH=\/opt\/bin\" on top of crontab\r\n    cron:\r\n      name: PATH\r\n      env: yes\r\n      job: \/opt\/bin\r\n\r\n  - name: Creates an entry like \"APP_HOME=\/srv\/app\" and insert it after PATH declaration\r\n    cron:\r\n      name: APP_HOME\r\n      env: yes\r\n      job: \/srv\/app\r\n      insertbefore: PATH<\/pre>\n<p>Chec and run:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook cron2.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [Ensure a job that runs at 5am and 5pm exists. Creates an entry like \"0 5,17 * * * df -h \u00bb \/tmp\/diskspace\"] ******************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [Creates an entry like \"PATH=\/opt\/bin\" on top of crontab] ********************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [Creates an entry like \"APP_HOME=\/srv\/app\" and insert it after PATH declaration] *********************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>And check what new playbook has done:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ sudo crontab -l\r\nAPP_HOME=\"\/srv\/app\"\r\nPATH=\"\/opt\/bin\"\r\n#Ansible: Job 0001\r\n0 5,17 * * * df -h \u00bb \/tmp\/diskspace<\/pre>\n<p>It added environment variables APP_HOME and PATH. APP_HOME has been added to crrontab before PATH as we wanted.<\/p>\n<p>If we want to delete our entries from cron we should use <strong><code>state: absent<\/code><\/strong> keyword:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat cron2.yml\r\n---\r\n- hosts: all\r\nuser: ansible\r\nbecome: yes\r\ngather_facts: no\r\ntasks:\r\n- name: Ensure a job that runs at 5am and 5pm exists. Creates an entry like \"0 5,17 * * * df -h \u00bb \/tmp\/diskspace\"\r\ncron:\r\nname: \"Job 0001\"\r\nminute: \"0\"\r\nhour: \"5,17\"\r\njob: \"df -h \u00bb \/tmp\/diskspace\"\r\nstate: absent\r\n\r\n- name: Creates an entry like \"PATH=\/opt\/bin\" on top of crontab\r\ncron:\r\nname: PATH\r\nenv: yes\r\njob: \/opt\/bin\r\nstate: absent\r\n\r\n- name: Creates an entry like \"APP_HOME=\/srv\/app\" and insert it after PATH declaration\r\ncron:\r\nname: APP_HOME\r\nenv: yes\r\njob: \/srv\/app\r\ninsertbefore: PATH\r\nstate: absent<\/pre>\n<p>Run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook cron2.yml\r\n\r\nPLAY [all] ************************************************************************************************************************************\r\n\r\nTASK [Ensure a job that runs at 5am and 5pm exists. Creates an entry like \"0 5,17 * * * df -h \u00bb \/tmp\/diskspace\"] ******************************\r\nchanged: [localhost]\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\n\r\nTASK [Creates an entry like \"PATH=\/opt\/bin\" on top of crontab] ********************************************************************************\r\nchanged: [managedhost2]\r\nchanged: [managedhost1]\r\nchanged: [localhost]\r\n\r\nTASK [Creates an entry like \"APP_HOME=\/srv\/app\" and insert it after PATH declaration] *********************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ************************************************************************************************************************************\r\nlocalhost : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Check the crontab:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ sudo crontab -l<\/pre>\n<p>It is empty.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">at module<\/span><\/p>\n<p><em>at software (in Linux)<\/em><\/p>\n<ul>\n<li>The at command is used to schedule jobs that will be running once in the future.<\/li>\n<li>It is used for single ad-hoc jobs and is not meant as a replacement for cron<\/li>\n<li>It is part of a group of commands that get installed with the at software.<\/li>\n<li>The other commands are:\n<ul>\n<li>at Executes commands at a specific time.<\/li>\n<li>atq Lists the users pending jobs.<\/li>\n<li>atrm Deletes a job by its job number.<\/li>\n<li>batch Executes the command depending on specific system load levels. A value must be specified.<\/li>\n<\/ul>\n<\/li>\n<li>Only at and atrm are controlled via the at module.<\/li>\n<\/ul>\n<p><em>Service Details<\/em><\/p>\n<ul>\n<li>It may not be on all systems, so verify its installation.<\/li>\n<li>On Red Hat or CentOS systems it is installed with a <code>yum install at<\/code> command.<\/li>\n<li>The service is controlled via the <em>atd<\/em> daemon.<\/li>\n<\/ul>\n<p>Example of at playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat at.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: install the at command for job scheduling\r\n    action: yum name=at state=installed\r\n  - name: Enable and Start service at if not started\r\n    service:\r\n      name: atd\r\n      enabled: yes\r\n      state: started<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ ansible-playbook --syntax-check at.yml\r\n\r\nplaybook: at.yml\r\n[ansible@controlnode scripts]$ ansible-playbook at.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************************\r\n\r\nTASK [install the at command for job scheduling] **************************************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [Enable and Start service at if not started] *************************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************************\r\nlocalhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Checck if at has been installed:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible all -m yum -a \"name=at state=present\"\r\nmanagedhost2 | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"msg\": \"\",\r\n\"rc\": 0,\r\n\"results\": [\r\n\"at-3.1.13-24.el7.x86_64 providing at is already installed\"\r\n]\r\n}\r\nmanagedhost1 | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"msg\": \"\",\r\n\"rc\": 0,\r\n\"results\": [\r\n\"at-3.1.13-24.el7.x86_64 providing at is already installed\"\r\n]\r\n}\r\nlocalhost | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n\"discovered_interpreter_python\": \"\/usr\/bin\/python\"\r\n},\r\n\"changed\": false,\r\n\"msg\": \"\",\r\n\"rc\": 0,\r\n\"results\": [\r\n\"at-3.1.13-24.el7.x86_64 providing at is already installed\"\r\n]\r\n}<\/pre>\n<p>Let&#8217;s modify playbook to add at job:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat at2.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: install the at command for job scheduling\r\n    action: yum name=at state=installed\r\n\r\n  - name: Enable and Start service at if not started\r\n    service:\r\n      name: atd\r\n      enabled: yes\r\n      state: started\r\n\r\n  - name: Schedule a command to execute in 20 minutes as the ansible user\r\n    at:\r\n      command: df -h &gt; \/tmp\/diskspace\r\n      count: 20\r\n      units: minutes<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check at2.yml\r\n\r\nplaybook: at2.yml\r\n[ansible@controlnode scripts]$ ansible-playbook at2.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************************\r\n\r\nTASK [install the at command for job scheduling] **************************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nTASK [Enable and Start service at if not started] *************************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nTASK [Schedule a command to execute in 20 minutes as the ansible user] ****************************************************************************************\r\nchanged: [managedhost2]\r\nchanged: [managedhost1]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************************\r\nlocalhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Check if the playbook works:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ sudo atq\r\n1 Mon May 25 20:25:00 2020 a root\r\n\r\n[ansible@controlnode scripts]$ sudo at -c 1\r\n#!\/bin\/sh\r\n# atrun uid=0 gid=0\r\n# mail ansible 0\r\numask 22\r\n.....OUTPUT OMITTED.......\r\ncd \/home\/ansible || {\r\necho 'Execution directory inaccessible' &gt;&amp;2\r\nexit 1\r\n}\r\n${SHELL:-\/bin\/sh} &lt;&lt; 'marcinDELIMITER0d442f6d'\r\ndf -h &gt; \/tmp\/diskspace<\/pre>\n<p>We can disable at job from the playbook by <strong><code>state:absent<\/code><\/strong> statement:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat at2.yml\r\n---\r\n- hosts: all\r\nuser: ansible\r\nbecome: yes\r\ngather_facts: no\r\ntasks:\r\n- name: install the at command for job scheduling\r\naction: yum name=at state=installed\r\n- name: Enable and Start service at if not started\r\nservice:\r\nname: atd\r\nenabled: yes\r\nstate: started\r\n\r\n- name: Schedule a command to execute in 20 minutes as the ansible user\r\nat:\r\ncommand: df -h &gt; \/tmp\/diskspace\r\ncount: 20\r\nunits: minutes\r\nstate: absent<\/pre>\n<p>Check if the job has been removed:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ ansible-playbook --syntax-check at2.yml\r\n\r\nplaybook: at2.yml\r\n[ansible@controlnode scripts]$ ansible-playbook at2.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************************\r\n\r\nTASK [install the at command for job scheduling] **************************************************************************************************************\r\nok: [managedhost1]\r\nok: [localhost]\r\nok: [managedhost2]\r\n\r\nTASK [Enable and Start service at if not started] *************************************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [Schedule a command to execute in 20 minutes as the ansible user] ****************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************************\r\nlocalhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\n\r\n[ansible@controlnode scripts]$ sudo atq\r\n[ansible@controlnode scripts]$ at -c 1\r\nCannot find jobid 1<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Security<\/span><br \/>\nAnsible Security Tasks<\/p>\n<ul>\n<li>Ansible is very useful as a security tool.<\/li>\n<li>You can make security changes to many nodes at once.<\/li>\n<li>You can apply changes to help with easily securing nodes.<\/li>\n<li>You can check lots of nodes for vulnerabilities quickly.<\/li>\n<li>It can work well with other tools that you may have in place.<\/li>\n<li>Check for Ansible modules that can be used for security tasks.<\/li>\n<li>Not just for Linux can be used for OS X, Solaris, Windows, and others.<\/li>\n<li>Can be used for devices such as NetApp or EMC storage, F5, and others.<\/li>\n<\/ul>\n<p>There are many modules that can be useful for security:<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li><strong><code>selinux<\/code> <\/strong>Configures the SELinux mode and policy.<\/li>\n<li><strong><code>firewalld<\/code> <\/strong>iptables Both manage firewall policies.<\/li>\n<li><strong><code>pamd<\/code> <\/strong>\u2014 Manages PAM modules.<\/li>\n<\/ul>\n<\/li>\n<li>Capable of working with <em>Datadog<\/em>, <em>Nagios<\/em>, and other monitoring tools.<\/li>\n<li>Manage users and groups (bulk add and delete users if you don&#8217;t have SSO ability).<\/li>\n<li>Can manage certificates such as <em>OpenSSL<\/em> or <em>SSH<\/em>.<\/li>\n<li>A colossal amount of other abilities exist. Search the All Modules page: https:\/\/docs.ansible.com\/ansible\/latest\/modules\/list_of_all_modules.html<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Example of playbook with selinux module:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat selinux.yml\r\n---\r\n- hosts: all\r\nuser: ansible\r\nbecome: yes\r\ngather_facts: no\r\ntasks:\r\n- name: Enable SELinux\r\nselinux:\r\npolicy: targeted\r\nstate: enforcing<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check selinux.yml\r\n\r\nplaybook: selinux.yml\r\n[ansible@controlnode scripts]$ ansible-playbook selinux.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************************\r\n\r\nTASK [Enable SELinux] *****************************************************************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************************\r\nlocalhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Nothing has been changed because selinux policy has already been set to enforcing.<\/p>\n<p>Example of playbook with firewall module:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat firewall.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: install firewalld\r\n    action: yum name=firewalld state=installed\r\n  - name: Enable firewalld on system reboot\r\n    service:\r\n      name=firewalld\r\n      enabled=yes\r\n  - name: Start service firewalld, if not started\r\n    service:\r\n      name: firewalld\r\n      state: started\r\n\r\n  - firewalld:\r\n      service: dhcpv6-client\r\n      permanent: yes\r\n      immediate: yes\r\n      state: disabled\r\n\r\n  - firewalld:\r\n      service: http\r\n      permanent: yes\r\n      immediate: yes\r\n      state: enabled\r\n\r\n  - firewalld:\r\n      service: ssh\r\n      permanent: yes\r\n      immediate: yes\r\n      state: enabled<\/pre>\n<p>Before we run the playbook let&#8217;s check which services are allowed on firewall at this moment:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible all -b -a \"\/bin\/firewall-cmd --list-services\"\r\nmanagedhost2 | CHANGED | rc=0 &gt;&gt;\r\ndhcpv6-client http ssh\r\nmanagedhost1 | CHANGED | rc=0 &gt;&gt;\r\ndhcpv6-client http ssh\r\nlocalhost | CHANGED | rc=0 &gt;&gt;\r\ndhcpv6-client http ssh\r\n<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check firewall.yml\r\n\r\nplaybook: firewall.yml\r\n[ansible@controlnode scripts]$ ansible-playbook firewall.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************************\r\n\r\nTASK [install firewalld] **************************************************************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [Enable firewalld on system reboot] **********************************************************************************************************************\r\nok: [managedhost1]\r\nok: [localhost]\r\nok: [managedhost2]\r\n\r\nTASK [Start service firewalld, if not started] ****************************************************************************************************************\r\nok: [managedhost2]\r\nok: [managedhost1]\r\nok: [localhost]\r\n\r\nTASK [firewalld] **********************************************************************************************************************************************\r\nchanged: [managedhost2]\r\nchanged: [managedhost1]\r\nchanged: [localhost]\r\n\r\nTASK [firewalld] **********************************************************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nTASK [firewalld] **********************************************************************************************************************************************\r\nok: [managedhost1]\r\nok: [managedhost2]\r\nok: [localhost]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************************\r\nlocalhost : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Let&#8217;s which services are allowed on firewall now:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible all -b -a \"\/bin\/firewall-cmd --list-services\"\r\nmanagedhost1 | CHANGED | rc=0 &gt;&gt;\r\nhttp ssh\r\nlocalhost | CHANGED | rc=0 &gt;&gt;\r\nhttp ssh\r\nmanagedhost2 | CHANGED | rc=0 &gt;&gt;\r\nhttp ssh\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Let&#8217;s add a group developers to the managed hosts:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat add-group.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Ensure group \"developers\" exists\r\n    group:\r\n      name: developers\r\n      state: present<\/pre>\n<p>Check and run the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check add-group.yml\r\n\r\nplaybook: add-group.yml\r\n[ansible@controlnode scripts]$ ansible-playbook add-group.yml\r\n\r\nPLAY [all] *********************************************************************************************************************************************\r\n\r\nTASK [Ensure group \"developers\" exists] ****************************************************************************************************************\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP *********************************************************************************************************************************************\r\nlocalhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\n<\/pre>\n<p>To create a user we need a password hash:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ sudo adduser tempuser\r\n\r\n[ansible@controlnode scripts]$ passwd adduser tempuser\r\npasswd: tylko root mo\u017ce poda\u0107 nazw\u0119 konta.\r\n\r\n[ansible@controlnode scripts]$ sudo passwd tempuser\r\nZmienianie has\u0142a u\u017cytkownika tempuser.\r\nNowe has\u0142o :\r\nB\u0141\u0118DNE HAS\u0141O: Has\u0142o jest kr\u00f3tsze ni\u017c 8 znak\u00f3w\r\nProsz\u0119 ponownie poda\u0107 nowe has\u0142o :\r\npasswd: zaktualizowanie wszystkich token\u00f3w uwierzytelniania si\u0119 powiod\u0142o.\r\n\r\n[ansible@controlnode scripts]$ sudo grep tempuser \/etc\/shadow\r\ntempuser:$6$INs\/ECOm$uWFAk\/HoNxHdPq.YPlPYLkvoUQb1Uou42dGwYgE7Yi7kglhh92pzLuH\/8P4D.Pl\/H3R8QDkh0VmfKla\/4Q0640:18407:0:99999:7:::<\/pre>\n<p>Besides the password hash we need expires date which we can get from www.epochconverter.com site.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-3688 aligncenter\" src=\"http:\/\/miro.borodziuk.eu\/wp-content\/uploads\/67.jpg\" alt=\"\" width=\"724\" height=\"220\" srcset=\"http:\/\/miro.borodziuk.eu\/wp-content\/uploads\/67.jpg 724w, http:\/\/miro.borodziuk.eu\/wp-content\/uploads\/67-300x91.jpg 300w\" sizes=\"(max-width: 724px) 100vw, 724px\" \/><\/p>\n<p>The playbook looks like:<\/p>\n<pre class=\"lang:sh decode:true\">[ansible@controlnode scripts]$ cat add-user.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Add a consultant whose account you want to expire\r\n    user:\r\n      name: james20\r\n      shell: \/bin\/bash\r\n      groups: developers\r\n      append: yes\r\n      expires: REPLACE-WITH-EPOCH\r\n      password: REPLACE-WITH-HASH<\/pre>\n<p>The playbook with expires and password:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat add-user.yml\r\n---\r\n- hosts: all\r\nuser: ansible\r\nbecome: yes\r\ngather_facts: no\r\ntasks:\r\n- name: Add a consultant whose account you want to expire\r\nuser:\r\nname: james20\r\nshell: \/bin\/bash\r\ngroups: developers\r\nappend: yes\r\nexpires: 1608932135\r\npassword: $6$INs\/ECOm$uWFAk\/HoNxHdPq.YPlPYLkvoUQb1Uou42dGwYgE7Yi7kglhh92pzLuH\/8P4D.Pl\/H3R8QDkh0VmfKla\/<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook --syntax-check add-user.yml\r\n\r\nplaybook: add-user.yml\r\n[ansible@controlnode scripts]$ ansible-playbook add-user.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************\r\n\r\nTASK [Add a consultant whose account you want to expire] ******************************************************************************************\r\n[WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.\r\nchanged: [localhost]\r\nchanged: [managedhost1]\r\nchanged: [managedhost2]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************\r\nlocalhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0<\/pre>\n<p>Let&#8217;s check if user james20 is added to system:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ sudo grep james20 \/etc\/passwd\r\njames20:x:1003:1004::\/home\/james20:\/bin\/bash<\/pre>\n<p>To remove user from system we need to use <strong><code>state:absent<\/code><\/strong> statement:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ cat add-user.yml\r\n---\r\n- hosts: all\r\n  user: ansible\r\n  become: yes\r\n  gather_facts: no\r\n  tasks:\r\n  - name: Add a consultant whose account you want to expire\r\n    user:\r\n      name: james20\r\n      shell: \/bin\/bash\r\n      groups: developers\r\n      append: yes\r\n      expires: 1608932135\r\n      password: $6$INs\/ECOm$uWFAk\/HoNxHdPq.YPlPYLkvoUQb1Uou42dGwYgE7Yi7kglhh92pzLuH\/8P4D.Pl\/H3R8QDkh0VmfKla\/\r\n      state: absent<\/pre>\n<p>Run the playbook and check if the user is still prenent in the system:<\/p>\n<pre class=\"lang:sh decode:true \">[ansible@controlnode scripts]$ ansible-playbook add-user.yml\r\n\r\nPLAY [all] ****************************************************************************************************************************************\r\n\r\nTASK [Add a consultant whose account you want to expire] ******************************************************************************************\r\n[WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.\r\nchanged: [managedhost2]\r\nchanged: [managedhost1]\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************\r\nlocalhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\nmanagedhost2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\r\n\r\n[ansible@controlnode scripts]$ sudo grep james20 \/etc\/passwd<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":3650,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86],"tags":[],"_links":{"self":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3648"}],"collection":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/comments?post=3648"}],"version-history":[{"count":41,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3648\/revisions"}],"predecessor-version":[{"id":3691,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3648\/revisions\/3691"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media\/3650"}],"wp:attachment":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media?parent=3648"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/categories?post=3648"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/tags?post=3648"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}