Compare commits

..

20 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 10s
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
15 changed files with 348 additions and 206 deletions

View File

@@ -1,166 +0,0 @@
kind: pipeline
type: docker
name: linux-amd64
platform:
arch: amd64
os: linux
steps:
- name: build
image: plugins/docker:linux-amd64
settings:
dockerfile: Dockerfile
dry_run: true
repo: docker.asperti.com/paspo/ftps
when:
event:
- push
- name: build_and_publish
image: plugins/docker:linux-amd64
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-amd64
- ${DRONE_SEMVER_MAJOR}.${DRONE_SEMVER_MINOR}-linux-amd64
- ${DRONE_SEMVER_MAJOR}-linux-amd64
when:
event:
- 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,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,9 +1,15 @@
FROM alpine:latest
RUN \
apk -U add proftpd proftpd-mod_tls proftpd-mod_ifsession proftpd-utils openssl perl acme.sh && \
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 rootfs /
HEALTHCHECK --interval=2m --timeout=3s \
CMD /app/healthcheck.sh || exit 1
ENTRYPOINT ["/app/entrypoint.sh"]

View File

@@ -144,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,19 +0,0 @@
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

View File

@@ -1,19 +0,0 @@
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

View File

@@ -23,6 +23,8 @@ if [ ! -f "/acme/cert/cert.pem" ] ; then
--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

View File

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

View File

@@ -1,5 +1,11 @@
#!/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
@@ -29,6 +35,42 @@ 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

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)
);

View File

@@ -1,4 +1,3 @@
AuthOrder mod_auth_file.c
AuthUserFile /auth/passwd
RequireValidShell off
ScoreBoardFile /run/proftpd/scoreboard
@@ -23,4 +22,28 @@ DefaultRoot ~
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