{"id":293,"date":"2024-05-24T20:35:02","date_gmt":"2024-05-24T20:35:02","guid":{"rendered":"https:\/\/blog.lottabytes.com\/?p=293"},"modified":"2024-05-24T20:35:02","modified_gmt":"2024-05-24T20:35:02","slug":"mfa-tutorial-yubikey-usage-for-both-console-and-ssh","status":"publish","type":"post","link":"https:\/\/blog.lottabytes.com\/index.php\/2024\/05\/24\/mfa-tutorial-yubikey-usage-for-both-console-and-ssh\/","title":{"rendered":"MFA Tutorial &#8211; Yubikey Usage for both Console and SSH"},"content":{"rendered":"\n<p>For a while now I&#8217;ve been curious about the application of multi-factor authentication for Linux console and SSH access. It&#8217;s a particularly challenging use-case, especially if you operate in an air-gapped configuration, and yet need to meet industry standards such as PCI&#8217;s Control 8.5.1, or NIST 800-53&#8217;s Control IA-2(1). Multi-factor is essentially a requirement to use at least two of the following identification types with authenticating users:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Something you <strong>know<\/strong> (e.g., password or PIN)<\/li>\n\n\n\n<li>Something you <strong>have<\/strong> (e.g., software authenticator or hardware token)<\/li>\n\n\n\n<li>Something you <strong>are<\/strong> (e.g., biometric data)<\/li>\n<\/ol>\n\n\n\n<p>Until now, I&#8217;ve not taken the opportunity to implement such a deployment to understand how it is configured and functions. There are several different documents available online, however, I was unable to find one that was holistic and in-depth, yet simple in explaining the process of using a Yubikey for both local (e.g., console) and remote (e.g., SSH) access on a Linux host. Thus, here&#8217;s my elementary take on it. <strong>As you begin, be aware, this is a great way to lock yourself out, so use caution to understand this and test it before you attempt to use it!<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Remote &#8211; Yubikey with SSH (and certificates)<\/h2>\n\n\n\n<p>The easiest use-case is to implement a Yubikey to use an ed25519-sk certificate to accompany your FIDO\/U2F device, aka. your YubiKey.  This is easily generated using your local account and the below command.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keygen -t ed25519-sk<\/code><\/pre>\n\n\n\n<p>As part of this generation process, you&#8217;ll need to touch the button on your key. As usual, you&#8217;ll end up with two files, one for your public key as well as your private key. You&#8217;ll want to copy the public key to your user account on the remote host.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-copy-id -i ~\/.ssh\/id_ed25519_sk.<strong>pub<\/strong> caleb@192.168.1.198<\/code><\/pre>\n\n\n\n<p><span style=\"text-decoration: underline;\">In the enterprise, this is where things get interesting<\/span>. As you&#8217;ll see later, we&#8217;re going to change the SSHD config to require this certificate (and associated Yubikey confirmation touch), as well as a password. This means that<strong> this step will no longer work<\/strong>, so you&#8217;ll have to setup a syncing process to pre-populate these public keys for every host and every user account.  However, during this first login you can now see that not only does it succeed, but only required the certificate, not MFA as no password was required.  <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"741\" height=\"269\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image.png\" alt=\"\" class=\"wp-image-296\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image.png 741w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-300x109.png 300w\" sizes=\"auto, (max-width: 741px) 100vw, 741px\" \/><\/figure>\n\n\n\n<p>To fix that, we need to change our<em> \/etc\/sshd\/sshd_config<\/em> with the below settings&#8230;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Ensures that we require both factors, key and password\nAuthenticationMethods publickey,password\n\n# Ensures that a user cannot upload a non-FIDO key file\nPubkeyAcceptedAlgorithms sk-ssh-ed25519@openssh.com\n\n# Ensures that the FIDO signature is required\nPubkeyAuthOptions touch-required<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"452\" height=\"237\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-1.png\" alt=\"\" class=\"wp-image-297\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-1.png 452w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-1-300x157.png 300w\" sizes=\"auto, (max-width: 452px) 100vw, 452px\" \/><figcaption class=\"wp-element-caption\">This is the entire sshd_config for future reference as we talk about console\/local access.<\/figcaption><\/figure>\n\n\n\n<p>As you can see upon next login, you are prompted for the Yubikey touch, as well as the password. Congratulations, you&#8217;ve successfully implemented multi-factor using phishing-resistant FIDO authentication!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"745\" height=\"287\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-2.png\" alt=\"\" class=\"wp-image-298\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-2.png 745w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-2-300x116.png 300w\" sizes=\"auto, (max-width: 745px) 100vw, 745px\" \/><\/figure>\n\n\n\n<p>As a last step, let&#8217;s see what happens if I launch the connection without my Yubikey inserted.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"949\" height=\"70\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-3.png\" alt=\"\" class=\"wp-image-299\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-3.png 949w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-3-300x22.png 300w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-3-768x57.png 768w\" sizes=\"auto, (max-width: 949px) 100vw, 949px\" \/><\/figure>\n\n\n\n<p>As expected, the connection will not succeed!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Console &#8211; Yubikey with USB (in air-gapped, challenge-response mode)<\/h2>\n\n\n\n<p>Accomplishing a console connection has a similar challenge to the SSH certificate, in that you&#8217;ll need to store the individual challenge files in a directory for the PAM module to access. For this example, I&#8217;m going to create <em>\/var\/yubico\/<\/em> and change it so that all users have write and execute permissions (to upload their challenge files). <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir \/var\/yubico\nsudo chmod 0773 \/var\/yubico<\/code><\/pre>\n\n\n\n<p>In a production environment however, these should be generated out-of-band when the Yubikey is provisioned for a user, and placed in a protected location, without direct user access.  Speaking of provisioning, we&#8217;ll do that now, by configuring our key to be in Challenge-Response Mode, using OTP in Configuration Slot 2. You can use the <a href=\"https:\/\/developers.yubico.com\/yubikey-manager-qt\/Releases\/yubikey-manager-qt-latest-linux.AppImage\">Yubikey Manager download<\/a>, or for my Ubuntu box it was available as the yubikey-manager package.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Install the Yubikey Manager\nsudo apt install yubikey-manager\n\n# Configure your key for Challenge-Response OTP, using Slot 2, requiring touch (-t) and generating (-g) the secret.  Make sure to save your secret.\nykman otp chalresp 2 -t -g<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"588\" height=\"80\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-7.png\" alt=\"\" class=\"wp-image-305\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-7.png 588w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-7-300x41.png 300w\" sizes=\"auto, (max-width: 588px) 100vw, 588px\" \/><\/figure>\n\n\n\n<p>Next, we&#8217;ll install the PAM module (unconfigured), and create our initial challenge file for this user and this Yubikey.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Install the PAM module\nsudo apt-get install libpam-yubico\n\n# Create the challenge file, storing it in \/var\/yubico.\nykpamcfg -2 -v -p \/var\/yubico\/<\/code><\/pre>\n\n\n\n<p>Note: when it is sending the HMAC challenge, you need to touch your button on the Yubikey.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"647\" height=\"129\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-8.png\" alt=\"\" class=\"wp-image-306\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-8.png 647w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-8-300x60.png 300w\" sizes=\"auto, (max-width: 647px) 100vw, 647px\" \/><\/figure>\n\n\n\n<p>Next, we&#8217;ll change the PAM configuration to require our Yubikey for user authentication. Detailed documentation is available in the pam_yubico man page (<a href=\"https:\/\/developers.yubico.com\/yubico-pam\/Manuals\/pam_yubico.8.html\">online version<\/a>).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dpkg-reconfigure libpam-yubico<\/code><\/pre>\n\n\n\n<p>At this point you&#8217;ll want to update the below (which is then stored in the \/etc\/pam.d\/common-auth).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mode=challenge-response chalresp_path=\/var\/yubico<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"334\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-4-1024x334.png\" alt=\"\" class=\"wp-image-301\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-4-1024x334.png 1024w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-4-300x98.png 300w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-4-768x251.png 768w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-4.png 1027w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, select the &#8220;Yubico authentication with Yubikey&#8221; option to enforce MFA.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1021\" height=\"351\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-5.png\" alt=\"\" class=\"wp-image-302\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-5.png 1021w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-5-300x103.png 300w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-5-768x264.png 768w\" sizes=\"auto, (max-width: 1021px) 100vw, 1021px\" \/><\/figure>\n\n\n\n<p>Now, you should be able to login with your Yubikey and password from the console. <strong>After typing in your user name for console login, you&#8217;ll not be visually prompted on the console output to do anything, however, your Yubikey will be blinking<\/strong>.  Once you touch the button, the normal prompt will appear for the user&#8217;s password.  Completion of these steps will now allow successful login.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Oh, no, SSH is now broken!<\/h2>\n\n\n\n<p>From my limited experience, this is where things start to get messy, and where you should consider if using PAM on the console is a strict requirement, or, if you can secure your console via an alternative mechanism, such as MFA to access the console screen (e.g., AWS Console). If you know of a better way to accomplish this, please send me a note! At this point you&#8217;ll notice that if you attempt to SSH (which previously worked), you will now no longer be able to login. Instead you will be prompted that your password is incorrect. This is because the PAM common-auth module is attempting to use the Yubikey, which of course isn&#8217;t accessible. You can do USB passthrough via SPICE (or similar), but at that point, why bother with SSH in the first place?<\/p>\n\n\n\n<p>I&#8217;ve tried various settings and options, from configuring the pam_u2f with a local authfile, to changing the AuthenticationMethods in sshd_config, but, thus far have not found a good working solution that allows this to work as-is with PAM. There is a workaround, which is to disable the use of PAM by setting <em>UsePAM <\/em>to no.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"429\" height=\"112\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-9.png\" alt=\"\" class=\"wp-image-311\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-9.png 429w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-9-300x78.png 300w\" sizes=\"auto, (max-width: 429px) 100vw, 429px\" \/><\/figure>\n\n\n\n<p>This workaround however does come with an implication, PAM is still used for certain operations, such as <em>sudo<\/em>, which means that as-is, your user will be prohibited from performing <em>sudo<\/em> operations by the same incorrect password error.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"735\" height=\"137\" src=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-11.png\" alt=\"\" class=\"wp-image-313\" srcset=\"https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-11.png 735w, https:\/\/blog.lottabytes.com\/wp-content\/uploads\/2024\/05\/image-11-300x56.png 300w\" sizes=\"auto, (max-width: 735px) 100vw, 735px\" \/><\/figure>\n\n\n\n<p>This again is easily handled (but has associated implications) by removing the password requirement to run the sudo command for certain users. In this case, by having an admin group that I&#8217;m a member of.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>%admin ALL=(ALL) NOPASSWD:ALL<\/code><\/pre>\n\n\n\n<p>I hope that this is useful to someone else out there in exploring MFA for both use cases together.  Individually, they are rather straight-forward, however, combined they have proved, at least to me, to be a bit more challenging.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For a while now I&#8217;ve been curious about the application of multi-factor authentication for Linux console and SSH access. It&#8217;s a particularly challenging use-case, especially if you operate in an air-gapped configuration, and yet need to meet industry standards such as PCI&#8217;s Control 8.5.1, or NIST 800-53&#8217;s Control IA-2(1). Multi-factor is essentially a requirement to &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blog.lottabytes.com\/index.php\/2024\/05\/24\/mfa-tutorial-yubikey-usage-for-both-console-and-ssh\/\" class=\"more-link\">Read more<span class=\"screen-reader-text\"> &#8220;MFA Tutorial &#8211; Yubikey Usage for both Console and SSH&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[81,74,72,75,77,79,80,71,73,76,78,70],"class_list":["post-293","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-2fa","tag-configuration","tag-console","tag-fido","tag-how-to","tag-mfa","tag-multifactor","tag-ssh","tag-terminal","tag-u2f","tag-ubuntu","tag-yubikey"],"_links":{"self":[{"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/posts\/293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/comments?post=293"}],"version-history":[{"count":23,"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/posts\/293\/revisions"}],"predecessor-version":[{"id":328,"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/posts\/293\/revisions\/328"}],"wp:attachment":[{"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/media?parent=293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/categories?post=293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.lottabytes.com\/index.php\/wp-json\/wp\/v2\/tags?post=293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}