diff --git a/.drone.yml b/.drone.yml index a19297c..698d724 100644 --- a/.drone.yml +++ b/.drone.yml @@ -42,11 +42,11 @@ steps: dockerfile: Dockerfile force_tag: true password: - from_secret: docker_gitea_password + from_secret: gitea_docker_password registry: git.asperti.com repo: git.asperti.com/paspo/docker-ftps username: - from_secret: docker_gitea_username + from_secret: gitea_docker_username tags: - ${DRONE_TAG}-linux-amd64 - ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-amd64 @@ -100,11 +100,11 @@ steps: dockerfile: Dockerfile force_tag: true password: - from_secret: docker_gitea_password + from_secret: gitea_docker_password registry: git.asperti.com repo: git.asperti.com/paspo/docker-ftps username: - from_secret: docker_gitea_username + from_secret: gitea_docker_username tags: - ${DRONE_TAG}-linux-arm64 - ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-arm64 @@ -158,11 +158,11 @@ steps: dockerfile: Dockerfile force_tag: true password: - from_secret: docker_gitea_password + from_secret: gitea_docker_password registry: git.asperti.com repo: git.asperti.com/paspo/docker-ftps username: - from_secret: docker_gitea_username + from_secret: gitea_docker_username tags: - ${DRONE_TAG}-linux-arm - ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-arm @@ -203,9 +203,9 @@ steps: ignore_missing: true spec: manifest2.tmpl username: - from_secret: docker_gitea_username + from_secret: gitea_docker_username password: - from_secret: docker_gitea_password + from_secret: gitea_docker_password tags: - latest - ${DRONE_TAG} diff --git a/Dockerfile b/Dockerfile index 61971df..ab583f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,9 @@ -FROM alpine:edge +FROM alpine:latest RUN \ - echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \ - apk -U add proftpd proftpd-mod_tls proftpd-mod_ifsession proftpd-utils openssl perl && \ + apk -U add proftpd proftpd-mod_tls proftpd-mod_ifsession proftpd-utils openssl perl acme.sh && \ mkdir -p /var/run/proftpd /etc/proftpd/custom.conf.d/ -COPY custom.conf /etc/proftpd/conf.d/custom.conf -COPY run.sh /run.sh -COPY cron.sh /cron.sh +COPY rootfs / -RUN \ - chmod +x /run.sh && \ - chmod +x /cron.sh && \ - ln -s /cron.sh /etc/periodic/15min/reconfigure_certs.sh - -ENTRYPOINT ["/run.sh"] +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/README.md b/README.md index 9a2e3db..b55dcd0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ docker run -d --name my-ftps \ ``` The *MASQUERADE* parameter is the only required one. You can use an IP address (which is discouraged) or a DNS name. -You must provide valid certificates for TLS; if you use Lets'Encrypt, you can mofify like this: +You must provide valid certificates for TLS; if you use Lets'Encrypt, you can modify like this: ```bash docker run -d --name my-ftps \ @@ -33,7 +33,7 @@ docker run -d --name my-ftps \ docker.asperti.com/paspo/ftps ``` -## docker-compose +## docker-compose (external certificate) ```yaml version: "3" @@ -56,12 +56,66 @@ services: - PASSIVEPORTS_START=21210 - PASSIVEPORTS_END=21220 - MAXCLIENTS=500 - - MAXCLIENTSPERHOST=100 + - MAXCLIENTSPERHOST=100 - TLS_CERT=/certs/live/ftp.mydomain.com/cert.pem - TLS_KEY=/certs/live/ftp.mydomain.com/privkey.pem - TLS_CHAIN=/certs/live/ftp.mydomain.com/chain.pem ``` +## docker-compose (using internal acme.sh) + +```yaml +version: "3" +services: + + ftps-server: + image: docker.asperti.com/paspo/ftps + restart: always + ports: + - "21:21" + - "20:20" + - "21210-21220:21210-21220" + volumes: + - "/srv/ftps/auth:/auth" + - "/srv/ftps/conf:/etc/proftpd/custom.conf.d:ro" + - "/srv/ftps/data:/home" + - "/srv/ftps/acme:/acme" + environment: + - MASQUERADE=ftp.mydomain.com + - PASSIVEPORTS_START=21210 + - PASSIVEPORTS_END=21220 + - MAXCLIENTS=500 + - MAXCLIENTSPERHOST=100 + - ENABLE_ACME=1 # "1" will enable, anything else means external cert + - ACME_SERVER=letsencrypt # optional + - ACME_EMAIL=myemail@gmail.com # used by letsencrypt + - ACME_DNS=dns_ovh # see below + - OVH_END_POINT=ovh-eu + - OVH_AK=abc123abc123abc1 # application key + - OVH_AS=abc123abc123abc1abc123abc123abc1 # application secret + - OVH_CK=abc123abc123abc1abc123abc123abc1 # consumer key +``` + +## The rationale behind the acme.sh alternative + +You normally use an external letsencrypt client to obtain the certificate and then pass it to the docker container. In some cases, you can't use an external acme client and/or you can't do HTTP-01 auth. +The included `acme.sh` client will help you to setup DNS-01 auth and in keeping the cert updated. +Please check [here](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) for supported dns providers. +Each provider will use different environment variables, you have to add these variables to the container's environment. + +### OVH + +A quick way to create required credentials for OVH: + +- login to your [OVH accuont](https://www.ovh.com/manager/) +- paste in your browser an URL like the following one: + +```txt +https://api.ovh.com/createToken/?GET=/domain/zone/mydomain.com/*&POST=/domain/zone/mydomain.com/*&PUT=/domain/zone/mydomain.com/*&GET=/domain/zone/mydomain.com&DELETE=/domain/zone/mydomain.com/record/* +``` + +This will create some credentials that'll allow management only for that domain (`mydomain.com`). + ## passive ports If you want to change the passive ports range (which by default is 50000-50050), you can do so via environment variables (PASSIVEPORTS_START and PASSIVEPORTS_END). diff --git a/rootfs/app/acme-cert-init.sh b/rootfs/app/acme-cert-init.sh new file mode 100755 index 0000000..0a89aa4 --- /dev/null +++ b/rootfs/app/acme-cert-init.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +MASQUERADE=${MASQUERADE:-127.0.0.1} +ACME_SERVER=${ACME_SERVER:-letsencrypt} +ACME_DNS=${ACME_DNS:-myapi} + +if [ ! -d /acme/cert ] ; then + mkdir -p /acme/cert +fi + +if [ -n "${ACME_EMAIL}" ] ; then + ACME_EMAIL="--accountemail ${ACME_EMAIL}" +fi + +if [ ! -f "/acme/cert/cert.pem" ] ; then + echo "Initializing certificate with acme.sh" + # shellcheck disable=SC2086 + acme.sh --issue -d "${MASQUERADE}" \ + --home /acme \ + --dns "${ACME_DNS}" \ + --server "${ACME_SERVER}" \ + --cert-file /acme/cert/cert.pem \ + --key-file /acme/cert/privkey.pem \ + --fullchain-file /acme/cert/chain.pem \ + --reloadcmd /app/acme-refresh-cert.sh ${ACME_EMAIL} +else + echo "Certificate ready" +fi diff --git a/rootfs/app/acme-cron.sh b/rootfs/app/acme-cron.sh new file mode 100755 index 0000000..5fe3003 --- /dev/null +++ b/rootfs/app/acme-cron.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ "$ENABLE_ACME" = "1" ] ; then + /usr/bin/acme.sh --cron --home /acme +fi diff --git a/rootfs/app/acme-refresh-cert.sh b/rootfs/app/acme-refresh-cert.sh new file mode 100755 index 0000000..8297404 --- /dev/null +++ b/rootfs/app/acme-refresh-cert.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +############ FILES + +TLS_CERT=/acme/cert/cert.pem +TLS_KEY=/acme/cert/privkey.pem +TLS_CHAIN=/acme/cert/chain.pem + +[ ! -f "$TLS_CERT" ] && exit 1 +[ ! -f "$TLS_KEY" ] && exit 1 +[ ! -f "$TLS_CHAIN" ] && exit 1 + +############ CHECK CERT KEY ALGO +ALGO=$(openssl x509 -in "$TLS_CERT"-text | sed -n 's/\ *Public Key Algorithm: //p' | tr '\n') + +############ UPDATE cert config if needed +if [ "$ALGO" = "id-ecPublicKey" ] ; then +cat > /etc/proftpd/conf.d/certificate.conf < + TLSECCertificateFile "$TLS_CERT" + TLSECCertificateKeyFile "$TLS_KEY" + TLSCertificateChainFile "$TLS_CHAIN" + +EOF +fi + +if [ "$ALGO" = "rsaEncryption" ] ; then +cat > /etc/proftpd/conf.d/certificate.conf < + TLSRSACertificateFile "$TLS_CERT" + TLSRSACertificateKeyFile "$TLS_KEY" + TLSCertificateChainFile "$TLS_CHAIN" + +EOF +fi + +############ RELOAD PROFTPD IF RUNNING +pidof proftpd >/dev/null && killall -HUP proftpd diff --git a/cron.sh b/rootfs/app/cert-init.sh old mode 100644 new mode 100755 similarity index 77% rename from cron.sh rename to rootfs/app/cert-init.sh index e82e276..8e552ba --- a/cron.sh +++ b/rootfs/app/cert-init.sh @@ -1,23 +1,18 @@ #!/bin/sh ############ TLS - TLS_CERT=${TLS_CERT:-/certs/cert.pem} TLS_KEY=${TLS_KEY:-/certs/privkey.pem} TLS_CHAIN=${TLS_CHAIN:-/certs/chain.pem} -cat $TLS_CERT > /etc/proftpd/cert.pem -cat $TLS_KEY > /etc/proftpd/privkey.pem -cat $TLS_CHAIN > /etc/proftpd/chain.pem - -############ IF CERT IS THE SAME, THEN EXIT - -md5sum -c /sums 1&>2 2>/dev/null && exit +cat "$TLS_CERT" > /etc/proftpd/cert.pem +cat "$TLS_KEY" > /etc/proftpd/privkey.pem +cat "$TLS_CHAIN "> /etc/proftpd/chain.pem ############ CHECK CERT KEY ALGO - ALGO=$(openssl x509 -in /etc/proftpd/cert.pem -text | sed -n 's/\ *Public Key Algorithm: //p' | tr '\n') +############ UPDATE cert config if needed if [ "$ALGO" = "id-ecPublicKey" ] ; then cat > /etc/proftpd/conf.d/certificate.conf < @@ -38,8 +33,5 @@ cat > /etc/proftpd/conf.d/certificate.conf < /sums - -############ RELOAD - -killall -HUP proftpd +md5sum "$TLS_CERT" > /app/sums +echo "Certificate ready" diff --git a/run.sh b/rootfs/app/cron.sh old mode 100644 new mode 100755 similarity index 50% rename from run.sh rename to rootfs/app/cron.sh index 0920887..a232ba2 --- a/run.sh +++ b/rootfs/app/cron.sh @@ -1,29 +1,23 @@ #!/bin/sh -############ MASQUERADE - -MASQUERADE=${MASQUERADE:-127.0.0.1} -echo "MasqueradeAddress ${MASQUERADE}" > /etc/proftpd/conf.d/masquerade.conf - -############ AUTH - -[ ! -f /auth/passwd ] && touch /auth/passwd - -chmod 0600 /auth/passwd -chmod 0700 /auth +############ IF ACME IS ENABLED, THIS IS THE WRONG SCRIPT +if [ ! "$ENABLE_ACME" = "1" ] ; then + exit +fi ############ TLS - TLS_CERT=${TLS_CERT:-/certs/cert.pem} TLS_KEY=${TLS_KEY:-/certs/privkey.pem} TLS_CHAIN=${TLS_CHAIN:-/certs/chain.pem} -cat $TLS_CERT > /etc/proftpd/cert.pem -cat $TLS_KEY > /etc/proftpd/privkey.pem -cat $TLS_CHAIN > /etc/proftpd/chain.pem +cat "$TLS_CERT" > /etc/proftpd/cert.pem +cat "$TLS_KEY" > /etc/proftpd/privkey.pem +cat "$TLS_CHAIN" > /etc/proftpd/chain.pem + +############ IF CERT IS THE SAME, THEN EXIT +md5sum -c /app/sums >/dev/null 2>/dev/null && exit ############ CHECK CERT KEY ALGO - ALGO=$(openssl x509 -in /etc/proftpd/cert.pem -text | sed -n 's/\ *Public Key Algorithm: //p' | tr '\n') if [ "$ALGO" = "id-ecPublicKey" ] ; then @@ -46,27 +40,7 @@ cat > /etc/proftpd/conf.d/certificate.conf < /sums +md5sum "$TLS_CERT" > /app/sums -############ PASSIVE PORTS - -PASSIVEPORTS_START=${PASSIVEPORTS_START:-50000} -PASSIVEPORTS_END=${PASSIVEPORTS_END:-50050} - -echo "PassivePorts ${PASSIVEPORTS_START} ${PASSIVEPORTS_END}" > /etc/proftpd/conf.d/passive_ports.conf - -############ MAX CLIENTS - -MAXCLIENTS=${MAXCLIENTS:-30} -MAXCLIENTSPERHOST=${MAXCLIENTSPERHOST:-5} - -echo "Maxclients ${MAXCLIENTS}" > /etc/proftpd/conf.d/maxclients.conf -echo "MaxClientsPerHost ${MAXCLIENTSPERHOST}" >> /etc/proftpd/conf.d/maxclients.conf - -############ START CRON - -crond -b - -############ START - -proftpd -n +############ RELOAD +killall -HUP proftpd diff --git a/rootfs/app/entrypoint.sh b/rootfs/app/entrypoint.sh new file mode 100755 index 0000000..d32ac7a --- /dev/null +++ b/rootfs/app/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +############ MASQUERADE +MASQUERADE=${MASQUERADE:-127.0.0.1} +echo "MasqueradeAddress ${MASQUERADE}" > /etc/proftpd/conf.d/masquerade.conf + +############ AUTH +[ ! -f /auth/passwd ] && touch /auth/passwd +chmod 0600 /auth/passwd +chmod 0700 /auth + +############ PASSIVE PORTS +PASSIVEPORTS_START=${PASSIVEPORTS_START:-50000} +PASSIVEPORTS_END=${PASSIVEPORTS_END:-50050} +echo "PassivePorts ${PASSIVEPORTS_START} ${PASSIVEPORTS_END}" > /etc/proftpd/conf.d/passive_ports.conf + +############ MAX CLIENTS +MAXCLIENTS=${MAXCLIENTS:-30} +MAXCLIENTSPERHOST=${MAXCLIENTSPERHOST:-5} +echo "Maxclients ${MAXCLIENTS}" > /etc/proftpd/conf.d/maxclients.conf +echo "MaxClientsPerHost ${MAXCLIENTSPERHOST}" >> /etc/proftpd/conf.d/maxclients.conf + +############ CERT INIT +ENABLE_ACME=${ENABLE_ACME:-no} + +if [ "$ENABLE_ACME" = "1" ] ; then + /app/acme-cert-init.sh +else + /app/cert-init.sh +fi + +############ START CRON +crond -b + +############ START +proftpd -n diff --git a/rootfs/app/refresh-cert.sh b/rootfs/app/refresh-cert.sh new file mode 100755 index 0000000..8297404 --- /dev/null +++ b/rootfs/app/refresh-cert.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +############ FILES + +TLS_CERT=/acme/cert/cert.pem +TLS_KEY=/acme/cert/privkey.pem +TLS_CHAIN=/acme/cert/chain.pem + +[ ! -f "$TLS_CERT" ] && exit 1 +[ ! -f "$TLS_KEY" ] && exit 1 +[ ! -f "$TLS_CHAIN" ] && exit 1 + +############ CHECK CERT KEY ALGO +ALGO=$(openssl x509 -in "$TLS_CERT"-text | sed -n 's/\ *Public Key Algorithm: //p' | tr '\n') + +############ UPDATE cert config if needed +if [ "$ALGO" = "id-ecPublicKey" ] ; then +cat > /etc/proftpd/conf.d/certificate.conf < + TLSECCertificateFile "$TLS_CERT" + TLSECCertificateKeyFile "$TLS_KEY" + TLSCertificateChainFile "$TLS_CHAIN" + +EOF +fi + +if [ "$ALGO" = "rsaEncryption" ] ; then +cat > /etc/proftpd/conf.d/certificate.conf < + TLSRSACertificateFile "$TLS_CERT" + TLSRSACertificateKeyFile "$TLS_KEY" + TLSCertificateChainFile "$TLS_CHAIN" + +EOF +fi + +############ RELOAD PROFTPD IF RUNNING +pidof proftpd >/dev/null && killall -HUP proftpd diff --git a/rootfs/etc/periodic/daily/acme-cron.sh b/rootfs/etc/periodic/daily/acme-cron.sh new file mode 120000 index 0000000..f8fce15 --- /dev/null +++ b/rootfs/etc/periodic/daily/acme-cron.sh @@ -0,0 +1 @@ +../../../app/acme-cron.sh \ No newline at end of file diff --git a/rootfs/etc/periodic/hourly/cron.sh b/rootfs/etc/periodic/hourly/cron.sh new file mode 120000 index 0000000..a7f5a39 --- /dev/null +++ b/rootfs/etc/periodic/hourly/cron.sh @@ -0,0 +1 @@ +../../../app/cron.sh \ No newline at end of file diff --git a/custom.conf b/rootfs/etc/proftpd/conf.d/custom.conf similarity index 100% rename from custom.conf rename to rootfs/etc/proftpd/conf.d/custom.conf