aboutsummaryrefslogtreecommitdiff
path: root/docker/letsencrypt
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2023-10-30 09:51:05 +0000
committerGerrit Code Review <review@openstack.org>2023-10-30 09:51:05 +0000
commit100b8b9aba9eb04dee8a3393afb2686f90f4991d (patch)
treef202d0464b1ad50350b9126fc8fb30384e13ea1c /docker/letsencrypt
parent5e7e80fd8828b6614fedc46cc967fe786e780219 (diff)
parent3213dccb8c318134695bcc9c9ba480448964314f (diff)
downloadkolla-100b8b9aba9eb04dee8a3393afb2686f90f4991d.tar.gz
kolla-100b8b9aba9eb04dee8a3393afb2686f90f4991d.zip
Merge "Rework letsencrypt"
Diffstat (limited to 'docker/letsencrypt')
-rw-r--r--docker/letsencrypt/Dockerfile.j238
-rw-r--r--docker/letsencrypt/extend_start.sh10
-rw-r--r--docker/letsencrypt/letsencrypt-base/Dockerfile.j214
-rw-r--r--docker/letsencrypt/letsencrypt-base/extend_start.sh69
-rwxr-xr-xdocker/letsencrypt/letsencrypt-lego/Dockerfile.j250
-rwxr-xr-xdocker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh167
-rw-r--r--docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh93
-rw-r--r--docker/letsencrypt/letsencrypt-webserver/Dockerfile.j232
-rw-r--r--docker/letsencrypt/letsencrypt-webserver/extend_start.sh3
9 files changed, 428 insertions, 48 deletions
diff --git a/docker/letsencrypt/Dockerfile.j2 b/docker/letsencrypt/Dockerfile.j2
deleted file mode 100644
index 3617e8714..000000000
--- a/docker/letsencrypt/Dockerfile.j2
+++ /dev/null
@@ -1,38 +0,0 @@
-FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
-{% block labels %}
-LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" build-date="{{ build_date }}"
-{% endblock %}
-
-{% block letsencrypt_header %}{% endblock %}
-
-{% import "macros.j2" as macros with context %}
-
-{% if base_package_type == 'rpm' %}
- {% set letsencrypt_packages = [
- 'openssh-clients',
- 'cronie'
- ] %}
-{% elif base_package_type == 'deb' %}
- {% set letsencrypt_packages = [
- 'openssh-client',
- 'cron'
- ] %}
-{% endif %}
-{{ macros.install_packages(letsencrypt_packages | customizable("packages")) }}
-
-COPY extend_start.sh /usr/local/bin/kolla_extend_start
-RUN chmod 644 /usr/local/bin/kolla_extend_start
-
-{% block lego_repository %}
-ENV lego_version=4.6.0
-ENV lego_download_url=https://github.com/go-acme/lego/releases/download/v${lego_version}/lego_v${lego_version}_linux_{{debian_arch}}.tar.gz
-{% endblock %}
-
-{% block lego_install %}
-RUN curl -o /tmp/lego.tar.gz ${lego_download_url} \
- && tar xvf /tmp/lego.tar.gz -C /opt/ \
- && rm -f /tmp/lego.tar.gz
-{% endblock %}
-
-{% block letsencrypt_footer %}{% endblock %}
-{% block footer %}{% endblock %}
diff --git a/docker/letsencrypt/extend_start.sh b/docker/letsencrypt/extend_start.sh
deleted file mode 100644
index 420c96028..000000000
--- a/docker/letsencrypt/extend_start.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-if [[ ! -d "/var/log/kolla/letsencrypt" ]]; then
- mkdir -p /var/log/kolla/letsencrypt
-fi
-if [[ $(stat -c %a /var/log/kolla/letsencrypt) != "755" ]]; then
- chmod 755 /var/log/kolla/letsencrypt
-fi
-
-. /usr/local/bin/kolla_httpd_setup
diff --git a/docker/letsencrypt/letsencrypt-base/Dockerfile.j2 b/docker/letsencrypt/letsencrypt-base/Dockerfile.j2
new file mode 100644
index 000000000..022ff2bfa
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-base/Dockerfile.j2
@@ -0,0 +1,14 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" build-date="{{ build_date }}"
+{% endblock %}
+
+{% block letsencrypt_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.install_packages(cinder_base_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{% block letsencrypt_base_footer %}{% endblock %}
diff --git a/docker/letsencrypt/letsencrypt-base/extend_start.sh b/docker/letsencrypt/letsencrypt-base/extend_start.sh
new file mode 100644
index 000000000..0300f0b44
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-base/extend_start.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# Logs
+
+if [[ ! -d "/var/log/kolla/letsencrypt" ]]; then
+ mkdir -p /var/log/kolla/letsencrypt
+fi
+if [[ $(stat -c %a /var/log/kolla/letsencrypt) != "755" ]]; then
+ chmod 755 /var/log/kolla/letsencrypt
+fi
+
+# Structure for lego and webserver
+
+FOLDERS_LEGO="/etc/letsencrypt /etc/letsencrypt/lego /etc/letsencrypt/lego/internal /etc/letsencrypt/lego/external"
+FOLDERS_WEBSERVER="/etc/letsencrypt/http-01 /etc/letsencrypt/http-01/.well-known /etc/letsencrypt/http-01/.well-known/acme-challenge"
+
+if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ USER="www-data"
+ GROUP="www-data"
+ USERGROUP_WEBSERVER="${USER}:${GROUP}"
+ USERGROUP="haproxy:haproxy"
+else
+ USER="apache"
+ GROUP="apache"
+ USERGROUP_WEBSERVER="${USER}:${GROUP}"
+ USERGROUP="haproxy:haproxy"
+fi
+
+for folder in ${FOLDERS_LEGO}; do
+ if [[ ! -d "${folder}" ]]; then
+ mkdir -p ${folder}
+ fi
+
+ if [[ $(stat -c %U:%G ${folder}) != "${USERGROUP}" ]]; then
+ if getent passwd ${USER} 2 > /dev/null; then
+ chown ${USERGROUP} ${folder}
+ fi
+ fi
+
+ if [[ "${folder}" == "/etc/letsencrypt" ]]; then
+ if [[ $(stat -c %a ${folder}) != "751" ]]; then
+ chmod 751 ${folder}
+ fi
+ else
+ if [[ $(stat -c %a ${folder}) != "755" ]]; then
+ chmod 755 ${folder}
+ fi
+ fi
+done
+
+for folder in ${FOLDERS_WEBSERVER}; do
+ if [[ ! -d "${folder}" ]]; then
+ mkdir -p ${folder}
+ fi
+
+ if [[ $(stat -c %U:%G ${folder}) != "${USERGROUP_WEBSERVER}" ]]; then
+ if getent passwd ${USER} 2 > /dev/null; then
+ chown ${USERGROUP_WEBSERVER} ${folder}
+ fi
+ fi
+
+ if [[ $(stat -c %a ${folder}) != "755" ]]; then
+ chmod 755 ${folder}
+ fi
+done
+
+if [ -e /usr/local/bin/kolla_letsencrypt_extend_start ]; then
+ . /usr/local/bin/kolla_letsencrypt_extend_start
+fi
diff --git a/docker/letsencrypt/letsencrypt-lego/Dockerfile.j2 b/docker/letsencrypt/letsencrypt-lego/Dockerfile.j2
new file mode 100755
index 000000000..47e9a5308
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-lego/Dockerfile.j2
@@ -0,0 +1,50 @@
+FROM {{ namespace }}/{{ image_prefix }}letsencrypt-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" build-date="{{ build_date }}"
+{% endblock %}
+
+{% block letsencrypt_lego_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='letsencrypt', shell='/bin/bash') }}
+
+{% if base_package_type == 'rpm' %}
+ {% set letsencrypt_lego_packages = [
+ 'openssh-clients',
+ 'cronie',
+ 'rsync'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set letsencrypt_lego_packages = [
+ 'openssh-client',
+ 'cron',
+ 'rsync'
+ ] %}
+{% endif %}
+{{ macros.install_packages(letsencrypt_lego_packages | customizable("packages")) }}
+
+{% block letsencrypt_lego_repository_version %}
+ARG letsencrypt_lego_version=4.6.0
+{% if debian_arch == 'arm64' %}
+ARG letsencrypt_lego_sha256sum=f5cecda8880d04ffc394049852a797ec120aebf0203ab0f1b877a0cd89bb0b3e
+{% else %}
+ARG letsencrypt_lego_sha256sum=c0c408788cdec96a4697300211c3944a050bb3d62ed3525a5409c136c94e09cb
+{% endif %}
+ARG letsencrypt_lego_url=https://github.com/go-acme/lego/releases/download/v${letsencrypt_lego_version}/lego_v${letsencrypt_lego_version}_linux_{{debian_arch}}.tar.gz
+{% endblock %}
+
+{% block letsencrypt_lego_install %}
+RUN curl -L -o /tmp/lego.tar.gz ${letsencrypt_lego_url} \
+ && echo "${letsencrypt_lego_sha256sum} /tmp/lego.tar.gz" | sha256sum -c \
+ && tar xvf /tmp/lego.tar.gz -C /opt/ \
+ && rm -f /tmp/lego.tar.gz
+{% endblock %}
+
+COPY letsencrypt-certificates.sh /usr/bin/letsencrypt-certificates
+COPY sync-and-update-certificate.sh /usr/bin/sync-and-update-certificate
+
+RUN chmod +x /usr/bin/letsencrypt-certificates /usr/bin/sync-and-update-certificate
+
+{% block letsencrypt_lego_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh b/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
new file mode 100755
index 000000000..464fe9c95
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
@@ -0,0 +1,167 @@
+#!/bin/bash
+
+function log_info {
+ local message="${1}"
+
+ echo "$(date +%Y/%m/%d) $(date +%H:%M:%S) [INFO] ${message}"
+}
+
+function log_error {
+ local message="${1}"
+
+ echo "$(date +%Y/%m/%d) $(date +%H:%M:%S) [ERROR] ${message}"
+}
+
+function obtain_or_renew_certificate {
+ local certificate_fqdns="${1}"
+ local certificate_type="${2}"
+ local listen_port="${3}"
+ local valid_days="${4}"
+ local acme_url="${5}"
+ local mail="${6}"
+ local letsencrypt_ssh_port="${7}"
+
+ certificate_domain_opts=$(echo ${certificate_fqdns} | sed -r -e 's/^/,/g' -e 's/,/--domains=/g' -e 's/--/ --/g')
+ certificate_fqdn=$(echo ${certificate_fqdns} | awk -F ',' '{print $1}')
+ certificate_fqdns=$(echo ${certificate_fqdns} | sed -r 's/,/\ /g')
+
+ if [ -d "/etc/letsencrypt/lego/${certificate_type}/certificates" ]; then
+ garbage_count=$(find /etc/letsencrypt/lego/${certificate_type}/certificates/ -type f | grep -v "${certificate_fqdn}" | wc -l)
+ if [ ${garbage_count} -ne 0 ]; then
+ log_info "[${certificate_fqdn} - cron] Cleaning up garbage in certificates directory."
+ find /etc/letsencrypt/lego/${certificate_type}/certificates/ -type f | grep -v "${certificate_fqdn}" | xargs rm -f
+ fi
+ fi
+
+ if [ -e "/etc/letsencrypt/lego/${certificate_type}/certificates/${certificate_fqdn}.pem" ]; then
+ certificate_current_fqdns=$(openssl x509 -text -in /etc/letsencrypt/lego/${certificate_type}/certificates/${certificate_fqdn}.pem \
+ | grep DNS: \
+ | sed -r -e 's/\ *DNS://g' -e 's/^/,/g' -e 's/$/,/g')
+
+ local domains_add=""
+ for i in ${certificate_fqdns}; do
+ if ! echo "${certificate_current_fqdns}" | grep -q ",${i},"; then
+ domains_add="${domains_add} ${i}"
+ fi
+ done
+ domains_add=$(echo "${domains_add}" | sed -r -e 's/^\ //g' -e 's/\ /, /g')
+
+ if [ "${domains_add}" != "" ]; then
+ log_info "[${certificate_fqdn} - cron] Domains ${domains_add} will be added to certificate."
+ rm -f /etc/letsencrypt/lego/${certificate_type}/certificates/*
+ fi
+ fi
+
+ [ ! -e "/etc/letsencrypt/lego/${certificate_type}/certificates/${certificate_fqdn}.pem" ] && local lego_action="run" || local lego_action="renew"
+
+ log_info "[INFO] [${certificate_fqdn} - cron] Obtaining certificate for domains ${certificate_fqdns}."
+ /opt/lego --email="${mail}" \
+ ${certificate_domain_opts} \
+ --server "${acme_url}" \
+ --path "/etc/letsencrypt/lego/${certificate_type}/" \
+ --http.webroot "/etc/letsencrypt/http-01" \
+ --http.port ${listen_port} \
+ --cert.timeout ${valid_days} \
+ --accept-tos \
+ --http \
+ --pem ${lego_action} \
+ --${lego_action}-hook="/usr/bin/sync-and-update-certificate --${certificate_type} --fqdn ${certificate_fqdn} --haproxies-ssh ${letsencrypt_ssh_port}"
+}
+
+
+# Parser
+
+INTERNAL_SET="false"
+EXTERNAL_SET="false"
+LOG_FILE="/var/log/kolla/letsencrypt/lesencrypt-lego.log"
+
+
+VALID_ARGS=$(getopt -o ief:p:d:m:a:v:h: --long internal,external,fqdns:,port:,days:,mail:,acme:,vips:,haproxies-ssh: -- "$@")
+if [[ $? -ne 0 ]]; then
+ exit 1;
+fi
+
+eval set -- "$VALID_ARGS"
+while [ : ]; do
+ case "$1" in
+ -i | --internal)
+ CERT_TYPE="internal"
+ INTERNAL_SET="true"
+ shift
+ ;;
+ -e | --external)
+ CERT_TYPE="external"
+ EXTERNAL_SET="true"
+ shift
+ ;;
+ -f | --fqdns)
+ FQDNS="${2}"
+ shift 2
+ ;;
+ -p | --port)
+ PORT="${2}"
+ shift 2
+ ;;
+ -d | --days)
+ DAYS="${2}"
+ shift 2
+ ;;
+ -m | --mail)
+ MAIL="${2}"
+ shift 2
+ ;;
+ -a | --acme)
+ ACME="${2}"
+ shift 2
+ ;;
+ -v | --vips)
+ VIPS="${2}"
+ shift 2
+ ;;
+ -h | --haproxies-ssh)
+ LETSENCRYPT_SSH_PORT="${2}"
+ shift 2
+ ;;
+ --) shift;
+ break
+ ;;
+ esac
+done
+
+FQDN=$(echo "${FQDNS}" | awk -F ',' '{print $1}')
+
+if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
+ if [ "${INTERNAL_SET}" = "${EXTERNAL_SET}" ]; then
+ log_error "[${FQDN} - cron] Only --internal or --external parameter is allowed at a time."
+ exit 1
+ fi
+
+ LETSENCRYPT_VIP_ADDRESSES="$(echo ${VIPS} | sed -e 's/,/|/g')"
+ if [ "${INTERNAL_SET}" = "true" ]; then
+ LETSENCRYPT_INTERNAL_FQDNS="${FQDNS}"
+ fi
+
+ if [ "${EXTERNAL_SET}" = "true" ]; then
+ LETSENCRYPT_EXTERNAL_FQDNS="${FQDNS}"
+ fi
+
+
+ if ip a | egrep -q "${LETSENCRYPT_VIP_ADDRESSES}"; then
+ log_info "[${FQDN} - cron] This Letsencrypt-lego host is active..."
+ if [ "${LETSENCRYPT_INTERNAL_FQDNS}" != "" ]; then
+ log_info "[${FQDN} - cron] Processing domains ${LETSENCRYPT_INTERNAL_FQDNS}"
+ obtain_or_renew_certificate ${LETSENCRYPT_INTERNAL_FQDNS} internal ${PORT} ${DAYS} ${ACME} ${MAIL} ${LETSENCRYPT_SSH_PORT}
+ fi
+
+ if [ "${LETSENCRYPT_EXTERNAL_FQDNS}" != "" ]; then
+ log_info "[INFO] [${FQDN} - cron] Processing domains ${LETSENCRYPT_EXTERNAL_FQDNS}"
+ obtain_or_renew_certificate ${LETSENCRYPT_EXTERNAL_FQDNS} external ${PORT} ${DAYS} ${ACME} ${MAIL} ${LETSENCRYPT_SSH_PORT}
+ fi
+ else
+ log_info "[${FQDN} - cron] This Letsencrypt-lego host is passive, nothing to do..."
+ fi
+fi
+
+if [ -d "/etc/letsencrypt/lego" ]; then
+ chown -R haproxy:haproxy /etc/letsencrypt/lego
+fi
diff --git a/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh b/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh
new file mode 100644
index 000000000..6dc76e988
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+function log_info {
+ local message="${1}"
+
+ echo "$(date +%Y/%m/%d) $(date +%H:%M:%S) [INFO] ${message}"
+}
+
+function log_error {
+ local message="${1}"
+
+ echo "$(date +%Y/%m/%d) $(date +%H:%M:%S) [ERROR] ${message}"
+}
+
+
+# Parser
+
+INTERNAL_SET="false"
+EXTERNAL_SET="false"
+LOG_FILE="/var/log/kolla/letsencrypt/lesencrypt-lego.log"
+
+
+VALID_ARGS=$(getopt -o ief:l: --long internal,external,fqdn:,haproxies-ssh: -- "$@")
+if [[ $? -ne 0 ]]; then
+ exit 1;
+fi
+
+eval set -- "$VALID_ARGS"
+while [ : ]; do
+ case "$1" in
+ -i | --internal)
+ CERT_TYPE="internal"
+ INTERNAL_SET="true"
+ shift
+ ;;
+ -e | --external)
+ CERT_TYPE="external"
+ EXTERNAL_SET="true"
+ shift
+ ;;
+ -f | --fqdn)
+ FQDN="${2}"
+ shift 2
+ ;;
+ -l | --haproxies-ssh)
+ HAPROXIES_SSH_HOSTS_PORT="${2}"
+ shift 2
+ ;;
+ --) shift;
+ break
+ ;;
+ esac
+done
+
+if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
+ if [ "${INTERNAL_SET}" = "${EXTERNAL_SET}" ]; then
+ log_error "[${FQDN} - hook] Only --internal or --external parameter is allowed at a time"
+ exit 1
+ fi
+
+ HAPROXIES_SSH_HOSTS_PORT=$(echo ${HAPROXIES_SSH_HOSTS_PORT} | sed -e 's/,/ /g')
+
+ for i in ${HAPROXIES_SSH_HOSTS_PORT}; do
+
+ server=$(echo $i | awk -F ':' '{print $1}')
+ port=$(echo $i | awk -F ':' '{print $2}')
+
+ if ! ip a | grep -q "${server}"; then
+
+ log_info "[${FQDN} - hook] Rsync lego data /etc/letsencrypt/lego/ to server ${server} and port ${port}"
+ rsync -a -e "ssh -p ${port} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityFile=/var/lib/letsencrypt/.ssh/id_rsa" /etc/letsencrypt/lego/ haproxy@${server}:/etc/letsencrypt/lego/ --delete >/dev/null 2>&1
+ if [ "$?" -eq 0 ]; then
+ log_info "[${FQDN} - hook] Rsync Successfull."
+ fi
+
+ else
+ log_info "[${FQDN} - hook] Rsync lego data /etc/letsencrypt/lego/ to server ${server} and port ${port} not needed."
+ fi
+
+ if [ "${INTERNAL_SET}" = "true" ]; then
+ internal_cert_path=$(find /etc/letsencrypt/lego/internal/ -name '*.pem')
+ sed -i '/^$/d' ${internal_cert_path}
+ rsync -av -e "ssh -p ${port} -o StrictHostKeyChecking=no -o IdentityFile=/var/lib/letsencrypt/.ssh/id_rsa" ${internal_cert_path} haproxy@${server}:/var/lib/haproxy/haproxy-internal.pem --delete >/dev/null 2>&1
+ ssh -p ${port} -i /var/lib/letsencrypt/.ssh/id_rsa -o StrictHostKeyChecking=no haproxy@${server} "/usr/bin/update-haproxy-cert --internal"
+ else
+ external_cert_path=$(find /etc/letsencrypt/lego/external/ -name '*.pem')
+ sed -i '/^$/d' ${external_cert_path}
+ rsync -av -e "ssh -p ${port} -o StrictHostKeyChecking=no -o IdentityFile=/var/lib/letsencrypt/.ssh/id_rsa" ${external_cert_path} haproxy@${server}:/var/lib/haproxy/haproxy.pem --delete >/dev/null 2>&1
+ ssh -p ${port} -i /var/lib/letsencrypt/.ssh/id_rsa -o StrictHostKeyChecking=no haproxy@${server} "/usr/bin/update-haproxy-cert --external"
+ fi
+
+ done
+fi
diff --git a/docker/letsencrypt/letsencrypt-webserver/Dockerfile.j2 b/docker/letsencrypt/letsencrypt-webserver/Dockerfile.j2
new file mode 100644
index 000000000..d97af8f9c
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-webserver/Dockerfile.j2
@@ -0,0 +1,32 @@
+FROM {{ namespace }}/{{ image_prefix }}letsencrypt-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" build-date="{{ build_date }}"
+{% endblock %}
+
+{% block letsencrypt_webserver_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set letsencrypt_webserver_packages = [
+ 'httpd',
+ 'mod_ssl'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set letsencrypt_webserver_packages = [
+ 'apache2'
+ ] %}
+{% endif %}
+{{ macros.install_packages(letsencrypt_webserver_packages | customizable("packages")) }}
+
+{% if base_package_type == 'rpm' %}
+RUN sed -i -r 's,^(Listen 80),#\1,' /etc/httpd/conf/httpd.conf \
+ && sed -i -r 's,^(Listen 443),#\1,' /etc/httpd/conf.d/ssl.conf
+{% elif base_package_type == 'deb' %}
+RUN echo > /etc/apache2/ports.conf
+{% endif %}
+
+COPY extend_start.sh /usr/local/bin/kolla_letsencrypt_extend_start
+
+{% block letsencrypt_webserver_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/docker/letsencrypt/letsencrypt-webserver/extend_start.sh b/docker/letsencrypt/letsencrypt-webserver/extend_start.sh
new file mode 100644
index 000000000..c55f0b199
--- /dev/null
+++ b/docker/letsencrypt/letsencrypt-webserver/extend_start.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+. /usr/local/bin/kolla_httpd_setup