Skip to content

Week 07 - TLS

Topic

Securing web and email services with TLS certificates. Obtaining certificates from a private Certificate Authority (HashiCorp Vault), enabling HTTPS on all Apache virtual hosts, configuring TLS for Postfix (SMTPS), and enabling IMAPS in Dovecot.

Company Requests

Ticket #701: Encrypt Web Traffic

"The security team has discovered that all four of our websites are served over unencrypted HTTP. This is unacceptable - customer data, login credentials, and email content are all visible to anyone intercepting the traffic. Implement HTTPS for all web services: <vm_name>.sysadm.ee, blog.<vm_name>.sysadm.ee, inventory.<vm_name>.sysadm.ee, and mail.<vm_name>.sysadm.ee."

Ticket #702: Encrypt Email Traffic

"The same applies to our email infrastructure. Postfix and Dovecot must support TLS so that authentication credentials and email content are encrypted in transit. Internal-only connections may remain unencrypted for simplicity - this means traffic between Dovecot and Postfix (on the same VM) and between Apache and the inventory backend API do not need TLS."

Unencrypted traffic

In production, HTTP traffic on port 80 should be redirected to HTTPS (port 443). In this course, we keep port 80 open and functional so that scoring checks from previous weeks (Labs 04–06) continue to pass. This is not best practice - in a real environment, you would configure automatic HTTP-to-HTTPS redirects.

Similarly, with other non-encrypted endpoints (like email), the best practice is to disable them entirely, and only use encrypted versions. In this course, we skip this step to allow scoring checks from previous labs to pass.

Certificate Authority

For this course, we use a private Certificate Authority powered by HashiCorp Vault at https://vault.sysadm.ee. Since this CA is not publicly trusted, browsers will show security warnings until you import the CA certificate. You can verify certificates using openssl s_client with the -CAfile flag, or import the CA certificate into your browser (see the verification task).

Scoring Checks

  • Check 7.1: Port 443 is open and accepts TLS connections.
    • Method: TCP connection with TLS handshake to your VM on port 443.
    • Expected: TLS handshake succeeds.
  • Check 7.2: <vm_name>.sysadm.ee answers HTTPS with a valid CA-signed certificate.
    • Method: The scoring server sends an HTTPS request to your VM with --resolve to <vm_name>.sysadm.ee. Certificate is validated against the course CA.
    • Expected: HTTPS response contains your VM's short name, and the certificate is trusted.
  • Check 7.3: blog.<vm_name>.sysadm.ee answers HTTPS with WordPress content.
    • Method: HTTPS request with --resolve to blog.<vm_name>.sysadm.ee, validated against the course CA.
    • Expected: HTTPS response contains WordPress indicators (wp-content/, wp-includes/), and the certificate is trusted.
  • Check 7.4: mail.<vm_name>.sysadm.ee answers HTTPS with Roundcube.
    • Method: HTTPS request with --resolve to mail.<vm_name>.sysadm.ee, validated against the course CA.
    • Expected: HTTPS response contains Roundcube indicators, and the certificate is trusted.
  • Check 7.5: Port 465 (SMTPS) is open and accepts TLS connections.
    • Method: TCP connection with TLS handshake to port 465.
    • Expected: TLS handshake succeeds.
  • Check 7.6: Port 993 (IMAPS) is open and accepts TLS connections.
    • Method: TCP connection with TLS handshake to port 993.
    • Expected: TLS handshake succeeds.
  • Check 7.7: IMAPS certificate is properly CA-signed.
    • Method: The scoring server connects to port 993 with openssl s_client and checks the Verify return code.
    • Expected: Verify return code is 0 (ok).

Required Documentation

Before starting this lab, review the following documentation:

Tasks

Task 1: Obtain TLS Certificates from Vault

Get a certificate and private key from the course Certificate Authority (HashiCorp Vault) for your services.

Complete

  1. Open the Vault web UI at https://vault.sysadm.ee in your browser.
  2. Choose LDAP as the authentication method and log in with your University of Tartu credentials.
  3. Navigate to the pki secrets engine and the sysadm.ee role.
  4. Generate a certificate. You have two options:
    • Per-service certificates: Generate separate certificates for each service (<vm_name>.sysadm.ee, blog.<vm_name>.sysadm.ee, inventory.<vm_name>.sysadm.ee, mail.<vm_name>.sysadm.ee).
    • Wildcard certificate : Use *.<vm_name>.sysadm.ee as the Common Name, and add <vm_name>.sysadm.ee in the Alt Names field. A wildcard only covers subdomains - it does not match the bare domain itself.
  5. Make sure the format is set to PEM.
  6. Save the three pieces Vault returns - certificate, private key, and CA chain. Once you close the window, Vault will not show them again. If you lose them, generate new ones.
  7. Copy the files to your VM at the standard locations:
    • Certificate: /etc/pki/tls/certs/server.crt
    • Private key: /etc/pki/tls/private/server.key
    • CA chain: /etc/pki/tls/certs/ca-chain.crt
  8. Set proper permissions on the private key:
    chmod 600 /etc/pki/tls/private/server.key
    

Wildcard certificates and bare domains

A wildcard certificate for *.example.sysadm.ee covers blog.example.sysadm.ee, mail.example.sysadm.ee, etc. - but it does not cover example.sysadm.ee itself. You must add the bare domain in the Alt Names field (or alt_names in the CLI) for it to be included in the certificate, or use a separate certificate for the plain domain.

CA chain format

The CA chain Vault returns may contain multiple certificates. Make sure they are in correct PEM format - each certificate block must be separated by a newline, not a comma. The format must be:

-----BEGIN CERTIFICATE-----
<base64 data>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<base64 data>
-----END CERTIFICATE-----

Certificate lifetime

Certificates issued by Vault in this course have a default lifetime of 7 days (maximum 1 year). If your certificate expires, simply generate a new one from Vault and replace the files on your VM. Remember to restart the affected services after replacing certificates.

Another option is to specify a longer certificate lifetime when generating the certificate in Vault.

Reference: SOP: Certificate Management - Sign a Certificate via Vault, Concepts: TLS and Certificates - Certificate File Formats

Task 2: Enable HTTPS for Web Services

Install mod_ssl and configure HTTPS virtual hosts for all four websites.

Complete

  1. Install the mod_ssl package.
  2. For each of your four virtual hosts (<vm_name>.sysadm.ee, blog.<vm_name>.sysadm.ee, inventory.<vm_name>.sysadm.ee, mail.<vm_name>.sysadm.ee), add a port 443 virtual host block alongside the existing port 80 one. Each HTTPS virtual host needs the SSL directives pointing to your certificate, key, and CA chain files.
  3. Open port 443 in firewalld and your cloud security group.
  4. Test the Apache configuration with apachectl configtest and restart the service.
  5. Verify each site works over HTTPS using openssl s_client with the -CAfile flag. Check that Verify return code is 0 (ok) for each.

Reference: SOP: Certificate Management - Install a Certificate in Apache, Concepts: TLS and Certificates - TLS in Apache

Task 3: Enable TLS for Postfix

Configure Postfix to support TLS on the SMTPS (465) port.

Complete

  1. Add TLS settings to /etc/postfix/main.cf - set the certificate file, key file, CA file, security level, and TLS logging.
  2. In /etc/postfix/master.cf, enable the smtps block (port 465). Uncomment it and configure it for implicit TLS (smtpd_tls_wrappermode=yes) with SASL authentication.
  3. Install cyrus-sasl-plain if not already present.
  4. Open port 465 in firewalld and your cloud security group.
  5. Restart Postfix.
  6. Verify with openssl s_client -connect mail.<vm_name>.sysadm.ee:465 -CAfile /etc/pki/tls/certs/ca-chain.crt and check for Verify return code: 0 (ok).

Reference: SOP: Certificate Management - Install a Certificate in Postfix/Dovecot, Concepts: TLS and Certificates - TLS in Postfix

Task 4: Enable TLS for Dovecot

Configure Dovecot to serve IMAP over TLS (IMAPS) on port 993.

Complete

  1. Edit /etc/dovecot/conf.d/10-ssl.conf and configure SSL with the certificate and key paths. Note: Dovecot uses a < prefix before file paths - this is Dovecot-specific syntax that tells it to read the contents of the file.
  2. Open port 993 in firewalld and your cloud security group.
  3. Restart Dovecot.
  4. Verify with openssl s_client -connect mail.<vm_name>.sysadm.ee:993 -CAfile /etc/pki/tls/certs/ca-chain.crt and check for Verify return code: 0 (ok).

Reference: SOP: Certificate Management - Install a Certificate in Postfix/Dovecot, Concepts: TLS and Certificates - TLS in Dovecot


Ansible Tips

This section covers tips for automating the tasks in this lab with Ansible. You are not required to automate everything this week, but starting early will save you time later.

Requesting Certificates from Vault with Ansible

Install the community.hashi_vault Ansible collection:

ansible-galaxy collection install community.hashi_vault

Use the vault_pki_generate_certificate module to issue certificates automatically:

- name: Request a certificate from HashiCorp Vault PKI
  community.hashi_vault.vault_pki_generate_certificate:
    url: "https://vault.sysadm.ee"
    auth_method: "ldap"
    username: "{{ vault_ldap_username }}"
    password: "{{ vault_ldap_password }}"
    engine_mount_point: "pki"
    role_name: "sysadm.ee"
    common_name: "*.{{ hostname }}.sysadm.ee"
    alt_names: "{{ hostname }}.sysadm.ee"
  register: vault_pki_response
  delegate_to: localhost
  become: false

Key points about this task:

  • delegate_to: localhost - the Vault API call runs from your local machine, not the remote VM.
  • become: false - no root privileges needed for the API call.
  • register: vault_pki_response - saves the response (certificate, key, chain) in a variable.

Protecting Credentials

Never store university credentials in plain text

You need your university credentials to authenticate with Vault. Do not write them directly in your playbook or variable files.

Option 1: Interactive prompt (simplest)

Add vars_prompt to your playbook to ask for credentials at runtime:

- hosts: all
  vars_prompt:
    - name: vault_ldap_username
      prompt: "Enter your university username"
      private: false
    - name: vault_ldap_password
      prompt: "Enter your university password"
      private: true

Option 2: Ansible Vault (more robust)

Encrypt sensitive variables with Ansible Vault (note: this is a different product from HashiCorp Vault):

ansible-vault encrypt_string --prompt

This prompts you for an encryption password and a variable name (e.g., vault_ldap_password), then outputs an encrypted blob you can put in a variable file. Run your playbook with --ask-vault-pass to decrypt at runtime.

Store the encrypted variable file separately and add it to .gitignore - even encrypted secrets should not be in version control.

Writing Certificate Files to the VM

Use the response from the Vault module to write certificate files:

- name: Write certificate files
  ansible.builtin.copy:
    content: "{{ item.content }}"
    dest: "{{ item.path }}"
    mode: "{{ item.mode }}"
  with_items:
    - { path: '/etc/pki/tls/certs/server.crt', content: "{{ vault_pki_response.data.certificate }}", mode: '0644' }
    - { path: '/etc/pki/tls/certs/ca-chain.crt', content: "{{ vault_pki_response.data.ca_chain | join('\n') }}", mode: '0644' }
    - { path: '/etc/pki/tls/private/server.key', content: "{{ vault_pki_response.data.private_key }}", mode: '0600' }
  notify:
    - restart httpd
    - restart postfix
    - restart dovecot

CA chain format

The CA chain is returned as a list. Use the join('\n') Jinja2 filter to convert it to a single PEM string with newline separators between certificates.

Handlers

Define handlers to restart services only when certificate files change:

# roles/tls/handlers/main.yml
- name: restart httpd
  systemd:
    name: httpd
    state: restarted

- name: restart postfix
  systemd:
    name: postfix
    state: restarted

- name: restart dovecot
  systemd:
    name: dovecot
    state: restarted

Tags

Use tags to run only TLS-related tasks:

- { role: tls, tags: tls }

Run only the TLS role: ansible-playbook --tags=tls playbook.yml

Useful Modules

  • community.hashi_vault.vault_pki_generate_certificate - request certificates from Vault
  • copy - write certificate/key content to files
  • template - deploy TLS-enabled virtual host configurations
  • firewalld - open ports (443, 465, 993)
  • systemd - restart services after config changes
  • file - set ownership and permissions on certificate files