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
Kommentare per Mail an post@wolfgang-jung.net.