Compare commits

...

34 Commits

Author SHA1 Message Date
3f6d7c34d5 fix build pipeline
All checks were successful
Container Publish / on-success-skip (push) Has been skipped
Container Publish / build-image (amd64) (push) Successful in 1m4s
Container Publish / build-image (arm64) (push) Successful in 26s
Container Publish / update docker manifest (push) Successful in 10s
Vulnerability Scan / Daily Vulnerability Scan (amd64) (push) Successful in 22s
Vulnerability Scan / Daily Vulnerability Scan (arm64) (push) Successful in 6s
2025-10-03 07:46:10 +02:00
76f62bafb1 switched from drone to gitea actions
Some checks failed
Container Publish / on-success-skip (push) Has been skipped
Container Publish / build-image (arm64) (push) Successful in 11s
Container Publish / build-image (amd64) (push) Successful in 11s
Container Publish / update docker manifest (push) Successful in 9s
Vulnerability Scan / Daily Vulnerability Scan (arm64) (push) Failing after 5s
Vulnerability Scan / Daily Vulnerability Scan (amd64) (push) Failing after 7s
2025-06-08 13:41:42 +02:00
9c460b101e multiarch trivy
Some checks failed
continuous-integration/drone/tag Build is passing
Vulnerability Scan / Daily Vulnerability Scan (push) Failing after 13s
continuous-integration/drone/push Build is passing
2025-01-17 08:03:14 +01:00
a4203ed149 fix typo
All checks were successful
continuous-integration/drone/push Build is passing
2025-01-16 23:59:45 +01:00
f799ffadb4 trivy cache not needed anymore
All checks were successful
continuous-integration/drone/push Build is passing
2025-01-16 23:44:42 +01:00
7758e98bd3 test trivy server
All checks were successful
continuous-integration/drone/push Build is passing
2025-01-16 23:37:52 +01:00
25eb14f93b cached trivy db
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Vulnerability Scan / Daily Vulnerability Scan (push) Successful in 9m24s
2024-12-02 16:45:42 +01:00
f4c0042ab2 packages upgrade
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-06-16 16:30:25 +02:00
6ed58a676f removed alternate repository 2024-06-16 16:21:12 +02:00
799895baa0 healthcheck timeout
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-20 19:02:28 +01:00
dcf768fd21 updated default proftpd config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-15 18:55:29 +01:00
9137f439f1 updated default proftpd config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-15 18:50:22 +01:00
fade210106 typo fix
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-14 17:36:14 +01:00
e1e1e1ee29 updated default proftpd config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-02-14 17:24:54 +01:00
8dcdd4a30a default dirs
All checks were successful
continuous-integration/drone/push Build is passing
Vulnerability Scan / Daily Vulnerability Scan (push) Successful in 15s
2024-02-12 12:39:50 +01:00
8289114ee6 sql auth and healthcheck
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-11 21:09:40 +01:00
9fca459f93 cron reload when acme enabled 2024-02-11 19:49:51 +01:00
d5c00a0308 fix startup with acme
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-01-28 11:20:13 +01:00
08134aefa5 periodic vulnerability scan
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-25 09:11:50 +01:00
e92da50d28 fix ftpasswd warning
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-25 09:03:48 +01:00
5eaede5ca8 typo
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-01-25 08:26:43 +01:00
83d15f9436 Merge branch 'master' of ssh://git.asperti.com:1022/paspo/docker-ftps 2024-01-24 00:29:56 +01:00
26f542354d removed arm arch
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-01-23 22:30:59 +01:00
962624e294 embedded cert.sh
Some checks failed
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build was killed
2024-01-23 17:22:00 +01:00
488acf16c1 gitea container repo
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build was killed
2022-09-27 15:27:01 +02:00
5a78015465 added arm arch, for 32bit rpi
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-06-11 18:04:53 +02:00
07629dfc63 multiarch
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-06-11 00:34:39 +02:00
9acb740486 multiarch
All checks were successful
continuous-integration/drone/tag Build is passing
2022-06-10 23:55:53 +02:00
476ba9bb82 multiarch
All checks were successful
continuous-integration/drone/tag Build is passing
2022-06-10 23:54:52 +02:00
ac93e435be multiarch
Some checks failed
continuous-integration/drone/tag Build was killed
2022-06-10 23:49:26 +02:00
f4579d2608 multiarch
Some checks failed
continuous-integration/drone/tag Build was killed
2022-06-10 23:47:26 +02:00
4ea2ba1fb8 multiarch
Some checks failed
continuous-integration/drone/push Build was killed
2022-06-10 23:44:45 +02:00
342593fb8b some docs update
All checks were successful
continuous-integration/drone/tag Build is passing
2022-03-29 10:14:18 +02:00
0bfeed1515 MAXCLIENTS configuration 2022-03-29 10:14:11 +02:00
19 changed files with 591 additions and 87 deletions

View File

@@ -1,24 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: build_and_publish
image: plugins/docker:linux-amd64
settings:
force_tag: true
password:
from_secret: docker_password
registry: docker.asperti.com
repo: docker.asperti.com/paspo/ftps
username:
from_secret: docker_username
tags:
- latest
- ${DRONE_TAG}
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}
- ${DRONE_SEMVER_MAJOR}
trigger:
event:
- tag

View File

@@ -0,0 +1,73 @@
---
name: Container Publish
env:
REGISTRY: docker.asperti.com
REPOSITORY: paspo/ftps
on:
push:
schedule:
- cron: "0 12 * * 3"
workflow_dispatch:
workflow_call:
workflow_run:
workflows: [vulnscan.yaml]
types: [completed]
jobs:
on-success-skip:
runs-on:
labels: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- run: exit_with_success
build-image:
runs-on:
labels: [ubuntu-latest, "arch-${{ matrix.arch }}"]
container:
image: catthehacker/ubuntu:act-latest
strategy:
matrix:
arch: [amd64, arm64]
steps:
- uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and publish
run: |
docker build \
--tag ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest-${{ matrix.arch }} \
--platform linux/${{ matrix.arch }} --no-cache -f Dockerfile .
docker push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest-${{ matrix.arch }}
manifest:
name: update docker manifest
needs: build-image
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: latest
run: |
docker manifest create \
${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest \
--amend ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest-amd64 \
--amend ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest-arm64
docker manifest push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest

View File

@@ -0,0 +1,64 @@
---
name: Vulnerability Scan
env:
REGISTRY: docker.asperti.com
REPOSITORY: paspo/ftps
on:
schedule:
- cron: "0 14 * * *"
workflow_dispatch:
workflow_call:
workflow_run:
workflows: [build_and_publish.yaml]
types: [completed]
jobs:
scan:
name: Daily Vulnerability Scan
runs-on:
labels: [ubuntu-latest, "arch-${{ matrix.arch }}"]
container:
image: catthehacker/ubuntu:act-latest
strategy:
matrix:
arch: [amd64, arm64]
steps:
- name: Pull docker image
run: docker pull ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest
- name: Setup trivy
run: |
echo "Installing Trivy for arch: $(uname -m)"
case $(uname -m) in
x86_64)
wget -O /tmp/trivy.deb https://github.com/aquasecurity/trivy/releases/download/v0.58.2/trivy_0.58.2_Linux-64bit.deb ;;
aarch64)
wget -O /tmp/trivy.deb https://github.com/aquasecurity/trivy/releases/download/v0.58.2/trivy_0.58.2_Linux-ARM64.deb ;;
*) exit 1 ;;
esac
dpkg -i /tmp/trivy.deb
- name: Run Trivy vulnerability scanner
id: scan
run: |
trivy --server ${{ secrets.TRIVY_SERVER }} --token ${{ secrets.TRIVY_TOKEN }} image --format json ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest > trivy-results.json
# if some vulnerability is found, we fail
- name: check output
id: vulncount
run: |
echo "VULNCOUNT=$(jq '.Results[0].Vulnerabilities|length' trivy-results.json)" >> ${GITHUB_OUTPUT}
if [ $(jq '.Results[0].Vulnerabilities|length' trivy-results.json) -ne "0" ] ; then exit 1 ; fi
- name: send telegram notification
if: failure()
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }}
format: markdown
message: |
Found **${{ steps.vulncount.outputs.VULNCOUNT }}** vulnerabilities in `${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest`

View File

@@ -1,17 +1,15 @@
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 --no-cache upgrade && \
apk --no-cache 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 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"]
HEALTHCHECK --interval=2m --timeout=3s \
CMD /app/healthcheck.sh || exit 1
ENTRYPOINT ["/app/entrypoint.sh"]

129
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,7 +53,7 @@ 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"
@@ -53,11 +61,74 @@ services:
- "/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.
@@ -73,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
```

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

20
cron.sh → rootfs/app/cert-init.sh Normal file → Executable file
View File

@@ -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 <<EOF
<IfModule mod_tls.c>
@@ -38,8 +33,5 @@ cat > /etc/proftpd/conf.d/certificate.conf <<EOF
EOF
fi
md5sum "$TLS_CERT" > /sums
############ RELOAD
killall -HUP proftpd
md5sum "$TLS_CERT" > /app/sums
echo "Certificate ready"

44
run.sh → rootfs/app/cron.sh Normal file → Executable file
View File

@@ -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,19 +40,7 @@ cat > /etc/proftpd/conf.d/certificate.conf <<EOF
EOF
fi
md5sum "$TLS_CERT" > /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
############ START CRON
crond -b
############ START
proftpd -n
############ 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

@@ -1,4 +1,3 @@
AuthOrder mod_auth_file.c
AuthUserFile /auth/passwd
RequireValidShell off
ScoreBoardFile /run/proftpd/scoreboard
@@ -8,8 +7,6 @@ AllowRetrieveRestart On
WtmpLog off
UseReverseDNS off
DefaultRoot ~
Maxclients 30
MaxClientsPerHost 5
<IfModule mod_tls.c>
TLSEngine on
@@ -25,4 +22,28 @@ MaxClientsPerHost 5
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