First Tests with SveltiaCMS
Using Hugo as static site renderer is probably the most secure form for a WebSite. Unfortunately, the format of the Markdown files, the local storage and publishing process is far from usable for the average end-user.
With SveltiaCMS and a gitlab CI/CD-Pipeline, that renders the GitLab Repository with the content into a self-contained Caddy container-image, there is no longer any interpreted code on the server, that might be exploited.
backend:
name: gitlab
repo: wherever/name
branch: develop
auth_type: pkce # Required for pkce
app_id: b....9
api_root: https://..../api/v4
base_url: https://....
auth_endpoint: oauth/authorize
media_folder: static/images
public_folder: /assets/images
site_url: https://wolfgang-jung.net
collections:
- name: 'blog'
label: 'Blog'
folder: 'content/posts'
create: true
slug: '{{slug}}'
path: '{{year}}/{{year}}-{{month}}-{{day}}-{{slug}}/index'
summary: "{{title}} — {{date | date('YYYY-MM-DD')}} ({{dirname}}) {{draft}}"
preview_path: 'posts/{{year}}-{{month}}-{{day}}-{{slug}}/'
preview_path_date_field: 'date'
sortable_fields: [title, date]
media_folder: ''
public_folder: ''
editor:
preview: false
fields:
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Draft', name: 'draft', widget: 'boolean' }
- { label: 'Publish Date', name: 'date', widget: 'datetime', format: 'YYYY-MM-DDTHH:mm:ssZ', picker_utc: false }
- { label: 'Body', name: 'body', widget: 'markdown' }
With the matching config.yaml for hugo, I can now edit the pages online, without fearing the next Wordpress attack.
baseURL: "https://wolfgang-jung.net/"
language:
locale: "de-DE"
title: "Wolfgang Jung - Blog"
theme: "whiteplain"
paginate: 10
paginatePath: "page"
pygmentsCodeFences: true
relativeURLs: false
DisqusShortname: null
ignoreLogs: ['warning-goldmark-raw-html']
permalinks:
posts: "/posts/:contentbasename/"
imaging:
# See https://github.com/disintegration/imaging
resampleFilter: "lanczos"
quality: 75
params:
dateFormat: "2006-01-02 15:04"
showShareIcons: false
blackfriday:
HrefTargetBlank: true
Smartypants: true
SmartypantsQuotesNBSP: false
AngledQuotes: true
Fractions: true
SmartDashes: true
LatexDashes: true
TaskLists: true
PlainIDAnchors: true
menu:
main:
- name: "About"
weight: 4
identifier: "about"
url: "/about/"
- name: "CV"
weight: 3
identifier: "cv"
url: "/cv/"
- name: "Datenschutz"
weight: 2
identifier: "datenschutz"
url: "/datenschutz/"
rel: "nofollow"
footer:
- name: "Impressum"
weight: 1
identifier: "about"
url: "/about/"
- name: "Datenschutz"
weight: 2
identifier: "datenschutz"
url: "/datenschutz/"
The Caddyfile must list the appropriate CSP information:
{
auto_https off
servers {
protocols h1
}
}
http://localhost:8080 {
root * /usr/share/caddy
@cachedFiles {
path *.jpg *.jpeg *.png *.gif *.ico *.css *.js
}
header @cachedFiles Cache-Control "public, max-age=604800, must-revalidate"
header Content-Security-Policy "default-src 'self';
script-src 'report-sample' 'self' https://unpkg.com/ 'unsafe-eval' 'wasm-unsafe-eval';
style-src 'unsafe-inline' 'self' https://fonts.googleapis.com/;
object-src 'none';
base-uri 'self';
connect-src 'self' data: https://unpkg.com/ https://git.wolle.dev/;
font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net/;
frame-src 'self';
img-src 'self' data: blob: https://secure.gravatar.com/;
manifest-src 'self' blob:;
media-src 'self';
worker-src 'none';"
file_server {
# Serve foo.css.br / foo.css.gz if the client supports it,
# preferring brotli over gzip
precompressed br gzip
}
}
The container is build straight forward as multi-stage docker build:
FROM hugomods/hugo:debian-go AS hugo-builder
RUN apt-get update && \
apt-get install -y brotli && \
apt-get clean
COPY / /src/
RUN hugo --source="/src" --theme="whiteplain" --destination="/dest" -F
RUN find /dest/about /dest/cv /dest/posts -name \*.jpg -not -name \*_hu_\* -delete && \
find /dest/about /dest/cv /dest/posts -name \*.jpeg -not -name \*_hu_\* -delete && \
find /dest/about /dest/cv /dest/posts -name \*.png -not -name \*_hu_\* -delete && \
find /dest '(' -name \*.css -o -name \*.html -o -name \*.js ')' -print0 | xargs -0 -n1 gzip -k -9 && \
find /dest '(' -name \*.css -o -name \*.html -o -name \*.js ')' -print0 | xargs -0 -n1 brotli -k -9 && \
chown -R 1000:1000 /dest/
FROM caddy:alpine
USER 1000:1000
COPY --from=hugo-builder /dest /usr/share/caddy/
COPY Caddyfile /etc/caddy/Caddyfile
WORKDIR /etc/caddy/
CMD ["caddy", "run"]
EXPOSE 8080