Compare commits

...

35 Commits

Author SHA1 Message Date
Paolo Asperti 799895baa0
healthcheck timeout
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-02-20 19:02:28 +01:00
Paolo Asperti dcf768fd21
updated default proftpd config
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-02-15 18:55:29 +01:00
Paolo Asperti 9137f439f1
updated default proftpd config
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-02-15 18:50:22 +01:00
Paolo Asperti fade210106
typo fix
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-02-14 17:36:14 +01:00
Paolo Asperti e1e1e1ee29
updated default proftpd config
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-02-14 17:24:54 +01:00
Paolo Asperti 8dcdd4a30a
default dirs
continuous-integration/drone/push Build is passing Details
Vulnerability Scan / Daily Vulnerability Scan (push) Successful in 15s Details
2024-02-12 12:39:50 +01:00
Paolo Asperti 8289114ee6
sql auth and healthcheck
continuous-integration/drone/push Build is passing Details
2024-02-11 21:09:40 +01:00
Paolo Asperti 9fca459f93
cron reload when acme enabled 2024-02-11 19:49:51 +01:00
Paolo Asperti d5c00a0308
fix startup with acme
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-01-28 11:20:13 +01:00
Paolo Asperti 08134aefa5
periodic vulnerability scan
continuous-integration/drone/push Build is passing Details
2024-01-25 09:11:50 +01:00
Paolo Asperti e92da50d28
fix ftpasswd warning
continuous-integration/drone/push Build is passing Details
2024-01-25 09:03:48 +01:00
Paolo Asperti 5eaede5ca8
typo
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-01-25 08:26:43 +01:00
Paolo Asperti 83d15f9436
Merge branch 'master' of ssh://git.asperti.com:1022/paspo/docker-ftps 2024-01-24 00:29:56 +01:00
Paolo Asperti 26f542354d
removed arm arch
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-01-23 22:30:59 +01:00
Paolo Asperti 962624e294
embedded cert.sh
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/tag Build was killed Details
2024-01-23 17:22:00 +01:00
Paolo Asperti 488acf16c1
gitea container repo
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-09-27 15:27:01 +02:00
Paolo Asperti 5a78015465
added arm arch, for 32bit rpi
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-06-11 18:04:53 +02:00
Paolo Asperti 07629dfc63
multiarch
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-06-11 00:34:39 +02:00
Paolo Asperti 9acb740486
multiarch
continuous-integration/drone/tag Build is passing Details
2022-06-10 23:55:53 +02:00
Paolo Asperti 476ba9bb82
multiarch
continuous-integration/drone/tag Build is passing Details
2022-06-10 23:54:52 +02:00
Paolo Asperti ac93e435be
multiarch
continuous-integration/drone/tag Build was killed Details
2022-06-10 23:49:26 +02:00
Paolo Asperti f4579d2608
multiarch
continuous-integration/drone/tag Build was killed Details
2022-06-10 23:47:26 +02:00
Paolo Asperti 4ea2ba1fb8
multiarch
continuous-integration/drone/push Build was killed Details
2022-06-10 23:44:45 +02:00
Paolo Asperti 342593fb8b
some docs update
continuous-integration/drone/tag Build is passing Details
2022-03-29 10:14:18 +02:00
Paolo Asperti 0bfeed1515
MAXCLIENTS configuration 2022-03-29 10:14:11 +02:00
Paolo Asperti ee03f1ed43
fix drone
continuous-integration/drone/tag Build is passing Details
2022-01-13 08:58:13 +01:00
Paolo Asperti 8c2fcfc4a1
Automatic cert reload via cron
continuous-integration/drone/tag Build is passing Details
2022-01-13 08:48:57 +01:00
Paolo Asperti f34ea92952
fix drone
continuous-integration/drone/tag Build is passing Details
2021-11-03 09:28:55 +01:00
Paolo Asperti 1769ab4503
support for EC certs
continuous-integration/drone/tag Build is failing Details
2021-11-03 09:25:54 +01:00
Paolo Asperti 89eab906e4
updated drone config 2021-11-03 09:25:42 +01:00
Paolo Asperti f837bda293
added dependancy
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone/push Build is passing Details
2020-08-12 16:13:34 +02:00
Paolo Asperti 9b26bdf411
better support for customization
continuous-integration/drone/push Build is passing Details
2020-08-12 16:07:38 +02:00
Paolo Asperti 750799ccdc
added extra proftod example config to readme
continuous-integration/drone/push Build is passing Details
2020-08-12 15:22:16 +02:00
Paolo Asperti 967cb2aaf5
fixed docker publishing
continuous-integration/drone/tag Build is passing Details
2020-08-12 15:17:46 +02:00
Paolo Asperti 51f2e1f2d4
fixed docker publishing
continuous-integration/drone/tag Build is passing Details
2020-08-12 15:14:16 +02:00
22 changed files with 759 additions and 86 deletions

View File

@ -1,35 +1,166 @@
kind: pipeline
name: default
type: docker
name: linux-amd64
platform:
arch: amd64
os: linux
steps:
- name: build
image: plugins/docker:linux-amd64
pull: always
settings:
daemon_off: false
dockerfile: Dockerfile
dry_run: true
repo: docker.asperti.com/paspo/ftps
tags:
- latest
when:
event:
exclude:
- tag
- push
- name: build_and_publish
image: plugins/docker:linux-amd64
pull: always
settings:
auto_tag: true
daemon_off: false
dockerfile: Dockerfile
force_tag: true
password:
from_secret: docker_password
registry: docker.asperti.com
repo: docker.asperti.com/paspo/ftps
tags:
- latest
username:
from_secret: docker_username
tags:
- ${DRONE_TAG}-linux-amd64
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-amd64
- ${DRONE_SEMVER_MAJOR}-linux-amd64
when:
event:
- tag
- tag
- name: build_and_publish2
image: plugins/docker:linux-amd64
settings:
dockerfile: Dockerfile
force_tag: true
password:
from_secret: gitea_docker_password
registry: git.asperti.com
repo: git.asperti.com/paspo/docker-ftps
username:
from_secret: gitea_docker_username
tags:
- ${DRONE_TAG}-linux-amd64
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-amd64
- ${DRONE_SEMVER_MAJOR}-linux-amd64
when:
event:
- tag
---
kind: pipeline
type: docker
name: linux-arm64
platform:
arch: arm64
os: linux
steps:
- name: build
image: plugins/docker:linux-arm64
settings:
dockerfile: Dockerfile
dry_run: true
repo: docker.asperti.com/paspo/ftps
when:
event:
- push
- name: build_and_publish
image: plugins/docker:linux-arm64
settings:
dockerfile: Dockerfile
force_tag: true
password:
from_secret: docker_password
registry: docker.asperti.com
repo: docker.asperti.com/paspo/ftps
username:
from_secret: docker_username
tags:
- ${DRONE_TAG}-linux-arm64
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-arm64
- ${DRONE_SEMVER_MAJOR}-linux-arm64
when:
event:
- tag
- name: build_and_publish2
image: plugins/docker:linux-arm64
settings:
dockerfile: Dockerfile
force_tag: true
password:
from_secret: gitea_docker_password
registry: git.asperti.com
repo: git.asperti.com/paspo/docker-ftps
username:
from_secret: gitea_docker_username
tags:
- ${DRONE_TAG}-linux-arm64
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-arm64
- ${DRONE_SEMVER_MAJOR}-linux-arm64
when:
event:
- tag
---
kind: pipeline
type: docker
name: manifest
steps:
- name: manifest
image: plugins/manifest
settings:
force_tag: true
ignore_missing: true
spec: manifest.tmpl
username:
from_secret: docker_username
password:
from_secret: docker_password
tags:
- latest
- ${DRONE_TAG}
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}
- ${DRONE_SEMVER_MAJOR}
when:
event:
- tag
- name: manifest2
image: plugins/manifest
settings:
force_tag: true
ignore_missing: true
spec: manifest2.tmpl
username:
from_secret: gitea_docker_username
password:
from_secret: gitea_docker_password
tags:
- latest
- ${DRONE_TAG}
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}
- ${DRONE_SEMVER_MAJOR}
when:
event:
- tag
trigger:
event:
- tag
depends_on:
- linux-amd64
- linux-arm64

View File

@ -0,0 +1,31 @@
name: Vulnerability Scan
on:
schedule:
- cron: "0 14 * * *"
workflow_dispatch:
jobs:
scan:
name: Daily Vulnerability Scan
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Pull docker image
run: docker pull docker.asperti.com/paspo/ftps:latest
# run: docker pull git.asperti.com/paspo/docker-ftps:latest
- name: Run Trivy vulnerability scanner
id: scan
uses: aquasecurity/trivy-action@master
with:
image-ref: "docker.asperti.com/paspo/ftps:latest"
# image-ref: "git.asperti.com/paspo/docker-ftps:latest"
format: "json"
output: "trivy-results.json"
# if some vulnerability is found, we fail
- name: check output
run: if [ $(jq '.Results[0].Vulnerabilities|length' trivy-results.json) -ne "0" ] ; then exit 1 ; fi

View File

@ -1,13 +1,14 @@
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-utils openssl perl && \
mkdir -p /var/run/proftpd
apk -U add proftpd proftpd-mod_tls proftpd-mod_ifsession \
proftpd-mod_deflate proftpd-mod_geoip proftpd-mod_sql_sqlite \
proftpd-utils openssl perl acme.sh lftp sqlite && \
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 rootfs /
RUN chmod +x /run.sh
ENTRYPOINT ["/run.sh"]
HEALTHCHECK --interval=2m --timeout=3s \
CMD /app/healthcheck.sh || exit 1
ENTRYPOINT ["/app/entrypoint.sh"]

132
README.md
View File

@ -4,6 +4,14 @@
Simple container for FTP+TLS+authentication
Supported architectures:
| Architecture | Available | Tag |
| :----: | :----: | ---- |
| x86-64 | ✅ | \<version tag\>-linux-amd64 |
| arm64 | ✅ | \<version tag\>-linux-arm64 |
| armhf | ❌ | - |
## build
```bash
@ -22,7 +30,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 +41,7 @@ docker run -d --name my-ftps \
docker.asperti.com/paspo/ftps
```
## docker-compose
## docker-compose (external certificate)
```yaml
version: "3"
@ -45,18 +53,82 @@ services:
ports:
- "21:21"
- "20:20"
- "50000-50500:50000-50500"
- "21210-21220:21210-21220"
volumes:
- "/srv/ftps/auth:/auth"
- "/srv/ftps/conf:/etc/proftpd/custom.conf.d:ro"
- "/srv/ftps/data:/home"
- "/etc/letsencrypt:/certs"
- "/etc/letsencrypt:/certs:ro"
environment:
- MASQUERADE=ftp.mydomain.com
- PASSIVEPORTS_START=21210
- PASSIVEPORTS_END=21220
- MAXCLIENTS=500
- 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).
In any case, you also have to enable a matching range of exposed ports.
## notes
Please note that you have to restart the container (or send sighup to proftpd) whenever the certificate is renewed.
@ -72,3 +144,55 @@ docker exec -ti my-ftps ftpasswd --passwd --name=paolo --uid=1000 --home=/home/p
```
You also have to create and chown the user's home folder.
## sql db for user authentication
It is possible to use a sqlite db for user authentication, just add `SQLITE_AUTH=1` to the environment:
```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"
- "/etc/letsencrypt:/certs:ro"
environment:
- SQLITE_AUTH=1
- MASQUERADE=ftp.mydomain.com
- PASSIVEPORTS_START=21210
- PASSIVEPORTS_END=21220
- MAXCLIENTS=500
- 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
```
Now, instead of using `/auth/passwd`, proftpd is using `/auth/ftpd.db`.
To create a new user, you must now update this db.
To create a new user:
```bash
docker exec -ti my-ftps sqlite3 sqlite3 /auth/ftpd.db <<EOF
INSERT OR IGNORE INTO users (userid,passwd,uid,gid,homedir,shell) VALUES ('new_user','',1000,1000,'/home/new_user','/bin/false');
INSERT OR IGNORE INTO groups (groupname,gid,members) VALUES ('new_user',1000,'new_user');
EOF
```
To update a password:
```bash
PASSWD_SHA=$(echo -n ChangeThisPass | mkpasswd -m sha512)
docker exec -ti my-ftps sqlite3 sqlite3 /auth/ftpd.db <<EOF
UPDATE users SET passwd='$PASSWD_SHA' WHERE userid='new_user';
EOF
```

View File

@ -1,25 +0,0 @@
AuthOrder mod_auth_file.c
AuthUserFile /auth/passwd
RequireValidShell off
ScoreBoardFile /run/proftpd/scoreboard
AllowOverwrite on
AllowStoreRestart On
AllowRetrieveRestart On
WtmpLog off
UseReverseDNS off
DefaultRoot ~
Maxclients 30
MaxClientsPerHost 5
<IfModule mod_tls.c>
TLSEngine on
TLSVerifyClient off
TLSRenegotiate none
TLSProtocol TLSv1.2
TLSRSACertificateFile /etc/proftpd/cert.pem
TLSRSACertificateKeyFile /etc/proftpd/privkey.pem
TLSCertificateChainFile /etc/proftpd/chain.pem
TLSCipherSuite "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:RC4-SHA:AES256-GCM-SHA384:AES256-SHA256:CAMELLIA256-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4"
TLSOptions NoSessionReuseRequired AllowClientRenegotiations
TLSRequired on
</IfModule>

19
manifest.tmpl Normal file
View File

@ -0,0 +1,19 @@
image: docker.asperti.com/paspo/ftps:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: docker.asperti.com/paspo/ftps:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: docker.asperti.com/paspo/ftps:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
variant: v8
architecture: arm64
os: linux

19
manifest2.tmpl Normal file
View File

@ -0,0 +1,19 @@
image: git.asperti.com/paspo/docker-ftps:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: git.asperti.com/paspo/docker-ftps:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: git.asperti.com/paspo/docker-ftps:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
variant: v8
architecture: arm64
os: linux

30
rootfs/app/acme-cert-init.sh Executable file
View File

@ -0,0 +1,30 @@
#!/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}
echo "Certificate ready"
else
/app/acme-refresh-cert.sh
echo "Certificate ready"
fi

8
rootfs/app/acme-cron.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
if [ "$ENABLE_ACME" = "1" ] ; then
/usr/bin/acme.sh --cron --home /acme
fi
############ RELOAD
pidof proftpd >/dev/null && killall -HUP proftpd

38
rootfs/app/acme-refresh-cert.sh Executable file
View File

@ -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 <<EOF
<IfModule mod_tls.c>
TLSECCertificateFile "$TLS_CERT"
TLSECCertificateKeyFile "$TLS_KEY"
TLSCertificateChainFile "$TLS_CHAIN"
</IfModule>
EOF
fi
if [ "$ALGO" = "rsaEncryption" ] ; then
cat > /etc/proftpd/conf.d/certificate.conf <<EOF
<IfModule mod_tls.c>
TLSRSACertificateFile "$TLS_CERT"
TLSRSACertificateKeyFile "$TLS_KEY"
TLSCertificateChainFile "$TLS_CHAIN"
</IfModule>
EOF
fi
############ RELOAD PROFTPD IF RUNNING
pidof proftpd >/dev/null && killall -HUP proftpd

37
rootfs/app/cert-init.sh Executable file
View File

@ -0,0 +1,37 @@
#!/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
############ 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 <<EOF
<IfModule mod_tls.c>
TLSECCertificateFile /etc/proftpd/cert.pem
TLSECCertificateKeyFile /etc/proftpd/privkey.pem
TLSCertificateChainFile /etc/proftpd/chain.pem
</IfModule>
EOF
fi
if [ "$ALGO" = "rsaEncryption" ] ; then
cat > /etc/proftpd/conf.d/certificate.conf <<EOF
<IfModule mod_tls.c>
TLSRSACertificateFile /etc/proftpd/cert.pem
TLSRSACertificateKeyFile /etc/proftpd/privkey.pem
TLSCertificateChainFile /etc/proftpd/chain.pem
</IfModule>
EOF
fi
md5sum "$TLS_CERT" > /app/sums
echo "Certificate ready"

46
rootfs/app/cron.sh Executable file
View File

@ -0,0 +1,46 @@
#!/bin/sh
############ 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
############ 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
cat > /etc/proftpd/conf.d/certificate.conf <<EOF
<IfModule mod_tls.c>
TLSECCertificateFile /etc/proftpd/cert.pem
TLSECCertificateKeyFile /etc/proftpd/privkey.pem
TLSCertificateChainFile /etc/proftpd/chain.pem
</IfModule>
EOF
fi
if [ "$ALGO" = "rsaEncryption" ] ; then
cat > /etc/proftpd/conf.d/certificate.conf <<EOF
<IfModule mod_tls.c>
TLSRSACertificateFile /etc/proftpd/cert.pem
TLSRSACertificateKeyFile /etc/proftpd/privkey.pem
TLSCertificateChainFile /etc/proftpd/chain.pem
</IfModule>
EOF
fi
md5sum "$TLS_CERT" > /app/sums
############ RELOAD
killall -HUP proftpd

78
rootfs/app/entrypoint.sh Executable file
View File

@ -0,0 +1,78 @@
#!/bin/sh
############ CREATE DIRS
mkdir -p /auth /logs
chown proftpd /auth /logs
chmod u+rw /auth /logs
chmod g-w /auth /logs
############ 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
############ INIT DB if needed
SQLITE_AUTH=${SQLITE_AUTH:-no}
if [ "$SQLITE_AUTH" = "1" ] ; then
if [ ! -f /auth/ftpd.db ] ; then
sqlite3 /auth/ftpd.db < /app/init.sql
fi
fi
############ GENERATE RANDOM PASSWORD FOR HEALTHCHECK
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 20 > /app/healthcheck.pwd
chmod 600 /app/healthcheck.pwd
############ UPDATE HEALTHCHECK CREDS
HEALTHCHECK_UID=1999
mkdir -p /home/healthcheck
chown ${HEALTHCHECK_UID}:${HEALTHCHECK_UID} /home/healthcheck
if [ "$SQLITE_AUTH" = "1" ] ; then
PASSWD_SHA=$(cat /app/healthcheck.pwd | mkpasswd -m sha512)
sqlite3 /auth/ftpd.db <<EOF
INSERT OR IGNORE INTO users (userid,passwd,uid,gid,homedir,shell) VALUES ('healthcheck','',${HEALTHCHECK_UID},${HEALTHCHECK_UID},'/home/healthcheck','/bin/false');
INSERT OR IGNORE INTO groups (groupname,gid,members) VALUES ('healthcheck',${HEALTHCHECK_UID},'healthcheck');
UPDATE users SET passwd='$PASSWD_SHA' WHERE userid='healthcheck';
EOF
else
cat /app/healthcheck.pwd | ftpasswd --stdin --passwd --name=healthcheck \
--uid=${HEALTHCHECK_UID} \
--home=/home/healthcheck --sha512 --shell=/bin/false --file=/auth/passwd
fi
############ CONFIGURE AUTH
if [ "$SQLITE_AUTH" = "1" ] ; then
echo "AuthOrder mod_sql.c" > /etc/proftpd/conf.d/auth.conf
else
echo "AuthOrder mod_auth_file.c" > /etc/proftpd/conf.d/auth.conf
fi
############ START CRON
crond -b
############ START
proftpd -n

7
rootfs/app/healthcheck.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
USER=healthcheck
LFTP_PASSWORD=$(cat /app/healthcheck.pwd)
lftp -e "set ssl-allow true; set ftp:ssl-force true; set ftp:passive-mode true; set ssl:verify-certificate false; set net:timeout 5; set net:max-retries 1; open -u ${USER},${LFTP_PASSWORD} ftp://127.0.0.1; ls; bye" > /dev/null
echo $?

14
rootfs/app/init.sql Normal file
View File

@ -0,0 +1,14 @@
CREATE TABLE `users` (
userid VARCHAR(30) NOT NULL UNIQUE,
passwd VARCHAR(80) NOT NULL,
uid INTEGER UNIQUE,
gid INTEGER,
homedir VARCHAR(255),
shell VARCHAR(255),
last_accessed DATETIME
);
CREATE TABLE `groups` (
groupname VARCHAR(30) NOT NULL UNIQUE,
gid INTEGER NOT NULL,
members VARCHAR(255)
);

38
rootfs/app/refresh-cert.sh Executable file
View File

@ -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 <<EOF
<IfModule mod_tls.c>
TLSECCertificateFile "$TLS_CERT"
TLSECCertificateKeyFile "$TLS_KEY"
TLSCertificateChainFile "$TLS_CHAIN"
</IfModule>
EOF
fi
if [ "$ALGO" = "rsaEncryption" ] ; then
cat > /etc/proftpd/conf.d/certificate.conf <<EOF
<IfModule mod_tls.c>
TLSRSACertificateFile "$TLS_CERT"
TLSRSACertificateKeyFile "$TLS_KEY"
TLSCertificateChainFile "$TLS_CHAIN"
</IfModule>
EOF
fi
############ RELOAD PROFTPD IF RUNNING
pidof proftpd >/dev/null && killall -HUP proftpd

View File

@ -0,0 +1 @@
../../../app/acme-cron.sh

View File

@ -0,0 +1 @@
../../../app/cron.sh

View File

@ -0,0 +1,49 @@
AuthUserFile /auth/passwd
RequireValidShell off
ScoreBoardFile /run/proftpd/scoreboard
AllowOverwrite on
AllowStoreRestart On
AllowRetrieveRestart On
WtmpLog off
UseReverseDNS off
DefaultRoot ~
<IfModule mod_tls.c>
TLSEngine on
TLSVerifyClient off
TLSRenegotiate none
TLSProtocol TLSv1.2 TLSv1.3
TLSOptions NoSessionReuseRequired AllowClientRenegotiations
TLSRequired on
</IfModule>
<IfModule mod_delay.c>
DelayOnEvent FailedLogin 5s
DelayTable /run/proftpd/proftpd.delay
</IfModule>
<IfModule mod_deflate.c>
DeflateEngine on
</IfModule>
<IfModule mod_sql.c>
<IfModule mod_sql_sqlite.c>
SQLBackend sqlite3
SQLConnectInfo /auth/ftpd.db
SQLEngine On
SQLAuthenticate users
SQLAuthTypes OpenSSL Crypt
SQLUserInfo users userid passwd uid gid homedir shell
SQLGroupInfo groups groupname gid members
SQLNamedQuery last_accessed UPDATE "last_accessed = DATETIME('now') WHERE userid='%u'" users
SQLLog PASS last_accessed
SQLMinId 33
SQLDefaultUID 33
SQLDefaultGID 33
RequireValidShell off
</IfModule>
</IfModule>
Include /etc/proftpd/custom.conf.d/

View File

@ -0,0 +1,56 @@
# This is the directory where DSO modules reside
ModulePath /usr/lib/proftpd
# Allow only user root to load and unload modules, but allow everyone
# to see which modules have been loaded
ModuleControlsACLs insmod,rmmod allow user root
ModuleControlsACLs lsmod allow user *
Include /etc/proftpd/modules.d/
ServerName "ProFTPD Default Installation"
ServerType standalone
# Port 21 is the standard FTP port.
Port 21
# Don't use IPv6 support by default.
UseIPv6 off
# Umask 022 is a good standard umask to prevent new dirs and files
# from being group and world writable.
Umask 022
# To prevent DoS attacks, set the maximum number of child processes
# to 30. If you need to allow more than 30 concurrent connections
# at once, simply increase this value. Note that this ONLY works
# in standalone mode, in inetd mode you should use an inetd server
# that allows you to limit maximum number of processes per service
# (such as xinetd).
MaxInstances 30
# Set the user and group under which the server will run.
User proftpd
Group proftpd
# To cause every FTP user to be "jailed" (chrooted) into their home
# directory, uncomment this line.
#DefaultRoot ~
# Normally, we want files to be overwriteable.
AllowOverwrite on
DefaultServer on
ShowSymlinks on
TimeoutNoTransfer 600
TimeoutStalled 600
TimeoutIdle 1200
DisplayLogin welcome.msg
DisplayChdir .message true
ListOptions "-l"
DenyFilter \*.*/
Include /etc/proftpd/conf.d/

4
rootfs/etc/shells Normal file
View File

@ -0,0 +1,4 @@
# valid login shells
/bin/sh
/bin/ash
/bin/false

34
run.sh
View File

@ -1,34 +0,0 @@
#!/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
############ 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
############ 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
############ START
proftpd -n