import { addBanner, addArticle, addTitle, addHeader, addParagraph, addClassParagraph, addSubHeader, addOrderedList, addUnorderedList, addBlockquote, addInset, addInsetList, addInsetCodeListing, addInsetBulletList, addImageWithCaption, addButtonGroup, addSidebar, addSyntax, addTable } from '/scripts/import.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("Linux Web Services")); heading.append(addParagraph("Scott Simpson - LinkedIn Learning - December 2016")); heading.append(addParagraph("Chapter 2 - Secure the Site and Control Access")); main.append(addSubHeader("Secure Access with SSL")); main.append(addParagraph("SSL (also known as TLS) stands for Secure Sockets Layer and it encrypts communications between a web server and a client.")); main.append(addParagraph("The client and server agree on a shared secret and the server has a cryptographic certificate for this purpose. This can come from a trusted third party in order to establish trust or it can be a self-signed certificate. Self-signed certificates are generally not trustworthy but if you have signed the certificate yourself, it can be trusted within your own network.")); main.append(addParagraph("There is usually some visible sign in the browser that the server has such a certificate and this can be a green padlock (indicating the server has a trusted certificate) or a red warning sign (indicating that it doesn’t). If you have a self-signed certificate, you won’t see the green padlock since the certificate is not signed by a trusted authority.")); main.append(addParagraph("More information on these can be found in the LinkedIn courses, Learning SSL/TLS and SSL Certificates for Web Developers. More general information on the topic can be found in the LinkedIn course, Learning Cryptography and Network Security.")); main.append(addHeader("Create a Self-Signed Certificate")); main.append(addParagraph("The steps involved in doing this are")); main.append(addInsetList(["Ceate a Private Key (using the OpenSSL tool).", "Create a Certificate Signing Request.", "Create a Certificate, signed by our Private Key using the Certificate Signing Request."])); main.append(addParagraph("More details on this can be found on the CentOS wiki page, Setting up an SSL secured Webserver with CentOS. Interesting side-note here, the URL in this case (pardon the pun) is actually case-sensitive which is a little bit strange! More info on this can be found here, it seems that document names are case sensitive but domains are not.")); main.append(addParagraph("The command to generate the key is")); main.append(addSyntax("sudo openssl genrsa -out ca.key 2048")); main.append(addParagraph("The -out option is used to specify the file we output the ley to and 2048 is the number of bits for the key, so it is a measure of the key’s strength and therefore also the level of security.")); main.append(addParagraph("It’s worth remembering that this will put the key into the current directory since we didn’t specify a path for the output so you may want to navigate to the appropriate location (probably your home folder) first to avoid having to specify a location.")); main.append(addParagraph("If we already had a key we trusted, whether it’s our own or is from a trusted 3rd party, we would use this to sign the certificate so this step will not always be necessary.")); main.append(addParagraph("The command to generate the Certificate Signing Request is")); main.append(addSyntax("sudo openssl req -new ca.key -out ca.csr")); main.append(addParagraph("Most of this is self-explanatory, req -new creates a new request, we specify the key to be used to sign the request and (similar to the previous step) the file to output the request to.")); main.append(addParagraph("We are then asked to provide some information about the organisation as shown in figure 31.")); main.append(addImageWithCaption("./images/figure31.png", "Figure 31 - the information we provide when creating our Certificate Signing Request")); main.append(addParagraph("So, this creates a certificate which we have vouched for by signing it with our own private key. The final step is to create the certificate and the command to do that is")); main.append(addSyntax("sudo openssl x509 -req -days90 -n ca.csr -signkey ca.key -out ca.crt")); main.append(addParagraph("In the command, x509 is a standard that describes how a certificate is managed, 90 days is the period before the certificate expires and we also specify the private key, the certificate signing request (both of which are used to create the certificate) and the certificate itself. The output from this command is shown in figure 32.")); main.append(addImageWithCaption("./images/figure32.png", "Figure 32 - the output from the command used to create the certificate")); main.append(addParagraph("Figure 33 shows a listing of my home directory after creating the certificate.")); main.append(addImageWithCaption("./images/figure33.png", "Figure 33 - a listing of my home directory with the certificate")); main.append(addParagraph("The process has created three files")); main.append(addInsetList(["ca.key - the private key", "ca.csr - the certificate signing request", "ca.crt - the actual certificate"])); main.append(addParagraph("Obviously, we will want to keep all three files safe and secure!")); main.append(addSubHeader("Use a Certificate from a Certificate Authority (CA)")); main.append(addParagraph("Obtaining a certificate from a Certificate Authority (CA) can take a while and when it is done, you will either have two files or will have access to two pieces of text. If the latter, you will want to copy them into plain text files with a .key extension for the key and a .crt extension for the certificate.")); main.append(addParagraph("The easiest way to do this is to copy the text from the website of the CA and paste into files called something like mycert.key ad mycert.crt in your home directory.")); main.append(addSubHeader("Add an SSL Certificate to Your Site")); main.append(addParagraph("To use the certificate, whether self-signed or from a CA, we need to configure the web server to use SSL by installing the appropriate module with")); main.append(addSyntax("sudo yum install -y mod_ssl")); main.append(addParagraph("As we saw with php, installing this module creates a conf file, ssl.conf, in the /etc/httpd/conf.d folder and we will edit this so that it points to our certificate.")); main.append(addParagraph("Before we do that, we will need to move our certificate over to the /etc/pki/tls/certs directory with")); main.append(addSyntax("sudo cp ~/ca.crt /etc/pki/tls/certs")); main.append(addParagraph("Similarly, we need to copy the key into the private folder which is in tls with")); main.append(addSyntax("sudo cp ~/ca.key /etc/pki/tls/private")); main.append(addParagraph("Now that the files are in the right place, we can edit the ssl.conf file to make sure that it is pointing to these files with")); main.append(addSyntax("sudo vim /etc/httpd/conf.d/ssl.conf")); main.append(addParagraph("If we scroll down, we should see a couple of sections for Server Certificate and Server Private Key. The directories should be pointing to the directories we put the certificate and the key in but you will want to double check that.")); main.append(addParagraph("We will also want to change the names of the files to the names of our files.")); main.append(addImageWithCaption("./images/figure34.png", "Figure 34 - the section of the ssl.conf file we have just edited")); main.append(addParagraph("Figure 34 shows the relevant part of the ssl.file and by default, these files are localhost.key and localhost.crt and these have been changed to ca.key and ca.crt.")); main.append(addParagraph("We need to restart the web server again and we will try to access the site with the new certicate using the URL, httpss://osztromok.com. In all likelihood, we will find that we are unable to connect to the site. This is because we have configured our site on the understanding that web traffic would come in on port 80, but https uses port 443.")); main.append(addParagraph("Actually, the likeliest cause of the problem here is that the firewall is blocking traffic on port 443 so let’s add a rule to allow the traffic with")); main.append(addSyntax("sudo firewall-cmd --zone=public --add-service thhps --permanent")); main.append(addParagraph("and reload the firewall with")); main.append(addSyntax("sudo firewall-cmd --reload")); main.append(addParagraph("Now, if we go back to the browser and reload, we should see a warning telling us that the connection is not secure. If we click on Learn more, we will see that the security certificate is invalid and that the cause of this is a self-signed certificate.")); main.append(addParagraph("If we trust the issuer (which is easy since we signed the certificate ourselves) we can click on Add Exception and the page should then load. However, since it is a self-signed certificate, we will not see the usual visual signs associated with a secure connection.")); main.append(addParagraph("If we obtain a certificate signed by a trusted source, the procedure would be exactly the same, but we would then see a secure connection when we connect to the site.")); main.append(addParagraph("One final thing we want to do is to ensure that all traffic that comes to our website comes through the secure connection. To do this, we will edit the sites config file which you may recall is in the /etc/httpd/conf.d folder and is called welcome-site.conf. So")); main.append(addSyntax("sudo vim /etc/httpd/conf.d/welcome-site.conf")); main.append(addImageWithCaption("./images/figure35.png", "Figure 35 - the config file for our website")); main.append(addParagraph("We will add a Redirect to this file as shown in figure 35 and after restarting the website again, we should see that even if we specify a URL such as http://osztromok.com, we are redirected to the secure connection (bearing in mind that if we are using a self-signed certificate, it may not look like a secure connection).")); main.append(addSubHeader("Protect a Site with a .htaccess File")); main.append(addParagraph("Previously, we saw how our website (perhaps it would be more accurate to say the web server) can be configured in general terms via the httpd.conf file. For instance, any settings which apply to the entire site are likely to be found here.")); main.append(addParagraph("However, we can use a .htacess file to add more specific rules targeting individual parts of our site.")); main.append(addParagraph("Unlike the httpd.conf file, changes made to the .htaccess file do not require a restart of the web server in order to take effect. For this reason, it is quite useful in development work or for a temporary redirect on a single part of the site.")); main.append(addParagraph("Recall that there is a directive in the httpd.conf file that prevents its settings from being overridden so this must be changed before we are able to make use of the .htaccess file. Within the httpd.conf file, we can sure for the direct we are interested in, AllowOverride. There are a number of these and the one we are interested in is the one that controls the site and it can be identified by the reference to .htaccess files.")); main.append(addParagraph("By default, it has a value of none and there are several options available here. We won’t go into too much details for these at this stage but will simply set the value to all and then save the file. We don’t really need to restart the web server at this stage since we haven’t actually created a .htaccess file but it is as well to do this to avoid confusion later on.")); main.append(addParagraph("In order to be able to do some testing here, it may be advisable to create a test folder and copy your site there so that you can work on it without risking damage to the site. I will do that by creating a folder within /var/www/html called test and I’ll copy the site into that folder with")); main.append(addSyntax("sudo cp -Rv * test")); main.append(addParagraph("In the test folder, we will amend the index.html file so that it can easily be distinguished from the main site and we can view it with")); main.append(addSyntax("www.osztromok.com/test")); main.append(addParagraph("Now, we will create a file inside the test folder called .htaccess. Inside the file, we will put the line")); main.append(addSyntax("Require all denied")); main.append(addParagraph("We don’t need to put this into a directory block to tell the server what folder we are referring to because it will operate on the folder it is found in. The Require directive specifies what conditions are required for access to the resource and so all denied simply denies all requests. If we wanted to accept all requests, we could specify this as")); main.append(addSyntax("Require all granted")); main.append(addParagraph("but since this is the default, we wouldn’t normally need to put in in the .htaccess file, but it can be useful in more fine-grained access control.")); main.append(addParagraph("We can now try to access the index.html file in the test folder and we will see a permission denied message. If we then delete the .htaccess file, permissions will be restored.")); main.append(addParagraph("If we want to create more complex access control, we do that with sets of that include other Require directives. There are three groupings")); main.append(addInsetList(["Require all - demands that no directive within it fails and at least one succeeds.", "Require any - demands that one or more directives within it succeeds, regardless of the other.", "Require any - demands that none of the directives within it succeeds."])); main.append(addParagraph("If the conditions for the evaluation of a block are met, the rule within it is applied.")); main.append(addParagraph("We can place Require directives within these blocks and it succeeds if it is presented with a condition that matches it.")); main.append(addParagraph("We can specify either an IP address or an IP address range and we can also negate a condition with not. To demonstrate this, let’s say that we want to deny access to everyone unless their IP address is in the 216 IP block.")); main.append(addImageWithCaption("./images/figure36.png", "Figure 36 - a block that control access by denying anyone not in the 216 IP block")); main.append(addParagraph("An example of the type of block we might use is shown in figure 36. Note that we have specified the IP address range with CIDR notation, but we can also specify this with a netmask.")); main.append(addParagraph("Since this is a Require any block, at least one of the directives inside it must succeed. The first directive, Require all denied, will always succeed and deny access. The second directive will only succeed if the IP address is in the 216 IP block and will grant access, overriding the previous directive and giving us the result we want.")); main.append(addParagraph("Let’s switch this around and assume that we want to deny access to anyone in the 216 IP block and grant it to everyone else.")); main.append(addImageWithCaption("./images/figure36.png", "Figure 37 - a block that control access by allowing anyone not in the 216 IP block")); main.append(addParagraph("The block for this is shown in figure 37. Again, it is a Require any block and the first directive, Require all granted, always succeeds, granting access to everyone. The second directive succeeds for IP addresses not in the specified range, but fails for any IP address that is in the specified range (that is, for any address in the IP block 216. So in this case, we again start by granting access to everyone but then deny access to those in IP block 216, overriding the previous directive.")); main.append(addParagraph("There is more that can be done with these directives so it is worth checking out the relevant documentation here. It is worth noting here that the blocks shown in figures 36 and 37 specify a single IP range, but we can specify more than one range or address.")); main.append(addParagraph("For any problems with the access, such as it granting access when it shouldn’t or vice versa, you should see more information on this in error_log.")); main.append(addSubHeader("Rewrite a URL with a .htaccess File")); main.append(addParagraph("Another useful module we can add to Apache is mod_rewrite which can take a URL request and rewrite it. This is a kind of shortcut where you might have a very long URL, but you don’t want users to have to type it all in or to give them a long link.")); main.append(addParagraph("Let’s say, for instance, we have a file in our site where the pathway to it (inside the /var/www/html folder) is something like")); main.append(addSyntax("/test/linux/learning/terminal/usage/docs/reference/pdfs/linuxcommands.pdf")); main.append(addParagraph("If we gave this out as a link, this would look like")); main.append(addParagraph("www.osztromok.com/test/linux/learning/terminal/usage/docs/reference/pdfs/linuxcommands.pdf")); main.append(addParagraph("In the site folder, we will put a .htaccess file and we will put two directives in here. The first is RewriteEngine on. This tells the server to interpret rewrite rules and we can switch this off by changing the directive to RewriteEngine off. This can be useful for testing purposes.")); main.append(addParagraph("The second directive is a rewrite rule and figure 38 shows the complete file.")); main.append(addImageWithCaption("./images/figure38.png", "Figure 38 - the .htaccess file with the URL rewrite rule")); main.append(addParagraph("The first part of the rewrite rule is a regular expression. The reason for this is that if we had simply put “commandlines”, the webserver would recognise this and request the file with the full path name. It would then say, I recognise “commandlines and make the same request again and so on. Apache after 10 redirects (by default) and throws an error.")); main.append(addParagraph("In the regular expression, the caret (^) at the start means we are looking for strings with “commandlines” at the start of the string. The $ at the end means that we are looking for a string with nothing after “commandlines”. Note, we are not matching this with the filename, commandlines.pdf, we are matching it with the URL typed in by the user in order to access the file.")); main.append(addParagraph("With the rewrite rule in place, we can then access our pdf with the URL")); main.append(addSyntax("www.osztromok.com/commandlines")); main.append(addParagraph("A couple of additional points about rewrite rules, they have to be unique for obvious reasons. They can also be more powerful than this simple example. For instance, we can use dynamic rewrite rules that match a number of things including user agents and host names. The online documentation for rewrite rules can be found here.")); addSidebar("webdev");