import { addBanner, addArticle, addHeader, addParagraph, addSubHeader } from "/scripts/article.js"; import { addInset, addInsetList, addInsetCodeListing, addInsetBulletList } from "/scripts/inset.js"; import { addImageWithCaption, addButtonGroup } from "/scripts/visuals.js"; import { menu } from "/scripts/buttongroups.js"; const main = document.querySelector("main"); main.append(addBanner("Learning SSH", "Scott Simpson", "February 2022")) main.append(addArticle()) const article = document.querySelector("article") article.append(addHeader("Installing SSH")) article.append(addParagraph("The most common ssh server is probably openssh which can be installed on a Raspberry Pi with the command")) article.append(addInset("sudo apt-install openssh-server")) article.append(addHeader("OpenSSH Server Configuration")) article.append(addParagraph("The primary config file for openssh-server is")) article.append(addInset("/etc/ssh/sshd_config")) article.append(addParagraph("You will probably also see a file called ssh_config and this is the configuration file for the ssh client. The image below shows part of the sshd_config file.")) article.append(addImageWithCaption("./images/config.jpg", "Part of the sshd_config file.")) article.append(addParagraph("You will normally see an include directive, in this case it is")) article.append(addInset("Include /etc/ssh/sshd_config.d/*.conf")) article.append(addParagraph("This will read in any additional config files from the specified location which is normally /etc/ssh/sshd_config.d/")) article.append(addParagraph("On my Raspberry Pi Dev machine, this folder is empty. If there are multiple files here, they will be read in in the order in which they are sorted in the directory, in other words in name order. For tha reason, it is common for the name to start with a number so you might see files like 01-VPN, 02-local_users and so on.")) article.append(addParagraph("The main advantage of these additional files is that they will allow you to make changes to the configuration without having to edit the primary config file. Not all Linux distros will use the same naming conventions for these files so you may need to read the documentation for your system to find out how the config files are setup or you can Google the ssh setup for your particular setup. The Raspberry Pi seems to follow these conventions pretty closely.")) article.append(addParagraph("The config files are read from top to bottom, as you might expect. Something you might not expect is that the settings are set on the basis of the first-encountered setting. In other words, if you have a particular setting that is set twice in the same file (or any included files), the first value set is the one that applies. For example, you might have a setting in the mai config file that you want to override - remember that one of the advantages of having additional config files is that you can change the settings without changing the main file - so you would probably put the new value in one of the additional files. However, you would want to ensure that it is read before the setting is encountered in the main file and for that reason, it is normal to have the includes at or near the top of the file.")) article.append(addParagraph("Another advantage to this approach is that if you want to revert to the original setting, you just need to delete the additional config or remove/disable that setting.")) article.append(addParagraph("On my Raspberry Pi, there are no additional config files by default, but if you do have some, it is important to know what settings are in those files because you might have a setting that doesn't have the value you expect if that value is in the main config file but is set in one of the additional files. If you have a setting that isn't being given a value in the config files, it will take a default value.")) article.append(addParagraph("If you need more info on the sshd_config, it has a man page you can access with the command")) article.append(addInset("man sshd_config")) article.append(addParagraph("You can also find an online version of the man page on linux.die.net and links to several relevant man pages at openssh.com, including those for ssh, sshd, ssh_config and, of course, sshd_config as well as some links to additional information.")) article.append(addParagraph("You might also find some additional useful info on the openssh.com.")) article.append(addParagraph("As you might expect, some of the key settings in the sshd_config file are near the top and you can see that in the portion of the file in the image above which shows the top of that file. Right after the include directive we have the port number which displays the port number ssh listens on - in other words, this is the port number you would use when you ssh to the machine. The default is 22 so if the value is set to that, you would normally see it commented out. Normally, you would enable this line when setting the port number to something else. I actually don't use port number 22 which is why it is commented out here but I changed it back for the image - security reasons! The reason you would use a non-standard number because it provides better security although it is not hugely effective, especially if you use one of the common alternatives such as 2222, 22022 and so on.")) article.append(addParagraph("The ListenAddress directive is use to specify which interface ssh is listening on so and the default address which is 0.0.0.0 means that it will listen on any address. This is only really relevant if you have more than one interface and you want to listen on one specifically so in the majority of cases, this won't be terribly relevant. If you do need to listen on a specific interface, you can insert the ip address and you would use the ListenAddress directive for an IPv4 interface and the ListenAddress :: directive for an IPv6 interface.")) article.append(addParagraph("As with the port number, in most cases the directives are commented out because the values they show are the defaults so if you want to change one, you could either uncomment it and change a value or leave it commented out and add the uncommented value as the setting that you want. This second option can be really useful because it does allow you to easily revert to the default value even where that is not easy to remember. It doesn't matter which order you put those in but personally, I would always have the default value first so any other settings would come after and can easily be recognised as customised values but either way, if you want the defaults to be easy to identify, you will need to apply the customisations in a consistent way.")) article.append(addParagraph("Another approach would be to copy the config file in its original state to something like sshd_config.def and never make any changes to that, creating additional backups if needed. If you are making any changes to a config file, it's always a good idea to back it up first, even if you are confident that you know the effect the changes will have.")) article.append(addInset("In this context, you should always remember that it is much better to have a backup that you don't need than to not have a backup and need it.")) article.append(addParagraph("The HostKey directives are very important, but they relate to the servers cryptography settings so we are not going to be covering them here.")) article.append(addParagraph("A little bit futher down, we have the directives that determine how people log in including LogInGraceTime (the amount of time you have to respond an authenticaton challenge) and MaxAuthTries. One that is potentially quite important from a security perspective is the PermitRootLogin directive")) article.append(addInset("#PermitRootLogin prohibit-password")) article.append(addParagraph("The default shown above allows a login by root but it doesn't allow a login with a password. It is considered more secure to change this to no.")) article.append(addParagraph("Next is the PubKeyAuthentication and the default value is yes which means the server will allow you to login with a key and there is a similar directive further down")) article.append(addInset("#PasswordAuthentication yes")) article.append(addParagraph("which, obviously, allows users to login with password. Again, this is set to yes but you may set it to no if you are allowing connections via ssh which we will do later. There are lots of other useful directives and reading through these is a worthwhile exercise even if you are not making any changes because it can give you a better idea of how ssh works by default and also how it can work by changing these settings.")) article.append(addParagraph("If you do need to make any changes to the sshd_config, or in the configuration fragments (the additional files, if any, in /etc/sshd/config.d/) you will have to restart the sshd with the command")) article.append(addInset("sudo systemctl restart sshd")) article.append(addParagraph("Of course, you can also use systemctl to stop or start the service or to check its status and you can use similar commands with ssh and just as a point of interest, you can also restart it with")) article.append(addInset("/etc/init.d/ssh restart")) article.append(addParagraph("Just a few quick notes on the man page for sshd_config, assuming you are looking at it on the server rather than one of the websites I provide links for. The start of the file is shown in the image below.")) article.append(addImageWithCaption("./images/man_config_sshd.jpg", "The start of the man page for sshd_config.")) article.append(addParagraph("You can easily navigate through the file using the arrow keys and you can also use f or b to move forwards and you can also use page down/page up keys on your keyboard and you can also search using the / key or the ? key.")) article.append(addParagraph("Here is a snippet of the man page showing the info for PermitRootLogin.")) article.append(addImageWithCaption("./images/PermitRootLogin.jpg", "Excerpt from the man page showing a description of the PermitRootLogin directive.")) article.append(addParagraph("The man page shows the usual default value and also gives you at least some of the options.")) article.append(addHeader("Managing Users and Access")) article.append(addParagraph("Consider this. Sitting at my desk, I am working on a Windows PC which is on the floor but I also have a couple of Raspberry PIs sitting on the desk and one of the ways I access these is by SSH. I could hook up one of these PIs to my monitor, add a keyboard and mouse and use it directly so I would start it up, login with one of the user accounts I have on the machine and I would then be presented with the desktop.")) article.append(addParagraph("If I don't want to go through the hassle of switching cables around and so on, I can just use my Windows machine to access them and there are a couple of ways I do that. If I want to use the PI's desktop, I would RDP to it but if I just want to open a shell on it to run commands, I can just use ssh. In both cases, I am logging into the machine with an account that I have on the Raspberry Pi asif I was logging in directly.")) article.append(addParagraph("These are both methods of initiating a remote login, but the important thing is that you are logging in as a user on the machine so you have to have an account that it will recognise in order to do that. Well, you actually don't, there are other methods you can use to grant access to a machine such as a directory service which you might want to use if you want to make a machine publicly accessible to users without an account on the machine, you could set up a directory service, but that isn't covered in this course.")) article.append(addParagraph("For the purpose of giving users access to a machine via ssh, we need to set up a username and password for those users although you may also find that accounts are setup for different purposes rather than for different users. For example, my two Raspberry PIs are a Web Server and a Development Server and both have an account with my name but I also have accounts which I use for Ansible, NextCloud or just for Development purposes on them although I may not necessarily want to use those accounts for ssh access.")) article.append(addParagraph("I can configure these servers to allow access via a password or I can use some other method such as ssg keys or a yubi key, to give a couple of examples. By default, local users on the server (in other words, users who would be able to log into the machine locally) can also access the machine via ssh.")) article.append(addParagraph("If you want to control which of the local users can log in remotely via ssh, you can add a directive to the sshd_config file which allows specific users to access the machines")) article.append(addInset("AllowUser leigh vishal stefan # only allow these users to log in")) article.append(addParagraph("or you can deny access to specific users with the DenyUser dircetive")) article.append(addInset("DenyUser bob mary paulina # allow all users except these")) article.append(addParagraph("Similarly, we can apply directives that allow or deny access based on group membership. For example, we can allow access to members of certain groups.")) article.append(addInset("AllowGroup admins developers # only allow users in these groups to log in")) article.append(addParagraph("We can deny access to members of groups with")) article.append(addInset("DenyGroup sales marketing # allow all users except those in these groups")) article.append(addParagraph("The order in which these directives are applied is important and remember that they are applied on the basis that the first one encountered is the config file is the one that applies. Essentially, you might think of it as this - once a directive is applied, it won't be overwritten by a later directive with a contradictory setting. In this context, that means that if a user is granted access via an AllowUsers directive and later in the file (or in another file that is read later) they are denied access via a DenyUsers directive, that user will be allowed access.")) article.append(addParagraph("You can use the Match directive in order to specify different access parameters based on different users or groups so this is a little bit more sophisticated than just allowing or denying access to certain users or groups. For example")) article.append(addInsetList(["Match Address 10.0.1.8/24", " PermitRootLogin yes", " PasswordAuthentication yes"])) article.append(addParagraph("This directive allows the root user to login with a password if they are logging in from an IP address of 10.0.1.x where x is any host on that network.")) article.append(addParagraph("Here's another example:")) article.append(addInsetList(["Match User alice bob Address *", " PasswordAuthentication no", " Pubkey Authentication yes"])) article.append(addParagraph("This allows the listed users, alice and bob, to login from any IP address using a key only. It doesn't allow them to login with a password.")) article.append(addParagraph("As with any form of access control, if you are implementing this in a large organisation, it can get very copmlicated so it is important to adopt a cohesive strategy and apply it consistently but for most users, it's probably not going to be much of an issue. The main advantage to developing an understanding of these is that it might be helpful in troubleshooting any issues you might have connecting to a machine.")) article.append(addParagraph("Another approach to access control, albeit one that isn't really suitable for use on a large scale, is to simply disable the users account on the server or to completely remove it.")) article.append(addHeader("Connecting to a Server Using a Password")) article.append(addParagraph("There are various ways to ssh to a client from a terminal or a terminal emulator like putty or kitty or from some client application like WinSCP, but if you are logging in using the credentials (username and password) of an account on the server, the basic method is always the same. For example, to ssh from a terminal to a remote server you would execute a command like:")) article.append(addInset("ssh philip@192.168.0.30")) article.append(addParagraph("If you don't provide a username in the command, you will be asked to provide it along with a password and possibly another form of authentication such as a yubi key. If you connect via a client like kitty, it will provide a simple input box you can insert the servers address and when you click connect, you will be prompted for a username (unless you included that with the hostname/IP address) and password.")) article.append(addImageWithCaption("./images/kitty.jpg", "Kitty - an SSH client.")) article.append(addParagraph("There can be some variation with this, typically with the port number as was mentioned earlier. With kitty, you can insert the required port number in the port field and if yoa are connecting via a terminal, you can use the -p flag to specify the port number like this:")) article.append(addInset("ssh philip@192.168.0.30 -p 2233")) article.append(addParagraph("If you are connecting to a edrver for the first time, a message will pop up to let you know that the authenticity of the host can't be established and you have the option of continuing to connect or aborting. If you continue, that hosts ECDSA key fingerprint will be saved in a file called known_hosts which is stored in the users home directory inside a hidden directory called .ssh. An example of this is")) article.append(addInset("|1|LOPsxL/rQ0cCFmuKpUJ7XxDONK0=|avw9JhFTvxskhotH2Ma2wuiZp6g= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKw78+w1QooELFbx4TTZVXGhmynpBrZPmnvCKsds1ZWceGIbmWB4eoOk5UkId3/kOM1dmqNcLtOYOzXY2oUAB8s=")) article.append(addParagraph("which is in the known_hosts file on my web server. This is the ECDSA key fingerprint for my second Raspberry Pi (my dev machine). You can manually add the fingerprint to the known_hosts file and this might be provided by an administrator.")) article.append(addParagraph("It's important to remember that this is a security feature because the fingerprint identifies a machine uniquely so it's a way of saying that the server you are connecting to is the machine that you think it is. If you get an error with this, it maybe because you are not connecting to the right machine although there are legitimate reasons why an error might occur and we will look into that in a bit more detail later.")) article.append(addParagraph("Logging into a server with a password is probably the most common way of establishing a remote connection but it is not as secure as using an ssh key so we will look at that next.")) article.append(addHeader("Creating a Key Pair with ssh-keygen")) article.append(addParagraph("When you create a key with openssh, both a public and private key are created. You would use the public key to encrypt data and, as the name suggests, this key is generally made avialble to the public. The private key is used to decrypt the data and is kept secret so only someone has that key will be able to decrypt the data. When you connect to a server with a key, you provide it with a copy of your public key so it can send encypted data to you but only you are able to decrypt it with your purivate key. If you need to send encrypted data to the server, you would encrypt the data with the servers public key. In essence, anyone can send you encrypted data to you using your public key but only you can decrpyt that data.")) article.append(addParagraph("Just a quick sidebar here to mention the obvious implication. When you send your public key to the server, if you sent it encrypted with your public key, the server cannot decrypt it. You can encrypt it with the server's pulic key, but only if you have received an unencrypted copy of it. Again, the server could send the data encrypted with your own public key, but it can only do that if it has received an unencrypted copy of your key.")) article.append(addParagraph("The key (pardon the pun) point to understand from this is that at least one of the keys has to be sent unecrypted or in some way provided to the server in an unencrypted form. Once that has happened, you can then start to communicate using encrypted data. For instance, let's say to forward an unencrypted message to the server saying here is my public key. The server can then send you its own public key but it can be sent in an encrypted message because it can encrpyt it with the public key you sent in. Both the client and the server now have each others public keys so they can communicate securely by encrypting data with the public key of the recipient.")) article.append(addParagraph("If you are setting up secure communication between two machines that you have physical access to, the public key doesn't have to be sent. It can be copied into the appropriate file but the overall effect is the same. Secure communication can only take place once at least one of the parties has received the other's public key and can only be secure in both directions once both parties have the other's public key.")) article.append(addParagraph("From the client's point of view, this provides you with both a more secure method of connecting to a server but it is also an easier method since it does not require you to enter a password.")) article.append(addParagraph("The command to generate a key pair is")) article.append(addInset("ssh-keygen")) article.append(addParagraph("There are two ways we can run this. We can run this in the conventional way by providing any required parameters but we can also run it in interactive mode by running the command on its own as shown. You will then be prompted to either provide the required parameters or accept the defaults.")) article.append(addParagraph("The image below show a key pair being generated in interactive mode.")) article.append(addImageWithCaption("./images/ssh-keygen.jpg", "Using ssh-keygen in interactive mode.")) article.append(addParagraph("The first parameter is the location in which the key is saved and the default is /home/developer/.ssh. We can cd into that directory and get a directory listing as shown below. ")) article.append(addImageWithCaption("./images/keys.jpg", "A listing from the .ssh directory.")) article.append(addParagraph("We have two keys, the private and public keys and there is also a file called known_hosts which we mentioned earlier. When we generated the keys, we had the option to change the directory in which they were stored and also the filename for the key. If you only have one key, it can be convenient to just use these default values because ssh client software will automatically look for it here. However, if you are using multiple keys for different purposes, the filename can be set to something that is more descriptive nad tells us what the purpose of the key is.")) article.append(addParagraph("Although not recommended, you can use one key for a variety of different purposes. It is more usual to have one key for each server and pass that key to any client which will need to access the server. We might also reverse that and have a different key on the server for each client that we will access.")) article.append(addParagraph("There was also the option to provide a password with the key and if one is entered, that password would be required every time the private key was used which, you might think, largely defeats the purpose of using a key, but it provdes an extra level of security in case the private key is accidentally given out. The question of whether a password should be set would come down to whether that additional security is worth giving up the convenience of using a key in place of a password. We also saw a visual representation of the key which can be used, later, to verify the key if needed but that isn't commonly used and we have the fingerprint (which, again, was mentioned earlier.)")) article.append(addParagraph("Once we have a key pair, we can transfer the public key to a server which will allow us to easily connect to it and we will look at that next.")) article.append(addHeader("Managing and Using Key Pairs")) article.append(addParagraph("So if we are going to connect to a server using an ssh key, we have to get our public key onto the server and the method we use depends on how much access we have to that server. The simplest was is where you already have access to the server - in other words, you can connect to it via a password or you already have a key setup. For example, you might have arranged access to it via a password with the intention of later disabling that access once you are able to connect to it via the key you've created for that purpose.")) article.append(addParagraph("If that's the case, you can use the command")) article.append(addInset("ssh-copy-id")) article.append(addParagraph("To demonstrate how this works, I will use the command to take the public key on my developer machine and transfer it to my web server so that I can login with out a password. Now, it's not enough just to transfer the key to the web server. When I try to login to the machine, I still need to login with an account on it because the web server will look in the .ssh folder belonging to the account I am using to connect. If it finds the public key that matches my private key, the log in will be accepted.")) article.append(addParagraph("This means that as a minimum, we need to tell the web server what account we want to setup the key for. In my case we will need an additional parameter to tell the web server the port number we want to connecto on and this would be the same port number you would normally use to ssh to the machine. The command will therefore look something like this.")) article.append(addInset("ssh-copy-id -p 22 philip@192.168.0.20")) article.append(addParagraph("with the actual port number we want to use replacing the 22. Once that's done, we can ssh to the machine with just the command")) article.append(addInset("ssh philip@192.168.0.20")) article.append(addParagraph("The value of this is quite limited since I would normally connect to the servers from my Windows machine using kitty which means that if I want to ssh to my web server without a password, I would first have to ssh to the dev machine with a password and then I can ssh without a password to the dev machine so this is atually just adding another layer to the process. In order for this to be really useful, I need to be able to create a key on the Windows machine and store that public key on the web server.")) article.append(addParagraph("Creating the key is as easy as it is on a Linux machine, it uses the same command and works in the same way so you would open up a command prompt and execute the command")) article.append(addInset("openssh-keygen")) article.append(addParagraph("and you will be asked the same questions again and we will again, have to copy the key server so we will look at other methods of doing that.")) article.append(addParagraph("There may be times when we don't have remote access to the server but we do have local access. That might be, for example a local server that doesn't allow us to connect via a poassword or an existing ssh key. If that's the case, an alternative option is to use some method that allows us to copy the file without needing to log into it.")) article.append(addParagraph("There are a number of ways to do that and these include:")) article.append(addInsetBulletList(["Send it by email.", "Upload it to the cloud.", "Save it to ane external device like a USB stick or an SD/MicroSD card or an external hard drive."])) article.append(addParagraph("In my case, I am going to use a method that is almost a hybrid of these two methods in that it copies the files over the internet so it actually does require an ssh login but not the usual login via kitty, and it does physically copy the file to the server. In other words, I will use a file transfer program which for me is WinSCP. This is a good and easy option for me because I always keep a connection to the server open via WinSCP because this monitors the local files for my website and copies any changes over to the web server. This means that if I copy the file into the folder that it is being monitored, it will almost instantly appear in the FTP folder in my home directory on the web server. From there, I movied it almost immediately to my home directory to avoid it being automnatically copied to the live site (probably not a big deal if it was since it is a public key bit I don't want it to clutter up my website). I can open it with Vim, copy it and paste it into the authorized_keys file and in theory, I should now be able to connect to the web server (from kitty on my Windows machine) without being asked for a password.")) article.append(addParagraph("For some reason, this doesn't work. I also tried, unsuccessfully to add the key to the kitty login session. I will come back to this later although I most likely won't update this course with details of how that was done.")) article.append(addParagraph("In some instances, we will not be able to access the server at all until our public ssh key has been added to the list of authorised hosts so if that's the case, we would need to send the public key (probably by email) to the administrator of that system and have them add the key for us. Another option would be to ask the system admin to generate a key pair for us, add the public key on their system and send the key pair (or at least the private key) to us.")) article.append(addParagraph("From a security standpoint, this is not much different from asking the sysadmin to create a password for us that we would not be able to change. In other words, it means that your private key is not completely private. For that reason, this is very much NOT a recommened option.")) article.append(addParagraph("There is no security risk in giving out your public key, but you should treat your private key as if it were a password which means not only keeping it private, but you would also want to generate a new key pair if your private key is compromised. If you are also using a passphrase along with your private key, this might give you a bit of additional security in the event that someone were to get a hold of your private key, but it makes sense to assume that someone who has been able to obtain your private key would have the passphrase too, so you should still generate a new key pair and if you are using this to log in to a server, you would have to replace the old public key with the new one.")) article.append(addParagraph("Once the key is installed and you are able to sign in to the server using the ssh key, you might then want to disable the ability to connect with a password. Bear in mind, your ssh key will not provide any additional security if you can still log in with just the password. Essentially, the reason for choosing to replace logging in with an ssh key rather than a password is because it is more secure - someone might guess your password but realistically, they can't guess your private key. However, if you don't disable the ability to log in with a password, that completely negates the advantage you get by installing the ssh key.")) article.append(addParagraph("To disable it, we need to edit the sshd_config file which you might recall is in the /etc/ssh folder. There are two directives in that folder that are of direct relevance here and the first is")) article.append(addInset("#PubkeyAuthentication yes")) article.append(addParagraph("This is the directive that allows us to sign in with an ssh key. This is currently disabled, but the default value is yes so enabling it will not really make any difference, although I would personally recommend enabling it because that makes it explicit. Someone might read this file to work out how connections can be made and may not know what the default value is so enabling it just makes it so much clearer. The second directive is")) article.append(addInset("#PasswordAuthentication yes")) article.append(addParagraph("Again, this is commented out but you don't need to enable it to allow users to login with a password since yes is the default and as with the previous directive, enabling it may just make these options a little bit clearer since we are not relying on defaults. There are a couple of things worth mentioning here. Firstly, the disabled options are the defaults (in general) so you might reaonably assume that the two disabled directives shown above will apply unless we enable them and set the value to no. Consider this scenario. You want to disable password authentication so you enable the comment and set the value to no. Later, you want to re-enable it so you do that by disabling the comment, assuming you know the default value is yes.")) article.append(addParagraph("This leaves us with a disaled comment and a value of no which we might assume is the default value.")) article.append(addParagraph("This is just a personal opinion but since it relates to security, I would suggest that you don't make any assumptions about the default values but that you enable the directives and set it to the required value which is far less likely to lead to the introduction of errors if you have misread the file or midunderstood a default value. If you need to change it later, change it to the bnew value but don't disable it again. If you do that, the config file will tell you exactly what the setting is so it is clear what authentication options are allowed.")) article.append(addParagraph("Another, perhaps more extreme, but probably accurate approach is to assume that any connection method is allowed unless it is explicitly set tp no in the config file.")) article.append(addParagraph("THe second thing to bear in mind is that you need to be absolutely certain that you can connect to a server via the ssh key before you disable the option to sign in with a password although this is less important if you have access to physically log in to the machine which would allow you to fix any errors in the ssh config. Another consideration is whether there are any other users or other accounts which might need to connect to the server. If you disable password authentication without setting up an ssh key for each user or one that each user can use, that might give you problems.")) article.append(addParagraph("For me personally, there is a chance this could be a problem because on both my web server and my development machine I have accounts for specific purposes such as a developer account and andible account on my dev machine and an admin account on my web server - these are in addition to my own account. In that scenario, I may need to ssh to either machine with any of these accounts so setting up the key for all of them would be better before disabling password authentication. In this situation, the problem isn't unrecoverable provided I can log in with at least one user account which would allow me to vopy the private key into the .ssh folder of the account that can't connect and I would also add the public key to the authorised hosts file for that user account on the server.")) article.append(addParagraph("The point here is that when you are changing the connection options, you should always ensure that you can still connect to the server, even if it is only with one account and it doesn't matter if that connection method is via ssh or a local login. I think that if you assume you are going to break something when you change the connection options, that's a more sensible approach than assuming that you will get it right the first time!")) article.append(addParagraph("That's a bit of a tangent, in this context, once we have confirmed that all accounts on the server will allow the user to connect with the ssh key, then we can go ahead and disable the password authentication.")) article.append(addParagraph("As I already hinted, this does mean that we need to enable the password authentication directive and set its value to no since no is not the default value.")) article.append(addParagraph("Don't forget, changes to the config file will only take effect once the sshd service has been restarted so we will do that with")) article.append(addInset("sudo systemctl restart sshd")) article.append(addParagraph("Once that is done, we can then exit from the server and then reconnect to the server using a command like")) article.append(addInset("ssh philip@102.168.0.20")) article.append(addParagraph("This is the most basic form of the command and we may want to add a couple of options. One of these is the port option which can be ignored if you are connecting on the ssh default port which is 22.")) article.append(addParagraph("You can also specify the key to use with the -i option. This isn't really necessary, because if you omit the option, the server will try all of its public keys to see if there is one that pairs up with your private key. It can take longer to connect if there are a lot of keys for the server to check but it's oribably not going to make a noticeable differece if there are obly a few keys for the sesrver to check - you might actually spend more time typing the path and name of the key than you would save!")) article.append(addParagraph("In any case, let's assume we want to connect via port 223344 using a key stored in the usual directory but with the name, mykey.pub. Since the port number is not the default option, this had to be provided with the ssh command with the name of the key being optional but if want to specify it, our ssh command would look like this.")) article.append(addInset("ssh philip@102.168.0.20 -p 223344 -i ~/.ssh/mykey.pub")) article.append(addParagraph("One advantage of using the -i option here is that if you don't use it, the server will only look for a key in the .ssh folder of the users home directory so this option does allow you to place the key in a differnet directory and then provide the path to that directory when connecting. So storing the key in a non-standard directory is one situation where the -i option will have to be specified. Another would be where you have several accounts using the same public key where the server stores the key in the default directory for the user for whom it was installed. If a different user is connecting and they don't have the key store in their .ssh directory, the user can use the -i option to specify that the key is in the .ssh directory of another user.")) article.append(addParagraph("Setting up an ssh key is a bit more work than using a password to login but gives you more security. For a home network like mine, it's probably not a criticla issue but in a real world setting, you might consider the convenience of logging in without a password and the added security against the need to set up a key for every user or at least ensure that every user has access to a key. From the users perspective, they may also consider less of a convenience to connect without a password if they have to type in the path to the key and its name every time they connect.")) article.append(addParagraph("These factors should be taken into consideration when deciding whether to let users sign in with a password or with an ssh key.")) article.append(addHeader("Client Configuration Options")) article.append(addParagraph("As with the SSH server software, we can set options for the client software either in a config file or by providing a command line parameter. For example, as we saw previously we can use the p option to specify a port number to connect on.")) article.append(addInset("ssh -p 223344 philip@192.168.0.20 -p 223344")) article.append(addParagraph("Another useful option is the o option which actuslly does mean option! We can use it to specify an option by name and that includes options, like port, that have their own single letter specification. The name of the option would be Port and we would specify it with the o option like this.")) article.append(addInset("ssh -oPort=223344 philip@192.168.0.20")) article.append(addParagraph("We put the options in front of the ip address but we can also put it after and it will work just as well.")) article.append(addParagraph("The man page for ssh lists all og the single letter options and all of the named options you can use with the o option and if you read this, you might get some idea of why we need both the name and single letter variants. Firstly the sinlge letter variants are just easier to type - for example, the -i option we saw earlier has the name identity_file so you can either type -i or -oIdentityFile (notice the name capitalises the first letter of each word).")) article.append(addParagraph("Conversely, the advamtage of the name option is that it allows us to specify more than 26 options! Another advantage is that we can specify the name options in a config file and we also saw that previously with Port and PasswordAuthentication, for example. If you are using the same options repeatedly, you can put these into a config file so you don't have to provide them from the command line. Like the client software, we can specify global options in the config file (ssh_config) n the /etc/ssh directory or we can put them in an additional config file in the /etc/ssh/ssh_config.d directory. If the settings are specific to a user, we can put then in a file in the .ssh directory in the users home folder and we would usually call that file simply config.")) article.append(addParagraph("It is worth remembering that opitions specified in this file will take precedence over the global options in either ssh_config or the config files in ssh_config.d so the user specific settings will have the highest priority of any settings in a config file. However, if you specify an option at the command line, that will override any saved settings so it has the highest priority of all. As a matter of interest, the settings that have the lowest priority, as you might expect, are the default settings.")) article.append(addParagraph("Let's look at an example of how we might set up a config file inside the .ssh direcory in the user's home directory. I'll start by typing cd to navigate to my home directory and then cd into .ssh and user vim to edit the config file. This file doesn't exist by default, so if it is being edited for the first time, vim will also create it.")) article.append(addParagraph("If we want, we can put a username here so if you use the same account name for every server you connect to, putting it in this config file means that you don't have to provide that when you connect. This would look like:")) article.append(addInset("User philip")) article.append(addParagraph("As with the server config, we can add comments to the config file by putting a hash at the start of the line:")) article.append(addInset("# this is a commet")) article.append(addParagraph("and we can also use include to read in an additional config file:")) article.append(addInset("Include ~/.ssh/moreconfig")) article.append(addParagraph("This can make the client configuration more modular which is usually easier to manage.")) article.append(addParagraph("Within the config file, we can user Host to specify a particular IP address or with a wildcard, a network or domain name and this allows us to configure connections for that host. For example, let's say that we add the following to the config file.")) article.append(addInsetList(["Host 192.168.0.10", " User admin", "", "Host 192.168.0.20", " User developer", "", "Host 192.168.0.*", " User philip", ""])) article.append(addParagraph("Don't forget, these directives are read in order and where there is a conflict, priority is given to the first instance. Reading from the top, this means that if the host we are connecting to is 192.168.0.10, the username will be admin, if it is 192.168.0.20, the username will be developer and if it is anything else in that address range (192.168.0.x), the username will be philip.")) article.append(addParagraph("A really useful application of the host directive is that it allows you to create an alias or nickname for s aerver. AS an example, let's say that I want to set up a connection to my webserver from my dev machine or vice versa. I can set up a Host directive that looks like this.")) article.append(addInsetList(["host webserver", " Hostname 192.168.0.20", " User philip", " Port 223344"])) article.append(addParagraph("If I save this in the config file in a .ssh file on my dev machine, I can then go back to the command line and type")) article.append(addInset("ssh webserver")) article.append(addParagraph("I am then asked for the password for the philip account on the web server and I am connected to the machine. Let's just breifly consider this in the other direction and setup an alias on the webserver to make it easier for me to connect to the dev machine. The host directive is going to look very similar.")) article.append(addInsetList(["host devserver", " Hostname 192.168.0.10", " User philip", " Port 223344"])) article.append(addParagraph("I can save that and then connect to the dev machine with")) article.append(addInset("ssh devserver")) article.append(addParagraph("and I am immediately connected to the dev server.")) article.append(addParagraph("This actually raises a couple of points which you might not have noticed or perhaps thought I just didn't mention, so I will mention them now! Firstly, I did not restart the ssh service or, indeed the ssh server for reasons that may be obvious. When I executed the ssh command, the ssh client software on the web server will check the config file, find the hostname and its details and pass them over to the server I am connecting to. In a sense, this is a kind of shorthand for just typing the ssh command at the command line so we don't need to restart the service.")) article.append(addParagraph("Probably even more obvious, we don't need to restart the ssh server because we didn't change its config and when we connect to the dev machine, it is the dev machine that is acting as the server so restarting it would have no effect. If it did need to be restarted, it would be restarted on the dev machine.")) article.append(addParagraph("Another point is that I didn't enter a password and this is because I have already set up an ssh key for this connection. I think this hints at a more important point in that the early part of this course references setting up that key as arguably the central subject of this course so I think that it is important to remember that the alias controls how we set up the connection to the server and it is largely irrlevant what authentication method is used although we could include some details relating to that with our host directive.")) article.append(addParagraph("I think that the reason this is important is because it highlights the fact setting up the config for the client, we are setting up the parameters for a connection to another machine. If we want to set up an ssh key, we would generate the keys on the client and add them to server but it is the server that permits or denies an access request. In essence, we set up the alias, used it to connect to the dev machine and the server software on the dev machine determined whether or not a password was required.")) article.append(addParagraph("This is just a roundabout way of saying that we can setup this config for the client to make connecting easier and it really doesn't matter whether you need to type in a password (the server does care) so you can use this to make life easier even if you don't want to connect via an ssh key.")) article.append(addParagraph("You can add other hosts using a similar directive but you should always remember that the order in which you specify directives can be important. To give you an example, let's say that I want to change the config file on my dev machine so that it looks like this.")) article.append(addInsetList(["host webserver", " Hostname 192.168.0.20", " User admin", " Port 223344", "", "User philip"])) article.append(addParagraph("Now, if I run the command")) article.append(addInset("ssh webserver")) article.append(addParagraph("The ssh client will use the username, admin, to connect to the web server. We also have a User directive to set the account name to philip, but if the machine we are connecting to matches the hostname pattern, the account name will already set to admin and so the second user directive will not take effect. If it doesn't, everything in the host directive will be ignored and this time the second user directive will take effect. In short, this means that we will connect to the web server with the admin account, but will use the philip account to connect to anything else.")) article.append(addParagraph("One final security issue to mention. You create this config file in the .ssh folder insider the user's home directory so the chances are that the file will be owned by that user and will have the default permissions. THe image below shows a listing of the contents of the .ssh folder for the user philip on my dev machine.")) article.append(addImageWithCaption("./images/config_permissions.jpg", "The contents of the .ssh folder for user philip on my dev machine.")) article.append(addParagraph("Notice that both the owner and group are shown as philip and the permissions are the same as those for the public key and known_hosts and these are the default permissions. You might also notice that the private key and the authorized_keys have stricter permissons. THe user, philip, has read-write access but other users do not have any access which makes sense as this information is something we need to keep private. These permissions were set up the ssh-keygen program but I created the config file with vim so although this is also something to be kept private since it may contain sensitive information, the permissions don't reflect that so we will want to rectify that with the command")) article.append(addInset("chown 600 config")) article.append(addParagraph("This correct that oversight and means that the config file can now only be accessed by the user. Note that I didn't use sudo here because I was logged in as philip so I can change the permissions on my own file wothout needing elevated priviliges (I don't need sudo) but if you are updating the file for a user you are not signed in as, you would need to use sudo.")) article.append(addParagraph("To sum up, the client config files can be quite simple or quite complex depending on the number of machines you might want to connect to and they can make it easier and faster to connect to a server, particularly if there is something about the server that causes you a headache such as the username or the hostname being long and complicated or just easy to forget. The config files allow you to store that data so that you don't have to remember!")) article.append(addParagraph("We have seen several of the options that can be used to configure an ssh client but there are lots more and lots of ways you can configure your connection that are not mentioned in this course so it is recommended that you spend some time looking through the man pages for both ssh and ssh_config.")) article.append(addParagraph("You should also remember that client config options can be set in one of two config files. These are globally in /etc/ssh/ssh_config or for a single user in ~/,ssh/config - not forgetting that both of these files can include other modular config files. For example, for the user config shown above, you could put each host directive in it's own separate file - you might but the config for the connection to the webserver in a file such as ~/.ssh/webserver.cng. You can use any name you want and give it any extension you want including no extension as long as the include directive in config references the same filename. If you wanted, you could create a separate folder for the config files as we have for the global config files (/etc/ssh/ssh_config.d/ - it really doesn't matter as far as the ssh client is concerned as long as it can find the file or files that you include in config but it makes sense to that in a consistent way so either all your additional config files go into the ~/.ssh folder or they all go into a folder created for that purpose so that would make your config file easier to read and troubleshoot.")) article.append(addParagraph("Let me just reiterate that the order of the options is important both when the same setting is changed at the same level (ie globally or locally) in which case the first time that value is set will give you the value that applies and if you pass a particular value, such as a part number from the command line, for example:")) article.append(addInset("ssh webserver -p 22")) article.append(addParagraph("or")) article.append(addInset("ssh webserver -oPort 22")) article.append(addParagraph("This will override any port value you might have set in the local or global config files.")) article.append(addHeader("Using SSH Client Applications")) article.append(addParagraph("Most users will use a terminal program of some kind to set up an ssh connection to a server, but you can also use an ssh client. Personally I use kitty but there are a number of others available for both desktop PCs and mobile devices such as Prompt or Blink for iOS. They can have some useful features so you might want to check those out. Connecting to a server via a mobile device can be really convenient if you need to check something or make a quick tweak to a config file but they are mostly not suited to doing any substantial amount of work.")) article.append(addParagraph("One of the dangers when you are running a task on a remote server is that the connection might go down leaving tasks incomplete or they may abort so it is often essential to use a terminal multiplexer such as screen or tmux.")) article.append(addParagraph("You will most likely already have screen installed but tmux wasn't on my Raspberry Pi or on a Centos machine I checked so you can install it with a command such as")) article.append(addInset("sudo apt install tmux")) article.append(addParagraph("The whole point of using a multiplexor is that when you launch it, it starts a process that is independent from the current shell so it keeps running even if you disconnect or the connection is dropped so it can be really handy, especially when you are running something that is expected to take (or even just may take) several hours. You can enter the tmux environment with the command")) article.append(addInset("tmux")) article.append(addParagraph("You should then see something like the following:")) article.append(addImageWithCaption("./images/tmux1.jpg", "The tmux environment.")) article.append(addParagraph("Notice the green bar at the bottom of the page which shows your tmux sessions although we only have one, shown as 0:bash and there is an asterisk to show that this is also the current session. If we create a new session with Control + B followed by C, we will now see another session added, 1:bash, and this is now the current session.")) article.append(addImageWithCaption("./images/tmux2.jpg", "The tmux environment with a new session opened.")) article.append(addParagraph("To switch between sessions you can press Control and B again and then P to go the previous pane or N to go to the next pane. To close a session, press Control and B followed by X and you will be prompted to kill the session. You can also detach from the session with Control and B followed by a D. With screen, detaching from the screen keeps it active and allows you to reattach (or reopen the screen) whereas killing it would stop it entirely and you wouldn't then be able to reactivate it so if you were running a task in that screen, it would be terminated.")) article.append(addParagraph("With tmux, you seem to be able to reattach the session regadless of whether you chose to close or detach it. Let's see that in action. I will open up the tmux environment and run a top command and then close or detach the session, you can also close the terminal entirely. I will use Control + B and then D. I will then reattach the session with the command")) article.append(addInset("tmux attach")) article.append(addParagraph("The result is shown below.")) article.append(addImageWithCaption("./images/tmux3.jpg", "The reattached tmux session.")) article.append(addParagraph("If you want to completely kill the tmux session you can get its pid with a command like")) article.append(addInset("ps aux | grep tmux")) article.append(addParagraph("or")) article.append(addInset("pidof tmux")) article.append(addParagraph("You can then kill it with the kill command and the pid so, for instance, if the pid is 1234, the kill command would be")) article.append(addInset("kill -9 1234")) article.append(addParagraph("We have just scratched the surface of tmux and skipped over screen, but this is a useful tool and there is more to it than we have seen here. You can learn more about both in another of Scott's courses, Linux: Multitasking at the Command Line which was released in March 2023.")) article.append(addParagraph("We saw an example of how we can open a session in tmux, but we can also configure ssh to open tmux as soon as we connect to a server and this is described in a blog post by J Bowen which you can find on blog.jbowwn.dev.")) article.append(addHeader("Transferring Files with SFTP")) article.append(addParagraph("The primary purpose of ssh is to connect to another machine, but it can do other things as well. For one thing, it provides a more secure alternative to ftp which once upon a time was the primary method of transferring files between computers and is still used. It is, however, notoriously insecure and definitely not recommended for anything other than trivial data where security is not a concern.")) article.append(addParagraph("A better option nowadays is SFTP (SSH File Transfer Protocol) which is similar to ftp, it use the same get/put model, but it operates on a secure ssh connection so is, of course, more secure! It transfers files over an ssh connection so it will use the ssh credentials to secure that connection. Let's see how that is done from the command line.")) article.append(addParagraph("For the purpose of this exercise I am imagining that I am creating a project on my dev machine that I will want to transfer over to my webserver at some point so I have created a projects folder and created some empty project files I can expermiment with. To work with sftp, we need to start it running with a conection which you can do with a command like the following:")) article.append(addInset("sftp philip@192.168.0.20")) article.append(addParagraph("You should then be prompted for a password and if you enter that successfully, you should see the sftp prompt which indicates that you are now ready to start. However, it is worth pointing out that sftp uses port 22 by default and I assume that is because you are using it via the ssh conection so it would probably be more accurate to say that it uses the same port number you use to connect to via ssh but the method for specifying the port is a little bit different. If you have already setup a host directive in the config file, you can use that for sftp as well, to do that you would type")) article.append(addInset("sftp webserver")) article.append(addParagraph("or whatever alias you have set up for that host so in that sense, it works in exactly the same way that an ssh command would and would use the port number you specified in config. If you need to specify the port number from the command line, it is similar to the syntax you would use with two small differences. The first is the you use an upper case P rather than a lower case p as you would for an ssh connection. Unlike ssh, the port number must come before the host name or ip address (with ssh, it can come before or after) and also you cannot use (as far as I can tell) an o option with the word Port (ie, oPort).")) article.append(addParagraph("That was a little long winded but it took me a few minutes to find that because of the fact that I don't use the standard port number for ssh so I thought it was worth mentioning. That all being said, the sftp command with, let's say port number 223344 would be")) article.append(addInset("sftp -P 223344 philip@192.168.0.20")) article.append(addParagraph("Here you can see both the command to initiate sftp and the sftp prompt.")) article.append(addImageWithCaption("./images/sftp1.jpg", "The SFTP prompt.")) article.append(addParagraph("A good place to start is help which displays a list of sftp commands.")) article.append(addImageWithCaption("./images/sftp2.jpg", "The SFTP help command.")) article.append(addParagraph("Remember that sftp follows the same get/put model as ftp and this is reflected in the fact that we have both get and put commands to download and upload a file respectively. Note that these commands are run on the client so, for example, download refers to downloading a file from the server to the client and upload refers to uploading a file from the client to the server.")) article.append(addParagraph("We can also change directory on either the client or the server so we have some control over where the file that we download (or upload) will be stored. I want to save my project files in the projects directory I had created for this purpose on the webserver so I will navigate to it with the command")) article.append(addInset("cd /home/philip/Learning/projects")) article.append(addParagraph("Next, I want to verify that the remote directory is empty with ls and that the local directory contains the files I want to copy with lls. I can then copy the files using the put command. Note that I have already connected sftp to the server so I don't need to supply the servers name or IP address. I can just use put with the filename and I will start by uploading pf1.css with the command")) article.append(addInset("put pf1.css")) article.append("This shows a confirmation that the file is being uploaded. In this example, it's an empty file so it doesn't take long. I'm not sure if I can use wildcards here so next I will try to upload all of the files with") article.append(addInset("put *")) article.append(addParagraph("I see a similar result, this time listing all 5 files and confirming the uploads. When it's done I can use ls again to confirm that all of the files have been uploaded and again, I will run lls to compare this to the files in the local directory to confirm they have all been uploaded. Of course, you can also connect to the machine in a separate terminal, in my case a separate kitty session and navigate to the directory there to confirm that the files have been successfully uploaded. All of this is shown in the image below.")) article.append(addImageWithCaption("./images/sftp3.jpg", "Transferring files from the client to the server using SFTP.")) article.append(addParagraph("This is pretty straightforward, but copying single files or even a batch of files using a wildcard is pretty limited. For me, personally, I would like to be able to upload or download an entire directory including its contents and subdirectories etc. That doesn't seem to be covered in the course, at least not here, so before we move on to a GUI equivalent of sftp, I looked this up to find out if it is possible and it is, as I discovered from an article on TecMint dated February 25 2017. The article is entitled How to Upload Files/Directories Using sFTP in Linux, thanks to Aaron Kili for providing that.")) article.append(addParagraph("In order to try this out, I've deleted the temp files used in the previous test and created something that looks more like a real-life project although the files are just dummy files so they are all empty, but I have a project folder for an imaginary blog project which contain a file and some folders and those folders contain files or subfolders and so on. The structure is shown in the image here.")) article.append(addImageWithCaption("./images/tree1.jpg", "The directory structure and files for my imaginary blog project.")) article.append(addParagraph("We should be able to upload the whole project with the put command just as we did previously, but we will also add the -r option to upload recursively (in other words, to copy directories and their contents as well) so the command would be")) article.append(addInset("put -r blog")) article.append(addParagraph("If the command is unsuccessful or if it doesn't do what you expect, that should be fairly obvious because you will wee a confirmation of the files that are being uploaded as shown here.")) article.append(addImageWithCaption("./images/sftp4.jpg", "Confirmation of the files uploaded from the blog folder.")) article.append(addParagraph("Again, I can check that this has worked as expected by running tree on the web server to show that the structure of the blog project has been completely transferred with all its files.")) article.append(addImageWithCaption("./images/tree2.jpg", "The directory structure and files for my imaginary blog project on the web server.")) article.append(addParagraph("So that's how we transfer files from the local machine (client) to the remote machine (server) using sftp. It is fairly straightforward to use and would probably allow me to accomplish the main file transfer task that I have which is copying my websites files which I write on my Windows PC over to the web server which is on a Raspberry Pi. You may be aware of a utility called scp which has largely been replaced by sftp so if you are working at the command line, you would be more likely to use sftp if it is available. If you want to use a graphical interface, which can be a lot easier if you are moving a lot of files, WinSCP is basically a front end for scp and can be really convenient to use. At the moment, I use it for transferring files to my website primarily because it is so easy to use and it allows me to sync the files on my local machine with the server. Let's take a look at that now.")) article.append(addParagraph("When you start WinSCP, you are presented with something that looks a little like File Explorer in Windows which is what it is.")) article.append(addImageWithCaption("./images/winscp1.jpg", "The WinSCP interface.")) article.append(addParagraph("THe files on the local system are shown on the left but the right pane is blank because I am not connected to the server at the moment. To make the conection, I will click on the New Session button near the top left corner which brings up the login window.")) article.append(addImageWithCaption("./images/winscp2.jpg", "The WinSCP login window.")) article.append(addParagraph("This is pre-populated with the login details for my server. Notice that WinSCP, in spite of the name, does support the newer sftp protocol but you can select others, includig scp and ftp. I am connecting with the ip address and the port number and user name are pre-populated because this was the last connection I made. If it's not clear, I don't actually use 223344 as a port number, this is effectively hiding the actual number I use. In fact, 223344 is not a port number (which would be in the range from 0 to 65535) so if you are following along, don't use that number! I could also insert the password here but I usually just click login and type the password at the prompt which is more secure.")) article.append(addParagraph("I haven't set up an ssh key for this connection yet so I will do that now. Actually, I made a bit of a mistake which should server as a warning to be careful using sftp. In this instance, WinSCP would have prevented the error but I copied my public key over to the .ssh folder on the web server, but it had the default key name and it overwrote the public key for the user, philip, on the webserver! I have started again with a more organised approach which will help me to avoid mistakes like that. I have created key pairs for three users, developer and philip on the dev machine and philip on the web server. For the key name I have used the username followed by an underscore and then dev on the dev machine and web on the webserver. For example, the philip account on the web server uses the identity philip_web and the philip account on the dev machine uses the identity philip_dev and for any other users that I might want to grant ssh access to I will use the same naming convention.")) article.append(addParagraph("This is significant because I have previously just used the default name for the keys and that changes how you connect which I was forgetting and it gave me a few headaches so I am going to start again from scratch by deleting everything in the .ssh folder and regenerating the keys etc. I will go through this step by step on the web server. So the account name is philip and I want to be able to ssh to the dev machine, connecting via the philip account.")) article.append(addParagraph("I'm on the web server in the .ssh folder (/home/philip/.ssh) so we will start by generating the key pair which we will do with the command")) article.append(addInset("ssh-keygen")) article.append(addParagraph("When prompted for the file where I want to save the key, I responded with")) article.append(addInset("/home/philip/.ssh/philip_web")) article.append(addParagraph("The next step is to transfer the public key over to the dev machine using the ssh-copy-id command which I have used previously but without an identity so that will be a little different. However, I will try without the identitiy first to see what happens if it is omitted. I don't know if you would be likely to have different keys in .ssh but bear in mind that if you also have a key with the default id, the command would work but would not pass over the key that you intended to send!")) article.append(addInset("ssh-copy-id -p 223344 philip_web philip@192.168.0.10")) article.append(addParagraph("I was starting with an empty .ssh folder so I don't have any other keys so this command will fail with the error message")) article.append(addInset("/usr/bin/ssh-copy-id: ERROR: No identities found")) article.append(addParagraph("Essentially, this error message means that the ssh-copy-id command has looked for an identity and didn't find it because it is looking for the default filename. You would also see this error if make a mistake when specifying the identity so you might have the path or the filename wrong or the key may not have the filename you think it has. If you do see the error, check the name of the key in the file, whether it has the default value etc. If you try running the command again, make sure you know the name of the key you want to install on the server. If it is the default, make sure you don't specify the identity or that it is specified correctly. If you have a different filename, just make sure that it is specified correctly - you can use the tolde to specify the home directory.")) article.append(addInset("ssh-copy-id -p 223344 -i /home/philip/.ssh/philip_web philip@192.168.0.10")) article.append(addParagraph("The output from this is shown below.")) article.append(addImageWithCaption("./images/ssh-copy-id1.jpg", "The output we get when installing the public key on the server.")) article.append(addParagraph("We can see both the output from the original command with the identity missing as well as the command where it was specified and the identity was correctly specified. So we saw the 'No identities found' error and they we and it then gives you the response to the command with the identity and it outputs the figngerpribnt of the server which will be added to the known hosts. It tries to log you in with the key and fails. Why does it do that?")) article.append(addParagraph("It does output a message saying that this is to filter out any that are already installed. Essentially, if the log in is successful, this means that the key is already installed. If it isn't, the login will fail because you haven't yet installed the key and you are then prompted to prove that you have a valid login - you are asked for the password for the philip account on the server and when you type that in correctly, the process of installing the key is completed and you can then log in with the command that it provides for you so that will let you know if everything is correct!")) article.append(addParagraph("We can now login with the command")) article.append(addInset("ssh -p 223344 philip@192.168.0.10")) article.append(addParagraph("Remember that when we do sign in, we are passing over our identity (in effect) and the server will try every key it has installed, if necessary, to work out which public key works so that doesn't have to be specified in the ssh command so this gets us onto the server.")) article.append(addParagraph("That's good enough to allow us to ssh without a password but we can make life a little easier by creating a nickname for the server by adding it's details to the config file in our client .ssh folder. I will start that with the command")) article.append(addInset("vim config")) article.append(addParagraph("We do need to specify the identity here so the config file will look something like this.")) article.append(addImageWithCaption("./images/config1.jpg", "The output we get when installing the public key on the server.")) article.append(addParagraph("I gave the server(my dev machine) the nickname philip_dev because I will want to be able to connect to different accounts. I can add another host with the name developer_dev with the same details with the exception of the value for User which I will change to developer, but of course I need to also install the key in the .shh file for that user. So I will copy the key with")) article.append(addInset("ssh-copy-id -p 223344 -i ~/.ssh/philip_web developer@192.168.0.10")) article.append(addParagraph("and I will copy the details for the philip_dev host, change the nickname to developer_dev and the username to developer and save. I can then log in to the dev machine as developer with")) article.append(addInset("ssh developer_dev")) article.append(addParagraph("I can repeat the process on the dev machine to allow me to ssh from there to the web server from either account, although in both cases, I will be logging in as philip on the web server. This has been a bit of a digression but I do think that it is useful for a couple of reasons. First of all is the fact that it acts as a really useful recap because although there is a lot more to ssh than just allowing you to connect to a server without a password, it tends to be central to everything you do with ssh because you are always going to need to connecto to the remote machine.")) article.append(addParagraph("I think that the process of going over this has also illustrated a number of useful points about troubleshooting in general. It demonstrates the fact that on order to get from a point where you are signing in with a password to where you are signing in with an ssh key only can sometimes be quite tricky, there are a number of errors you might encounter so it is important to work in a way that is logical and that you are able to troubleshoot any errors you might have. That means, for example, making sure that you have the proper credentials to allow you to sign in and then you can start setting up your ssh key. You also want to make sure that you can sign in with the ssh key before you think about setting the config to allow you to connect with just the nickname you set up for the server in your config file. Of course, you also want to make sure that you are able to connect to the server without a password before you consider disabling password authentication in the sshd_config file.")) article.append(addParagraph("It is also important, of course, to test the connection at every step to make sure that the security measures you are applying are applied in the way that you think that they are! At the same time, it is important to remember that you may very well want to make sure that we can also connect via any other tools we might be using including kitty (or putty) ad WinSCP.")) article.append(addParagraph("I have used kitty to generate a key pair on my desktop machine. Copying the public key to the servers can be a bit tricky but WinSCP helps with that so I will describe the process there first. Let's start with the private key. I already have a session saved for my connection to the web server because I use it to transfer files to the server on a daily basis so I will start by loading that up, clicking Edit and then clicking on the Advaced button.")) article.append(addImageWithCaption("./images/winscp3.jpg", "The WinSCP Advanced settings.")) article.append(addParagraph("You can generate your key pair here by clicking on the Tools menu and selecting Generate New Key Pair with PuTTYgen and you can save these in your local .ssh folder. You can then point the Private key at the saved key. There are a couple of options for helping you to install the public key on the server and as before, your choices here may be determined by what sort of access you have to the server. For my web server, I have clicked on the Display Public Key button and then the Copy key button. I then signed into the web server via Kitty and pasted it into the Authorizes keys file in the .ssh folder. I can then connect by clicking the New Session tab and double clicking on the Web Server connection and I am connected to the web server without having to type the password.")) article.append(addParagraph("A nice little bonus here is that I only need to open up Kitty, load the web server connection and expanding the SSH options in the left panel, clicking on Auth and then using the Browse button to locate the same private key and I can then connect to the web server via Kitty without a password. I don't need to worry about the public key because I am using the same key pair that WinSCP uses so the public key is already on the server.")) article.append(addParagraph("For the dev server, I want to setup the connection on WinSCP to allow me to login as developer with they key so I have browsed for the private key as before, but I have then selected the option to copy the key to the server which initiates a login and prompts me for the password. When I provide that, they key is installed and I get a box popping up to confirm that.")) article.append(addImageWithCaption("./images/winscp4.jpg", "Confirmation that WinSCP has copied the public key over to the server.")) article.append(addParagraph("I can then click on OK and OK again which takes me back to the edit page for that connection so I can save it and again, I can just point the logins for the dev machine on Kitty to point to that same private key and I can then use it to log in.")) article.append(addParagraph("As I mentioned, testing your connections is critical because you don't want to switch off password authentication on the server and then find that you are unable to connect, especially via Kitty! I have made sure that I can connect via Kitty to both the web and dev machines and although it's not as critical, I did test this for both the developer and philip accounts on the dec machine. I do have other accounts on at least one of the machines but as long as you can connect via at least one user account, that allows you to at least connect to set up logins for other users which is really straightforward once you have set up one account. Also, don't forget that as perhaps a last resort, you can always switch password authentication back on as long as you have one account you can log in with.")) article.append(addParagraph("So do make sure that you always have one account that you can connect with to allow you to troubleshoot if needed. Having ensured that I can ssh between servers and that I can connect to the servers via either kitty or WinSCP using my ssh key, I can now switch off password authentication on both servers. Before I do that, I have backed up the sshd_config file before making any changes (it's always a good idea to do that so you can always recover to a working version of the file if you need to. I then edited the live config file and enabled the PasswordAuthentication and set it to no.)")) article.append(addParagraph("The easiest way to test this is by opening a Kitty session and trying to login with the IP address and the port number (so we are ignoring the saved logins. You can put the account name in front of the IP address or provide it when prompted and also provide the password when prompted. In theory, you shouln't be able to log in but you might if you have forgotten to restart the sshd service, in which case, the update to the sshd_config file won't have taken effect so if you are able to login (as I was!!!), try restarting the sshd service with)")) article.append(addInset("sudo systemctl restart sshd")) article.append(addParagraph("And then try logging in again. If you can still login, you should check your config files to see if there is a setting overriding the sshd_config file or a setting earlier in the config file which invalidates the setting you used. Of course, you also want to ensure that the PasswordAuthenticate directive has been set to no.")) article.append(addParagraph("If you have successfully disabled password authentication, you should see this error.")) article.append(addImageWithCaption("./images/winscp5.jpg", "The error message you should see if you are logging in to a server with a password in order to confirm that the PasswordAuthoenticate directive has been set correctly (and that you remembered to restart the sshd service).")) article.append(addParagraph("So that's a brief overview of sftp. To recap, it is a secure replacment for ftp and it is used in a variety of applications in order to allow files to be trasferred to and form a server. We saw how it was possible to do that from the command line or using a GIU such as WinSCP, but there are lots of other applications that also use it. Many IDEs do and VS Code is an example of that. How you would set that up can vary, which is something we have seen here, so I won't go into any details for specific apps, but be aware that it is possible. On a personal note, it might be handy for me if I could do that with Dreamwwaver which might allow me to sync files with my wev server without having to use ftp which kind of underlines the fact that although I do use Drewamweaver every day, the truth is that I have never really learned to use it properly so it might be worth going through one of the DW courses on LinkedIn (or perhaps just reading the expensive Dreamweaver Clasrtoom in a Book that I bought in February 2021).")) article.append(addParagraph("That leads me to close off this section with a couple of thoughts. Thought number one. The sftp protocol is extremely useful and versatile so learning how it works in general or in terms of a specific application can be extremely beneficial. Thought number two is that the tools we use can be quite powerful but sometimes we use them on a quite superficial level without knowing all of the things that they can do so learning those tools can also be hugely beneficial!")) article.append(addHeader("Transferring Files with SCP")) article.append(addParagraph("SCP is short for secure copy and it is basically the Linux command, cp, but it copies files between systems. Because of that, it is a convenient command for use in scripts or for a quick command to copy a single file and it can also be used in a reverse tunnel to allow you to copy files back from the remote to the local server.")) article.append(addParagraph("To copy a file over to the remote server, you use the scp command followed by the filename and then the destination (username and hostname and a :). Again, I had problems with this because of the port number, although I was able to use the server's nickname so the standard format would be something like")) article.append(addInset("scp file1 philip@192.168.0.10:")) article.append(addParagraph("Using the server's nickname, that would be")) article.append(addInset("scp file1 philip_web:")) article.append(addParagraph("To copy a file from the server to the client, we switch these around a bit and add a filename to copy the file to so")) article.append(addInset("scp philip@192.168.0.20:file1 file3")) article.append(addParagraph("or again, using the nickname")) article.append(addInset("scp developer_dev:file1 file2")) article.append(addImageWithCaption("./images/scp1.jpg", "Example of the SCP command and the directory listing to confirm the file was downloaded.")) article.append(addParagraph("You may have noticed that we added a colon (:) at the end of the remote host and this is important because it allows us to specify the path of the file we want to save to or copy from on the remote machine. In these examples, we used the : on its own which means that if we pass a file over to the remote system, the file will go into the home directory of the specified user and if we download a file, it will be downloaded from the user's home direstory. If we take that last example, we were connecting to the server with the user credentials for developer on the dev machine, so we downloaded the file from that user's home directory.")) article.append(addParagraph("We specified the filename only for the last parameter and so the file was saved in our home directory. It was actually saved to the directory we were in when we executed that scp command which happened to be our home directory. Let's say that we wanted to download it to our Downloads folder instead, there are two ways we could do that. We could specify that directory name in the command, so")) article.append(addParagraph("scp developer_dev:file4 ~/Downloads/file4")) article.append(addParagraph("which will download file4 and place it in the downloads folder. Alternatively, we can specify the filename on its own if we navigate to the Downloads folder first so if I")) article.append(addInset("cd ~/Downloads")) article.append(addParagraph("and then run the command as")) article.append(addParagraph("scp developer_dev:file4 file4")) article.append(addParagraph("again, that will download the file to that Downloads folder.")) article.append(addParagraph("If we want to download a file from a directory other than the home directory on the remote system, I can specify the path after the colon. For example, let's say that I want to copy a file from the Downloads folder in the developer home sirectory on the dev machine. I can do that with")) article.append(addInset("scp devloper_dev:Downloads/file5 file5")) article.append(addParagraph("Here, I am using a relative path starting from the home directory and the file is being downloaded to the current directory because I didn't specify a path for the file location. If I had wanted to, I could use a path for the location to save the file as well.")) article.append(addParagraph("Now, consider this example. Let's say that I want to copy a file that isn't in the user's home directory. As an example, let's assume that I want to download bootstrap.log from the /var/log directory on the dev machine, if that is even possible! Let's assume that it is and run the command")) article.append(addInset("scp developer_dev:/var/log/bootstrap.log bootstrap.log")) article.append(addParagraph("I expected that to fail because I don't have permissions for that file as developer on the dev machine, although I do have read only access so let's try again with boot.log which I have no permissions for.")) article.append(addInset("scp developer_dev:/var/log/boot.log boot.log")) article.append(addParagraph("I do get a permissions error this time. I suspect that running the command as sudo wouldn't give us the appropriate permissions because that would only give root access to the user on the client system, but in any case, it fails with the nickname because that was set up for the user philip and so is not accessible to root. From the dev system, I have tried to upload that file to the web server with")) article.append(addInset("scp philip_web:/var/log/boot.log boot.log")) article.append(addParagraph("and again I get a permissions error. I would expect to get an error if I run this as sudo for the reason mentioned above, but probably will not see a permissions error. The error I do get is")) article.append(addInset("ssh: Could not resolve hostname philip_web: Name or service not known")) article.append(addParagraph("This has illustrated two important points about scp. Notice that we specified the path as an absolute path - /var/log/ - so that didn't look in the home directory and this allows us to download a file from anywhere on the remote system. However, we do need to have permissions to access the file in order to copy it and read is sufficient for that.")) article.append(addParagraph("You can, in theory, omit the colon but if you do that, the string you used to specify the host will be treated as a filename and can give you unexpected results. For example, let's say that I want to copy a filename called rename from my dev machine to the web server and that to do that, I am logged in as developer on the dev machine. The correct way to do that is with the command")) article.append(addInset("scp rename philip_web:")) article.append(addParagraph("which will upload to the home directory for philip on the web server. If I try that command again but without the colon, it does seem to work, but nothing happens. You might say that without the colon, the results are undefined so we might have nothing happen, as it did here, or we might see some unexpected result such as the file being renamed as philip_web rather than being copied over to the web server.")) article.append(addParagraph("As we said before, the scp command is probably not the best way to transfer files, particularly if you are copying large numbers of files. It does more or less the same things as sftp or even a tool like WinSCP, but it does it in a slightly different way and so the tool you choose for a particular task will be detmerined by what the task is and which tool is better suited to it. In some cases, for example a one-off file copy, scp can be the best tool to choose so it is useful to know about it and to be able to use it.")) article.append(addHeader("Multi-Step SSH Connections")) article.append(addParagraph("Most of the time, when you connect to an ssh server, this is a direct connection from the client machine to the server. In some cases, however, an origanisation might want to control access to machines in the network by using a server that acts as a gateway.")) article.append(addParagraph("This is known as a jump host or a bastion host and is often used in large organisations because it allows you to employ heavy security on one machine which users will have to connect to before connecting to other machines. It also means, of course, that you don't need the other machines to be as secure. It can be on the same network as the machine that you want to connect to, but it doesn't have to be.")) article.append(addParagraph("Let's say that we are on a client machine, we want to connect to a server but we don't have access to it directly, we have to connect via an intermediary.")) article.append(addParagraph("There two ways to do that and the simplest is just the same way in which we have been connected so far, just repeated.")) article.append(addParagraph("We would do that in two steps. First, we connect to the intermdeiary via ssh. We may not have access to do very much on that intermediary machine, but from there, we can ssh to the server.")) article.append(addParagraph("Alternatively, we can connect from the client to the server in one step by sing the -J option which allows us to specify one or more intermediate hosts so the syntax for that ould be:")) article.append(addParagraph("ssh intermediary1,intermdiary2 server")) article.append(addParagraph("For example, let's say that I want to connect from my web server to my dev machine via a copuple of other hosts, the command might look like this:")) article.append(addInset("ssh -J philip@192.168.0.5,philip@192.168.0.15 philip@192.168.0.20")) article.append(addParagraph("Notice that there is a clear distinction between the intermediary hosts and the taget server. The intermdiary hosts are separated by a comma and there are no spaces between them. The target server is separated from the list of intermediaries by a space. Depending on how the hosts are configured for authentication, we might have to authenticae at each step so, for example, if the hosts allow password authentication, we might be prompted for a password at each step.")) article.append(addParagraph("One advatage of this approach is that we only need to store our private key on our own server, it doesn't need to be stored on each intermediate. Consider this, if we want to connect to each separately, we would do it like this. First, we would ssh to 192.168.0.5 with the command")) article.append(addInset("ssh philip@192.168.0.5")) article.append(addInset("If we are connecting via an ssh key, we are using the private key on our own server and we connect using the public key stored in the first intermediary. For the second step, we will use a similar command.")) article.append(addInset("ssh philip@192.168.0.15")) article.append(addParagraph("But this won't work if our private key is not also stored on this server or we have another private key saved there, which would mean a different public key stored on the second intermediary. For the last step")) article.append(addInset("ssh philip@192.168.0.20")) article.append(addParagraph("we again need to have a private key on the intermediary and a public key on the target server. Essentially, each connection will need a key pair so we might use the same key pair for every connection and we might not, but we do have to have a private key stored on them. If we use the -J option, we would need our private key stored on the original server and a public key stored on each of the intermediaries and the target server. In this case, we are using the same key pair in each step, but the private key is being passed along so we only need to store it on the web server and we can then install the public key on the machines we will connect to along the way including the dev machine - the target server.")) article.append(addParagraph("Whichever method we use, the overall result will be the same. We will be connected to the target server as though we had connected to it directly via ssh. We will be able to access whatever resoources on the machine we have permissions for but we will not have any access to the intermediary hosts but it is wirth remembering that a host can act as both an intermediate and a target machine. For instance, one of the hosts in my example might be a media server so I may be able to connect to it, do some work on the machine and then ssh over to the dev machine and do some work there. However, once I have connected to the dev machine, that is the only machine I have access to.")) article.append(addParagraph("We can set up a connection via one or more intermediates in our config file within the .ssh folder which you might want to do if you have a complicate route to follow. To give you an example of this. If I sign in to my dev machine with the username, philip, this is config file in the .ssh directory for that account.")) article.append(addImageWithCaption("./images/proxy1.jpg", "The config file in the .ssh folder for user philip on my dev machine.")) article.append(addParagraph("Let's say that I want to setup another connection to a machine I don't have access to, but that the web machine shown in this config file does. I can set up a connection to it using the ProxyJump directive so the config file would look like this,")) article.append(addImageWithCaption("./images/proxy2.jpg", "The config file set up to allow me to connect to another machine via the web server.")) article.append(addParagraph("I can then connect to server 2 via the command")) article.append(addParagraph("ssh server2")) article.append(addParagraph("Again, I would have to authenticate on the web server (with a password if that is how it accepts connections but I am using an ssh key) and would then have to authenticate on server 2 using an ssh key (if that is set up) or a password pr whatever form of authentication that is required to connect to server 2.")) article.append(addParagraph("For the sort of setup I have at home, it's unlikely that I would want to use a bastion host but if I had more servers, I could set that up if I wanted to. Ishave encountered them in a work situation where I might be required to connect to a couple of different hosts set up for different purposes. In my experience, it is more common to be able to connect to whichever server I need to access but if a client hosts their own servers, it is probably more common to only be able to connect directly to one host and I can then connect from that to other hosts on the client's network as required.")) article.append(addHeader("Port Forwarding with SSH")) article.append(addParagraph("One of the faclilities offered by OpenSSH is port forwarding which allows us to send different types of data such as web pages from one system to another. A typical use case would be in a scenario where you are hosting a web server on a home network. The web server typically will have a p private IP address but it is made available to the public via your public IP address. In my case, the router, which handles all incoming data is set up to forward any requests on port 80 to the web server so this works as if the public IP address was the address of the system. If I open a web browser and type my public IP address, I get the web page that is being server up by the web server.")) article.append(addParagraph("Just a reminder, there are a number of ports which are reserved as the default values for certain services such as port 22 for SSH (as we have seen earlier), ports 80 for HTTP or port 443 for HTTPS. Any service that runs over the web will use a port in order to communicate over the network.")) article.append(addParagraph("We can configure port forwarding to have data sent from our local system to the remote system or from the renote system to our local system so essentially we can set up a port on one system to accept data sent to the other and this data is sent securely using ssh. There are 3 ways to do this.")) article.append(addInset("ssh -L [bind_addr:]port:host:port")) article.append(addParagraph("This is local port forwarding so this would send data that comes into the specified port to a port on the remote system.")) article.append(addInset("ssh -R [bind_addr:]port:host:port")) article.append(addParagraph("This is remote port forwarding so the remote system would send any data that it receives on the specified port to our local system.")) article.append(addInset("ssh -L [bind_addr:]port:host:port")) article.append(addParagraph("Dynamic port forward is a little bit more complicated but we are going to look at each in turn so I will leave that for now. One interesting point is that the IP address is optional and this is the IP address of the local machine. We might have to specify an IP address if we have more than network interface, this option tells the system which IP address we want to bind the tunnel to. If it is omitted, ssh will be able to use any available address. We might also specify an IP address for the remote host but we can also use an alias or some other form of hostname.")) article.append(addHeader("Local Port Forwarding")) article.append(addParagraph("With local port forwarding, we are specifying a port on our local host that sends traffic to a port on the remote system and this allows us to use some service on the remote system as if we were using the server that actually provides that service. An example of that might be where we need to access a database on server that is running on a private network. Let's say we have a host called server1 with a database running on port 3306 and we want to make this available on our local server via port 3333. We could set that up with the command:")) article.append(addInset("ssh -L 3333:localhost:3306 user@server1")) article.append(addParagraph("We can then use port 3333 to access the service although I experimented with this on my hoem network where I tried to access mysql which I have running on my webserver on my dev machine (which doesn't have mysql). I first ran the command")) article.append(addInset("ssh -L 3333:localhost:3306 philip_web")) article.append(addParagraph("but this just had the effect of ssh'ing to the web server. I also tried it in a format more like that in the course video")) article.append(addInset("ssh -L 3333:localhost:3306 philip@192.168.0.20")) article.append(addParagraph("but this time, I got an error due to the fact that it was trying to connect me via the default ssh port. It's possible that this works differently on a Raspberry Pi but it looks as though the -L option is being ignored and I am simply being connected via ssh. However, I checked the man page for ssh and the -L option is shown there with the following entry.")) article.append(addImageWithCaption("./images/proxy3.jpg", "The -L option in the man page for ssh.")) article.append(addParagraph("Connecting to a database isn't really the topic of this course but it would be an interesting way to demonstrate that it works so hopefully this will become clearer before the end of the course but if not, it will become a topic to investigate later.")) article.append(addParagraph("In any case, the effect of this should be that we can access the database as if it were running on our local machine, in my case, the dev machine. Note that the localhost in the command refers to the remote server and as I mentioned earlier, we can bind this to a specific network interface if there were more than one available.")) article.append(addParagraph("In our example, we were forwarding a port from our dev machine which acted as the ssh server and is forwarding the service, so to speak, on to the web machine which suggests that I was running this command the wrong way round and should actually have run it from the web machine but when I tried that, the effect was the same!")) article.append(addParagraph("It might be helpful to work through the course example and see what is happening there so we will start with the command")) article.append(addInset("ssh -L 8080:localhost:80 scott@10.0.1.110")) article.append(addParagraph("The interesting part here is probably")) article.append(addInset("8080:localhost:80")) article.append(addParagraph("and understanding this is key to understanding the concept of port forwarding so let's break it down into its three parts.")) article.append(addInset("8080")) article.append(addParagraph("This is the port I want to open up on my local machine.")) article.append(addInset("localhost")) article.append(addParagraph("This is from the perspective of the server so essentially it is localhost on the server.")) article.append(addInset("80")) article.append(addParagraph("This is the port we want to open on the server and remember, the server is a web server so it means that we want traffic going to the web server to be forward to port 8080 on the local machine.")) article.append(addParagraph("Now, if you go yo the local machine, you should be able to type localhost:8080 in a browser URL and the website being server up by the browser should be available there. Following that logic, I should be able to make my website available on my dev machine in the same way so to do that, I will log into the dev machine and run the command")) article.append(addInset("ssh -L 8080:localhost:80 philip_web")) article.append(addParagraph("The result of doing that is shown in the image below.")) article.append(addImageWithCaption("./images/local_port_forwarding1.jpg", "RDP to my dev machine and viewing the default page from my web server at localhost:8080")) article.append(addParagraph("That seems to work perfectly well which demonstrates the fact that web traffic is being forwarded from port 80 on my web server to port 8080 on my dev machine. I guess that what's happening is that when I go to localhost 8080, since we have the port forwarding in place, the browser is going to port 80 on the web server to get the page.")) article.append(addParagraph("To me, this sounds like remote port forwarding since traffic is being forwarded by th web server to the local machine but this is probably because of a misunderstanding of what was meant be local port forwarding. Based on this, my interpretation of the command would be that we are, from the dev machine, asking the web server to forward the traffic on so we are actually setting up local port forwarding up on the web server. In other words, the command isn't to allow us to forward traffic onto another machine, it is to allow another machine to forward traffic on to us. I guess that if we wanted to configure the remote machine to accept traffic from our local machine, this would be remote port forwarding which we will see in the next section.")) article.append(addParagraph("In essence, what we are doing with local port forwarding is to open up an ssh connection to the machine whilest at the same time, connecting the port (in this example 80) on the machine we ssh'ed to over to port 8080 on the originating machine so that is probably why we consider that to be local port forwarding. The port forwarding will remain active as long as that ssh connection remains open so if we exit to go back to the original machine, traffic will no longer be forwarded and we will no longer be able to view the web page from the web server using local host.")) article.append(addParagraph("Remember that when we ssh'ed from one machine to another, we were able to do that either directly or via an intermediary host, and with port forwarding, we can use a similar method. Before I do that, I want to be able to check that it works correctly. I have added a third Raspberry Pi to my active network, it doesn't have any specific purpose at the moment so I just refer to it as my Pi3 since it is the only Raspberry Pi 3 I currently have active. I will start by ssh'ing over to the Pi3 and using the command")) article.append(addInset("ssh -L 8080:localhost:80 philip_web")) article.append(addParagraph("to set up port forwarding from port 80 on the web server to port 8080 on the Pi3, just as I did previously on the dev machine and the result is the same. When I type in localhoust:80 in a browser, I get the webpage in a browser on my Pi3. I'm now going to amend the command so that it routes the web traffic through the development machine so the command becomes")) article.append(addInset("ssh -L 8080:localhost:80 philip_web")) article.append(addParagraph("Again, I will confirm this worked by showing a screenshot of the desktop on the Pi3 with the browser pointing to localhost:80.")) article.append(addImageWithCaption("./images/local_port_forwarding2.jpg", "RDP to my Pi3 and viewing the default page from my web server at localhost:8080 with the web traffic being routed via the dev machine.")) article.append(addHeader("Remote Port Forwarding ")) article.append(addParagraph("As we mentioned earlier, remote port forwarding is essentially the same as local port forwarding but in reverse and it can be useful when we have already established an ssh connection to allow, for example, copying files. This is also sometimes referred to as a reverse tunnel since it creates a tunnel from the remote system to the local system. Let's look at an example of that.")) article.append(addInset("ssh -R 5432:localhost:22 user@server1")) article.append(addParagraph("This has the effect of opening up port 5432 on the remote system and forwarding it to port 22 on the local system. To experiment with this, I am going to try forwarding web traffic from my web server to my dev server. Just as a reminder, I previously did that by going to the dev server and running the command")) article.append(addInset("ssh -L 8080:localhost:80 philip_web")) article.append(addParagraph("The effect of this was that I had connected to the remote server, the web server, which therefore became the local server and this began to forward traffic from the web server to the remote server (which is mow the dev machine) and so in a remote desktop connected to the dev machine, I was able to access the web site server up by my web server by going to the URL")) article.append(addInset("localost:8080")) article.append(addParagraph("The goal here is to forward traffic from the remote server to the local machine, so we want to be ssh'ed into the local machine and then have the web traffic follow - that is, the web traffic should also be forwarded from the web server to the local machine so this time, we will start off on the web server and run the command")) article.append(addInset("ssh -R 8080:localhost:80 philip_dev")) article.append(addParagraph("To confirm whether that worked or not, here is the browser on my dev machine pointing to localhost:8080.")) article.append(addImageWithCaption("./images/remote_port_forwarding1.jpg", "RDP to my dev machine and viewing the default page from my web server at localhost:8080")) article.append(addParagraph("So that did work. The only point I find a little confusing is that according to the course video, the ssh command should forward traffic on to port 80 from port 8080 on the local machine and if that were the case, I should be able to view the web site with just localhost or localhost:80 but I have to specify port 8080. Also, the traffic we want to forward should be coming from port 80 and this suggests that this has been described the wrong way round in the video.")) article.append(addParagraph("In any case, you might think that this is largely pointless since much of what we have seen in these examples is really resundant. For example, we can use local port forwarding to send the web traffic from our web server to the dev machine so you might question whether there is any benefit to trying to figure out how remote port forwarding works and yes, it might be advantageous to set up the reverse tunnel so we can work on our dev machine while forwarding the traffic. However, we could also do that using local port forwarding and then opening a second session to work on the dev machine.")) article.append(addParagraph("Maybe even more relevant is the fact that we can access the webpage on our dev system without forwarding the web traffic just by using the correct url - in this case osztromok.com in our browser. I think that the important thing to remember here is that there may be some instances where it is useful to be able to forward traffic on to a port on your local machine and you do have an ssh connection available, so these tools are really just taking advantage of the fact that the ssh connection is available. At this point, I'm not really all that sure about how this might be useful to me but all of the machines I need to work on are easily accessible, but I can imagine that there may be some situations where these tools may be useful.")) article.append(addHeader("Dynamic Port Forwarding")) article.append(addParagraph("Dynamic port forwarding works by creating a SOCKS proxy on the local system which we can use to send traffic out via a port on the local system to an ssh server and that traffic can then be forwarded on to any machine that can access the ssh server. Essentially, it allows you to use a port on your own system to act as a proxy. This is often useful for web browsing when you don't want your local system to be exposed to the internet.")) article.append(addParagraph("As an example, I will set up a dynamic proxy between my dev machine and my web server so that traffic is forwarded from my webserver to the dev machine. The syntax is pretty similar to that for local port forwarding so I start on the web server and execute the command")) article.append(addInset("ssh -D 3000 philip_dev")) article.append(addParagraph("In order to use this, we need to configure the system or the browser to do that. For example, to do that in Firefox, we want to go to the settings and look for Network Settings. From there, we will switch over to Manual proxy configuration, insert localhost as the SOCKS Host and 3000 as the Port as shown below.")) article.append(addImageWithCaption("./images/dynamic_port_forwarding1.jpg", "Configuring the network settings in Firefox to work with the SOCKS proxy.")) article.append(addParagraph("We can then browse the internet using the proxy as shown below.")) article.append(addImageWithCaption("./images/dynamic_port_forwarding2.jpg", "Browsing the internet via the SOCKS proxy.")) article.append(addParagraph("To confirm that we are browsing the internet via the proxy, I will close the ssh session to stop the port forwarding and we should then see that the LinkedIn homepage is no longer reachable and the result is shown below.")) article.append(addImageWithCaption("./images/dynamic_port_forwarding3.jpg", "Browsing the internet via the SOCKS proxy after closing the ssh session which stops port forwarding.")) article.append(addParagraph("Once the ssh session has been closed, if Firefox is still configured to use the proxy, we won't be able to browse the internet unless we resinstate port forwarding or we set the proxy connection settings in Firefox back to no proxy.")) article.append(addHeader("Options to Use with Port Forwarding")) article.append(addParagraph("In the examples we have seen so far, when we set up port forwarding using ssh, we opened a new session on the remote server. For example, the command")) article.append(addInset("ssh -L 8080:localhost:80 philip_web")) article.append(addParagraph("not only set up the port forwarding from the web server to my local machine, it also connected us to the web server. This is because the process, as is normal with the ssh command, opens up a new shell session. However, we don't actually have to do that. There are some useful options we can use with port forwarding and these are")) article.append(addInsetBulletList(["-f - this forks the ssh process into the background.", "-n - this prevents reading from stdin", "-N - this prevents remote commands from running", "-T - this prevents a TTY from being allocated."])) article.append(addParagraph("The options, -nNT, work together to prevent a new shell from being opened and the -f option puts the ssh process into the background so that we can continute to work in the existing shell. To demonstrate that, we will use the command again to set up local port forwarding from the web server to the dev machine. The screenshot below shows the ip address, 192.168.0.10, which is the address of my dev machine, the ssh command that sets up the local port forwarding (note that I am forwarding to port 80801 this time) with these options and the ip address again which is still 192.168.0.10 showing that we haven't opened up a new shell process.")) article.append(addImageWithCaption("./images/local_port_forwarding4.jpg", "Setting up local port forwarding using the options -fnNT.")) article.append(addParagraph("Back on the dev machine, I can then put localhost:8081 into the browser's address bar and the result is shown below.")) article.append(addImageWithCaption("./images/local_port_forwarding5.jpg", "Browsing the internet via local port forwarding on port 8081.")) article.append(addParagraph("Since the ssh process has gone into the background, closing the ssh connection will not stop the port forwarding since we put that process into the background which means that it will continue to run even after the ssh session we ran it from is closed. We can check that by closing the terminal and then going back to the browser on the dev machine to check if we can still see the web page with localhost:8081 and we can.")) article.append(addParagraph("We can also confirm the process is still running by opening a terminal on the dev machine and using the ps command like this")) article.append(addInset("ps x | grep ssh")) article.append(addParagraph("The output from this is shown below.")) article.append(addImageWithCaption("./images/local_port_forwarding6.jpg", "The output from running 'ps x | grep ssh' on the dev machine.")) article.append(addParagraph("This shows that traffic is still being forwarded from port 80 on the web server to port 8081 on the dev machine and it also gives us the process id which is 63853 in this case. To close the connection, we will need to kill that process with the command")) article.append(addInset("kill 63853")) article.append(addParagraph("and we can then refresh the browser on the dev machine to confirm that the web traffic is no longer being forwarded to port 8081.")) article.append(addParagraph("We can add these port forwarding options to our config file in the .ssh folder. As an example, one of the entries in my config file for user philip on the dev machine specifies a host called philip_web which allows me to easily connecting to the web server using an ssh key and it looks like this.")) article.append(addInsetList(["Host philip_web", " Hostname 192.168.0.20, ", " User philip", " Port 22", " IdentityFile /home/philip/.ssh/philip_dev"])) article.append(addParagraph("I will add another host with the name local-port-forward which I will use to set up local port forwarding from the web server to the dev machine so it will look like this.")) article.append(addInsetList(["Host local-port-forward", " Hostname 192.168.0.20, ", " User philip", " Port 22", " LocalForward localhost:8080 localhost:80", " IdentityFile /home/philip/.ssh/philip_dev"])) article.append(addParagraph("This is different to the syntax given in the course video so I am amending the LocalForward directive as shown and I will also add a couple of other entries for remote forwarding to demonstrate the syntax for those.")) article.append(addParagraph("One thing I discovered when I added the entry in the config file as shown in the course video is that if there is an error in the config file, none of the entries will work so I got an error when I tried to connect with local forwarding which pointed to the line that I had to change. However, I also noticed that when I tried to connect to any other host via the configuration in the config file, I got the same error so an error in the config file (at least this error) will affect all of the configured connections. The fact that open ssh gives you a nice and clear error message is really helpful and it made it very easy for me to find a solution on the internet.")) article.append(addParagraph("I also added options in the config file for remote and dynamic port forwarding and these are the same as the config section for with the exceptions of the connections nickname and the actual line that configres the forwarding so for remote port forwarding, it looks like this.")) article.append(addInsetList(["Host remote-port-forward", " Hostname 192.168.0.20, ", " User philip", " Port 22", " RemoteForward localhost:8080 localhost:80", " IdentityFile /home/philip/.ssh/philip_dev"])) article.append(addParagraph("and for dynamic port forwarding it looks like this")) article.append(addInsetList(["Host dynamic-port-forward", " Hostname 192.168.0.20, ", " User philip", " Port 22", " DynamicForward 3000", " IdentityFile /home/philip/.ssh/philip_dev"])) article.append(addHeader("Tools That Use and Extend SSH")) article.append(addParagraph("For some tools, being able to use a secure ssh connection can be very useful since it allows them to offer features such as file copy with the protection offered by your ssh connection. A good example of this is WinSCP which essentially provides you with a graphical interface for manipulating files on a server via an ssh connection.")) article.append(addSubHeader("mosh")) article.append(addParagraph("mosh is an example of a tool that extends ssh. It works pretty much like and uses ssh but it gives you a fault-tolerant connection. It has to be installed on both the client and the server and you can do that on most Linux systems with a command like")) article.append(addInset("sudo apt install mosh")) article.append(addParagraph("We also need to open a port up for mosh to use - it uses UDP ports in the 60,000 range and we can open one port or a ranger of ports. To open one, we would use a command like")) article.append(addInset("sudo ufw allow 60001/udp")) article.append(addParagraph("The mosh command is essentially the same command as ssh but unlike ssh, if you are running a command in the shell you have connected to and the connection goes down, the command will keep running. When you mkae the connection using mosh, you might notice something unusual compared to using ssh. Consider the following image.")) article.append(addImageWithCaption("./images/mosh1.jpg", "Connecting to the web server with the ssh command.")) article.append(addParagraph("In this case, I have opened a kitty session with a connection to my dev machine and I have then used ssh to connect to the web server. I have then connected to the web server via ssh. This looks fairly normal so I won't comment on it until we see what happens when I do the same thing but using the mosh command.")) article.append(addImageWithCaption("./images/mosh2.jpg", "Connecting to the web server with the mosh command.")) article.append(addParagraph("As you can see, this looks exactly pretty much what you see when you just open up a kitty session so I am using ssh to connect via kitty to the dev machine but not connecting to any other server. For reference, this looks like")) article.append(addImageWithCaption("./images/mosh3.jpg", "Connecting to the dev machine with kitty - for comparison.")) article.append(addParagraph("There doesn't seem to be much difference between connecting directly to a server via kitty or connecting via mosh aside from a couple of lines generated by kitty. Perhaps more significantly, it shows [mosh] in the title bar and this is similar to what we see when opening a screen. Given that mosh preforms a similar function to screen, I suspect that it is essentially opening a screen when you make the connection so it is kind of equivalent to connecting via ssh and then opening a screen.")) article.append(addParagraph("It doesn't actually use or require the screen command because you can use mosh even of you don't have screen installed. Nevertheless, mosh is still effectively using a screen so I guess it has that functionality built in and I think that it does make sense to think of mosh as being a combination of ssh and screen.")) article.append(addSubHeader("blink")) article.append(addParagraph("blink is an ssh client for iPad and it uses mosh so it gives you not only a secure connection, but also will allow you to reconnect if you do have a dropped connection without stoppoing whatever processes were running. I do, on occassion, connect to my web server from my iPad but only for small and quick tasks such as quickly editing a file or checking its status so that's not really critical for me and I use a simple client called Raspberry Pi Helper which is perfectly sufficuent for me. I believe blink offers free and subscription options but I don't have any reason to consider subscribing. For reference, you can click here for more information on blink and there are a bumver of YouTube videos relating to Raspberry Pi Helper.")) article.append(addParagraph("However, if you do need to connect to a server via ssh, tools kike mosh and blink can be very useful to have and this is particularly true if you are working with an unreliable internet connection.")) article.append(addSubHeader("rsync")) article.append(addParagraph("rsync can be used to either copy or sync files both remotely and locally. It has it's own security mechanisms but it can use ssh which might be useful in simplifying the admin on a system. The course video only mentions it briefly, but it is interesting for me because it may be simpler for me to be able to sync the web site files I copy to the server with the live files so it is worth digging a little bit more into it.")) article.append(addParagraph("There is a useful article on this topic on TecMint entitled 16 Rsync Command Examples for Efficient File Synchronization. This is by Tarunika Shrivastava and it was updated as recently as July 18 2023 which at time of writing this is around 6 weeks ago. This provides some useful background to the command and covers different options. As the title suggests, it does also provide 16 different examples of using the command and the first one I am interested in is a slight digression from the topic of this course because it doesn't involve ssh. It shows how to sync two directories on one machine. The general format for the syntax for that is")) article.append(addInset("rsync -azvh /source_folder /destination_folder")) article.append(addParagraph("For reference, the command I usually use to copy the files either manually or via cron is")) article.append(addInset("logger Starting to copy files for website && cp -Rf /home/philip/FTP/website/* /var/www/html/ && logger Finished copying the files for website")) article.append(addParagraph("In my case, the command I need to run rsync would be")) article.append(addInset("rsync -azvh /home/philip/FTP/website/ /var/www/html/")) article.append(addParagraph("To test it, I will save this file in Dreamweaver. I have disabled the copy command in cron by commenting it out and checked in the logs - /var/log/sysfile to ensure that it is't already running. Now, if I save this file, WinSCP will copy the file over to the source directory and rsync should copy the file over to the live site so I should be able to refresh the page and see the changes. This is what the website looks like (this is the bottom of the file before I run rsync)")) article.append(addImageWithCaption("./images/rsync1.jpg", "The bottom of the web page before running rsync - this will be the last line of the page at that point.")) article.append(addParagraph("I am now adding this paragraph and will run rsync and refresh the page. Notivr that the previous image shows a missing image file so you can only see the caption and this is because I took the screenshot and then saved the file. As a result, when I run rsync, I am expecting that it will update this file and also copy across the image. If it works, the following image will look similar but the image will no be missing and you will also see this paragraph.")) article.append(addImageWithCaption("./images/rsync2.jpg", "The bottom of the web page after running rsync - this will be the last line of the page at that point.")) article.append(addHeader("Securing an SSH Server")) article.append(addParagraph("Obviously, security is very important on an ssh server, so there are a few things you should consider, most of which are configurable in /etc/ssh/sshd_config.")) article.append(addInset("Permit Root Login")) article.append(addParagraph("The ability to login as root is a vulnerability because if an attacker was able to do that, they would have full access to your server, so it's probably a good idea to disable it. You would do that by setting the value of PermitRootLogin to no. In most cases, there will be no need to login as root, but if there is, you could also set this to prohibit-password which would allow you to set up a login for root with an ssh key. On both my web server and dev machines, this directive was commented out so I have enabled them and set the value to no.")) article.append(addInset("Password Authentication")) article.append(addParagraph("If possible, you should also prevent other users from logging in with a password. This is done with the PasswordAuthentication directive which I have already set to no on both my machines, so it is no longer possible to log into a user account with a password. It should be quite obvious, but it is very important to ensure that you can log in without a password, in my case I have set up ssh keys, before you set this to no. You should also ensure that PubkeyAuthentication is set to yes.")) article.append(addInset("Port")) article.append(addParagraph("Changing the port number used to ssh to a server can make it a little bit harder for someone to hack into it, but it's not as effective as it used to be.")) article.append(addInset("Encryption Method")) article.append(addParagraph("In the sshd_config file, you can configure the server's encrtpytion method for greater security but that is not covered in this course.")) article.append(addInset("User Control")) article.append(addParagraph("This is not really an issue for me since I am the sole user on my servers but you can use this to control who can or cannot access a server using DenyUser, AllowUser, DenyGroup and AllowGroup directives.")) article.append(addInset("Fail2Ban")) article.append(addParagraph("Fail2ban can help to protect you against brute-force attacks so you may want to consider installing it if you know that your server is constantly seeing attempted logins from an unknown source. It can ban connections from an ip address that makes too many failed login attempts.")) article.append(addInset("Bastion Host")) article.append(addParagraph("If you have a number of servers you want to protect, you might want to consider setting up a bastion host. This doesn't necessarily make your servers more secure, but it simplifies security management by ensuring that you only have to safeguard one system and you can then prevent logins from outside your network on other servers.")) article.append(addInset("VPN")) article.append(addParagraph("You can put your server behind a VPN if you want to ensure only authorised users can access it. This has a number of advantages, particularly in that it will prevent things like brute-force attacks. Note the wording here, it doesn't protect against brute-force attacks, it prevents them altogether. While this type of attack will almost always fail if your server is secured with, for example, ssh key authentication with password logins disabled, it still generates both traffic and logs so there can be overheads even if an attack doesn't succeed.")) article.append(addInset("Fingerprint")) article.append(addParagraph("Strictly speaking, this is not something that makes your server more secure, but if you provide users with the servers fingerprint, tihs can help them to avoid inadvertently logging into a machine that is impersonating your server. You my be able to get the key by using the command, ssh-keyscan with the servers ip-address but I got a connection refused message when I tried it. This isn't important for me since I only login to my servers from within the network, but if you provide the key to a user, they can add it to their known-hosts file so they don't need to rely on getting that from the server when they log in for the first time.")) article.append(addInset("Organizational Requirements")) article.append(addParagraph("Again, this is a non-issue for me since my servers are owned by me, but if you are securing your ssh servers on behalf of an organization, you may have to take account of that organizations policy with regard to security. For example, there may be a policy that prohibits the use of passwords for logging in to some or all servers.")) article.append(addHeader("Troubleshooting SSH")) article.append(addParagraph("If you can't ssh to a server, the first thing you will want to check is whether the ssh server software is running on that machine and you can do that with systemctl. For example, we can check the status with")) article.append(addInset("systemctl status sshd")) article.append(addParagraph("With luck, the output should look something like this, showing that the serviuce is active and running.")) article.append(addImageWithCaption("./images/systemctl.jpg", "The output from checking the status of the ssh server software with systemctl.")) article.append(addParagraph("If necessary, we can also use the systemctl command to start or restart the ssh server.")) article.append(addParagraph("If the service is being prevented from starting, we can use the journalctl command to check that and we can also searxh specifically for issues with ssh with the command")) article.append(addInset("journalctl -u ssh")) article.append(addParagraph("The journalctl using something like less to provide output so you can navigate through it with the arrow or page up/down keys and quit with q. You can also pipe the output to grep or redirect it to a file if you need to do more analysis. For example, let's say we want to filter for a specific ip address, say 192.168.0.5 (which for me is my Windows machine) and put the output into a file called journalctl.txt, the command for that would be")) article.append(addInset("journalctl -u ssh | grep 192.168.0.5 > ~/journalctl.txt")) article.append(addParagraph("A common source of errors with ssh is typos in the shhd_config file. When you make changes to this file, you need to restart the ssh server for the changes to take effect so it is a good idea to check the status after doing that in case of any problems. Personally, I also like to take a backup of any file I make changes to so I can easily return to the previous version of the file if needed.")) article.append(addParagraph("If you are seeing issues with users connecting with ssh, but you can see that the service is running, you should check the firewall to make sure traffic on that part is permitted and you can do that with the command")) article.append(addInset("sudo ufw status")) article.append(addParagraph("Depending on which Linux OS you are using and which firewall software is running, this command may change. It does work on the Raspberry Pi but isn't installed by default. As far as I know, there is no firewall. I just installed ufw on my web server but I don't have it configured yet so if I run the status command I just get back status: inactive. If you are using it, you should see something like this in the output.")) article.append(addImageWithCaption("./images/ufw.jpg", "Sample output from the command - sudo ufw status")) article.append(addParagraph("If the port is blocked for some reason, you can open it using the appropriate commands for that firewall software. For ufw, you can check the man page for more information or you can find plenty of info with a Google search!")) article.append(addParagraph("You might also want to check the user's account if a specific individual is having problems connecting. for exapmle, does the user account exist, is it blocked by a block user or block group directive or is it locked. To check if a user account is locked, you can a command like")) article.append(addInset("sudo cat /etc/shadow | grep philip")) article.append(addParagraph("If the account exists, you should see something like this.")) article.append(addInset("philip:$5$bzMIb5soxP$e.sT/3BKVaIS/kasnxrqq/VrlYrt6EV2kORWUhjkX/2:19257:0:99999:7:::")) article.append(addParagraph("The long string of characters after the first colon starts with a $ sign and this is the user's hashed password. If the account is locked, this will start with an excalamtion mark")) article.append(addParagraph("If you know that the user's account should not be locked, you can unlock it with")) article.append(addInset("sudo usermod -U philip")) article.append(addParagraph("If a user account is locked, it's probably been locked for a good reason so you should be careful about using the unlock command if you are not sure whether or not that account should be locked.")) article.append(addParagraph("You may also want to check the users authorisation method. How are they trying to login, is that authentication method permitted and are the credentials correct. That means checking their password, perhaps updating it and checking whether the correct key has been stored for that user and is still in place if the user is signing in with an ssh key.")) article.append(addParagraph("A user will see a warning message if they try to sign in to a server whose fingerprint is not stored in the kmown hosts file. This is something that should always be invesigated. It can be a result of a server rebuild or it could be an indication of a man-in-the-middle attack where another machine is intercepting traffic intended for your server (hence the fingerprint not being recognised).")) article.append(addParagraph("Other things you can check when there is a problem are the clients ssh configuration, either at machine level in config_ssh or at an individual level in the config file in the users .ssh directory.")) article.append(addParagraph("A connection can also go down while a client is connected and this will usually lead to the client seeing a broken pipe error. As well as the obvious problems (server shotdown,restarted or crashed) this can also happen if the server goes to 'sleep' so if you want a client to have continual access, you may need to configure power settings on the machine. From the clients point of view, if this happens regularly, it might be worth considering something like mosh for a fault-tolerant connection that can be resumed when a connection is available again.")) article.append(addHeader("Conclusion")) article.append(addParagraph("If you want to improve your ssh skills, a good way to do that is to setp up a small lab you can experiment with. As well as my Windows machine, I also have three Raspberry Pis and these can be great for tring out ssh commands. Having three means that I can not only ssh between them but I can also set up one to forward traffic to another, for example if I want to experiment with a bastion host. In addition, it's a good idea to check out the man page for ssh and the config files, ssh_config and sshd_config.")) article.append(addParagraph("A good follow up course to this one might be Learning VPN which is also authored by Scott or Linux:Desktops and Remote Access by Grant McWilliams.")) main.append(menu("nomenu"))