{"id":2494,"date":"2021-10-23T00:50:01","date_gmt":"2021-10-23T00:50:01","guid":{"rendered":"https:\/\/dft.wiki\/?p=2494"},"modified":"2026-06-08T22:23:33","modified_gmt":"2026-06-09T02:23:33","slug":"2fa-with-yubikey-on-ubuntu-20-04","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=2494","title":{"rendered":"2FA with Yubikey on Ubuntu"},"content":{"rendered":"<p>In this post, I will cover the usages of a Yubikey for:<\/p>\n<ul>\n<li>Protecting local sudo commands,<\/li>\n<li>Protecting the local GNOME login screen,<\/li>\n<li>Protecting the local TTY screen,<\/li>\n<li>Protecting remote SSH connections.<\/li>\n<\/ul>\n<hr \/>\n<p><strong>PROTECTING LOCAL SUDO<\/strong><\/p>\n<pre>sudo apt update &amp;&amp; sudo apt upgrade -y\r\nsudo apt install libpam-u2f -y\r\nmkdir -p ~\/.config\/Yubico\r\npamu2fcfg &gt; ~\/.config\/Yubico\/u2f_keys\r\nsudo nano \/etc\/pam.d\/sudo<\/pre>\n<p>Add the line in bold after the mentioned line:<\/p>\n<pre>@include common-auth\r\n<strong>auth required pam_u2f.so<\/strong><\/pre>\n<p>Done. Try using the <strong>sudo<\/strong> command with and without the Yubikey connected.<\/p>\n<p>The Yubikey will flash, asking you to touch its button to allow the command to go through.<\/p>\n<hr \/>\n<p><strong>PROTECTING LOCAL GNOME LOGIN<\/strong><\/p>\n<pre>sudo nano \/etc\/pam.d\/gdm-password<\/pre>\n<p>Add the line in bold after the mentioned line:<\/p>\n<pre>@include common-auth\r\n<strong>auth required pam_u2f.so<\/strong><\/pre>\n<hr \/>\n<p><strong>PROTECTING LOCAL TTY SCREEN<\/strong><\/p>\n<pre>sudo nano \/etc\/pam.d\/login<\/pre>\n<p>Add the line in bold after the mentioned line:<\/p>\n<pre>@include common-auth\r\n<strong>auth required pam_u2f.so<\/strong><\/pre>\n<hr \/>\n<p><strong>PROTECTING REMOTE SSH<\/strong><\/p>\n<pre>sudo apt update &amp;&amp; sudo apt upgrade -y\r\nsudo add-apt-repository ppa:yubico\/stable\r\nsudo apt install libpam-yubico -y\r\nsudo nano \/etc\/ssh\/yubikeys<\/pre>\n<p>Add one line per user, followed by the first 12 characters of their Yubikey:<\/p>\n<pre>usera:f8v6d6f687d6\r\nuserb:df98b67d7b68:adf1n98b7kf5\r\nuserc:edf8n98b7nf6<\/pre>\n<p>Create API credentials on the Yubico website: <a href=\"https:\/\/upgrade.yubico.com\/getapikey\" target=\"_blank\" rel=\"noopener\">https:\/\/upgrade.yubico.com\/getapikey<\/a>.<\/p>\n<p>Edit the PAM configuration file for the SSH server:<\/p>\n<pre>sudo nano \/etc\/pam.d\/sshd<\/pre>\n<p>Add the following line at the top of the file, replacing <span style=\"color: #ff0000;\"><strong>ID<\/strong><\/span> and <span style=\"color: #ff0000;\"><strong>KEY<\/strong><\/span> with your API credentials:<\/p>\n<pre>auth required pam_yubico.so id=<span style=\"color: #ff0000;\"><strong>ID<\/strong><\/span> key=<span style=\"color: #ff0000;\"><strong>KEY<\/strong><\/span> mode=client debug authfile=\/etc\/ssh\/yubikeys<\/pre>\n<p>The most common <strong>keyboard-interactive<\/strong> options are:<\/p>\n<ul>\n<li><strong>required<\/strong> (both OTP and password must be provided),<\/li>\n<li><strong>sufficient<\/strong> (either OTP or password is enough).<\/li>\n<\/ul>\n<p>Then edit the file:<\/p>\n<pre>sudo nano \/etc\/ssh\/sshd_config<\/pre>\n<p>Make sure both of the following settings are enabled:<\/p>\n<pre>ChallengeResponseAuthentication yes\r\nUsePAM yes\r\nAuthenticationMethods publickey,keyboard-interactive<\/pre>\n<p>This requires both authentication methods:<\/p>\n<ul>\n<li><strong>publickey<\/strong> (SSH key),<\/li>\n<li><strong>keyboard-interactive<\/strong> (handled by PAM).<\/li>\n<\/ul>\n<p>To allow a specific user to connect using only an SSH key, append:<\/p>\n<pre>Match User userd\r\n    AuthenticationMethods publickey<\/pre>\n<p>Restart the service:<\/p>\n<pre>sudo systemctl restart ssh<\/pre>\n<hr \/>\n<p><strong>PROTECTING REMOTE SUDO<\/strong><\/p>\n<pre>sudo nano \/etc\/pam.d\/sudo<\/pre>\n<p>Add the following line at the top of the file, replacing <span style=\"color: #ff0000;\"><strong>ID<\/strong><\/span> and <span style=\"color: #ff0000;\"><strong>KEY<\/strong><\/span> with your API credentials:<\/p>\n<pre>auth required pam_yubico.so id=<span style=\"color: #ff0000;\"><strong>ID<\/strong><\/span> key=<span style=\"color: #ff0000;\"><strong>KEY<\/strong><\/span> mode=client authfile=\/etc\/ssh\/yubikeys<\/pre>\n<hr \/>\n<p><strong>PROTECTING REMOTE RASPBERRY PI SSH<\/strong><\/p>\n<p>Following the same steps to protect SSH, you may notice that the repository below does not exist for RPi:<\/p>\n<pre>sudo add-apt-repository ppa:yubico\/stable<\/pre>\n<p>Do not let that stop you. The rest of the process works the same way.<\/p>\n<hr \/>\n<p><strong>USING ANSIBLE WITH 2FA<\/strong><\/p>\n<p>There are several workaround solutions that simply create bypass conditions for Ansible, such as allowing root access from a specific address or network:<\/p>\n<pre>Match Address 10.1.1.0\/24,192.168.111.222\r\n  PermitRootLogin yes<\/pre>\n<p>Or suppressing 2FA for a dedicated Ansible user:<\/p>\n<pre>Match User ansible_user\r\n  AuthenticationMethods publickey<\/pre>\n<p>However, the cleanest approach is to configure Ansible to handle the OTP prompt directly. Add the following to <strong>\/etc\/ansible\/ansible.cfg<\/strong>:<\/p>\n<pre>[ssh_connection]\r\nssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o PreferredAuthentications=publickey,keyboard-interactive<\/pre>\n<p>Note: Ansible executes tasks across multiple servers in parallel for each module. If more than one server requires an OTP, the prompts may overlap. To avoid this, serialize the execution order:<\/p>\n<pre>  serial: 1\r\n  order: <strong>inventory<\/strong><\/pre>\n<p>The order options are: inventory, reverse_inventory, sorted, reverse_sorted, or shuffle.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, I will cover the usages of a Yubikey for: Protecting local sudo [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,6],"tags":[],"class_list":["post-2494","post","type-post","status-publish","format-standard","hentry","category-linux","category-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/2494","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2494"}],"version-history":[{"count":14,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/2494\/revisions"}],"predecessor-version":[{"id":5708,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/2494\/revisions\/5708"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2494"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2494"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2494"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}