You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
42 KiB
170 lines
42 KiB
import { addBanner, addArticle, addTitle, addHeader, addParagraph, addSubHeader } from '/scripts/article.js'; |
|
import { addInset, addInsetList, addInsetCodeListing, addInsetBulletList } from '/scripts/inset.js'; |
|
import { addImageWithCaption, addButtonGroup } from '/scripts/visuals.js'; |
|
import { addSidebar} from '/scripts/sidebar.js'; |
|
import { addSyntax } from '/scripts/code.js'; |
|
|
|
const heading = document.querySelector(".heading"); |
|
const global = document.querySelector(".global_menu"); |
|
const local = document.querySelector(".local_menu"); |
|
const sidebar = document.querySelector(".sidebar"); |
|
const main = document.querySelector(".main_content"); |
|
|
|
heading.append(addTitle("Learning SSH")); |
|
heading.append(addParagraph("Scott Simpson - LinkedIn Learning - October 2022")); |
|
heading.append(addParagraph("Chapter 2 -SSH Basics")); |
|
|
|
|
|
main.append(addHeader("Connecting to a Server Using a Password")) |
|
main.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:")) |
|
main.append(addSyntax("ssh philip@192.168.0.30")) |
|
main.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.")) |
|
main.append(addImageWithCaption("./images/kitty.jpg", "Kitty - an SSH client.")) |
|
main.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:")) |
|
main.append(addSyntax("ssh philip@192.168.0.30 -p 2233")) |
|
main.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")) |
|
main.append(addSyntax("|1|LOPsxL/rQ0cCFmuKpUJ7XxDONK0=|avw9JhFTvxskhotH2Ma2wuiZp6g= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKw78+w1QooELFbx4TTZVXGhmynpBrZPmnvCKsds1ZWceGIbmWB4eoOk5UkId3/kOM1dmqNcLtOYOzXY2oUAB8s=")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addHeader("Creating a Key Pair with ssh-keygen")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addParagraph("The command to generate a key pair is")) |
|
main.append(addSyntax("ssh-keygen")) |
|
main.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.")) |
|
main.append(addParagraph("The image below show a key pair being generated in interactive mode.")) |
|
main.append(addImageWithCaption("./images/ssh-keygen.jpg", "Using ssh-keygen in interactive mode.")) |
|
main.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. ")) |
|
main.append(addImageWithCaption("./images/keys.jpg", "A listing from the .ssh directory.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.)")) |
|
main.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.")) |
|
main.append(addHeader("Managing and Using Key Pairs")) |
|
main.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.")) |
|
main.append(addParagraph("If that's the case, you can use the command")) |
|
main.append(addSyntax("ssh-copy-id")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addSyntax("ssh-copy-id -p 22 philip@192.168.0.20")) |
|
main.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")) |
|
main.append(addSyntax("ssh philip@192.168.0.20")) |
|
main.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.")) |
|
main.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")) |
|
main.append(addSyntax("openssh-keygen")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addParagraph("There are a number of ways to do that and these include:")) |
|
main.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."])) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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")) |
|
main.append(addSyntax("#PubkeyAuthentication yes")) |
|
main.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")) |
|
main.append(addSyntax("#PasswordAuthentication yes")) |
|
main.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.")) |
|
main.append(addParagraph("This leaves us with a disaled comment and a value of no which we might assume is the default value.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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!")) |
|
main.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.")) |
|
main.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.")) |
|
main.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")) |
|
main.append(addSyntax("sudo systemctl restart sshd")) |
|
main.append(addParagraph("Once that is done, we can then exit from the server and then reconnect to the server using a command like")) |
|
main.append(addSyntax("ssh philip@102.168.0.20")) |
|
main.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.")) |
|
main.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!")) |
|
main.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.")) |
|
main.append(addSyntax("ssh philip@102.168.0.20 -p 223344 -i ~/.ssh/mykey.pub")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addHeader("Client Configuration Options")) |
|
main.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.")) |
|
main.append(addSyntax("ssh -p 223344 philip@192.168.0.20 -p 223344")) |
|
main.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.")) |
|
main.append(addSyntax("ssh -oPort=223344 philip@192.168.0.20")) |
|
main.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.")) |
|
main.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).")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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:")) |
|
main.append(addSyntax("User philip")) |
|
main.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:")) |
|
main.append(addSyntax("# this is a commet")) |
|
main.append(addParagraph("and we can also use include to read in an additional config file:")) |
|
main.append(addSyntax("Include ~/.ssh/moreconfig")) |
|
main.append(addParagraph("This can make the client configuration more modular which is usually easier to manage.")) |
|
main.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.")) |
|
main.append(addInsetList(["<strong>Host</strong> 192.168.0.10", " <strong>User</strong> admin", "", "<strong>Host</strong> 192.168.0.20", " <strong>User</strong> developer", "", "<strong>Host</strong> 192.168.0.*", " <strong>User</strong> philip", ""])) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addInsetList(["<strong>host</strong> webserver", " <strong>Hostname</strong> 192.168.0.20", " <strong>User</strong> philip", " <strong>Port</strong> 223344"])) |
|
main.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")) |
|
main.append(addSyntax("ssh webserver")) |
|
main.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.")) |
|
main.append(addInsetList(["<strong>host</strong> devserver", " <strong>Hostname</strong> 192.168.0.10", " <strong>User</strong> philip", " <strong>Port</strong> 223344"])) |
|
main.append(addParagraph("I can save that and then connect to the dev machine with")) |
|
main.append(addSyntax("ssh devserver")) |
|
main.append(addParagraph("and I am immediately connected to the dev server.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addInsetList(["<strong>host</strong> webserver", " <strong>Hostname</strong> 192.168.0.20", " <strong>User</strong> admin", " <strong>Port</strong> 223344", "", "<strong>User</strong> philip"])) |
|
main.append(addParagraph("Now, if I run the command")) |
|
main.append(addSyntax("ssh webserver")) |
|
main.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.")) |
|
main.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.")) |
|
main.append(addImageWithCaption("./images/config_permissions.jpg", "The contents of the .ssh folder for user philip on my dev machine.")) |
|
main.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")) |
|
main.append(addSyntax("chown 600 config")) |
|
main.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.")) |
|
main.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!")) |
|
main.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.")) |
|
main.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.")) |
|
main.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:")) |
|
main.append(addSyntax("ssh webserver -p 22")) |
|
main.append(addParagraph("or")) |
|
main.append(addSyntax("ssh webserver -oPort 22")) |
|
main.append(addParagraph("This will override any port value you might have set in the local or global config files.")) |
|
main.append(addHeader("Using SSH Client Applications")) |
|
main.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.")) |
|
main.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.")) |
|
main.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")) |
|
main.append(addSyntax("sudo apt install tmux")) |
|
main.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")) |
|
main.append(addSyntax("tmux")) |
|
main.append(addParagraph("You should then see something like the following:")) |
|
main.append(addImageWithCaption("./images/tmux1.jpg", "The tmux environment.")) |
|
main.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.")) |
|
main.append(addImageWithCaption("./images/tmux2.jpg", "The tmux environment with a new session opened.")) |
|
main.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.")) |
|
main.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")) |
|
main.append(addSyntax("tmux attach")) |
|
main.append(addParagraph("The result is shown below.")) |
|
main.append(addImageWithCaption("./images/tmux3.jpg", "The reattached tmux session.")) |
|
main.append(addParagraph("If you want to completely kill the tmux session you can get its pid with a command like")) |
|
main.append(addSyntax("ps aux | grep tmux")) |
|
main.append(addParagraph("or")) |
|
main.append(addSyntax("pidof tmux")) |
|
main.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")) |
|
main.append(addSyntax("kill -9 1234")) |
|
main.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, <a href='https://www.linkedin.com/learning/linux-multitasking-at-the-command-line-18466403'>Linux: Multitasking at the Command Line</a> which was released in March 2023.")) |
|
main.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 <a href='https://blog.jbowen.dev/tips/ssh-tmux/'>blog.jbowe.dev</a>.")) |
|
|
|
addSidebar("linux"); |