From 33019276818542ac0513d60228ff48b192c5067f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Tue, 29 Jun 2021 18:41:59 +0200 Subject: Initial commit. --- files/filters/hugo-authentication.lua | 331 ++++++++++++++++++++++++++++++++++ files/filters/hugo-highlighting.sh | 138 ++++++++++++++ files/filters/hugo-pre.sh | 15 ++ files/logo.png | Bin 0 -> 16244 bytes files/logo_large.png | Bin 0 -> 105311 bytes files/root_readme | 17 ++ manifests/init.pp | 62 +++++++ templates/cgitrc.epp | 64 +++++++ 8 files changed, 627 insertions(+) create mode 100644 files/filters/hugo-authentication.lua create mode 100755 files/filters/hugo-highlighting.sh create mode 100755 files/filters/hugo-pre.sh create mode 100644 files/logo.png create mode 100644 files/logo_large.png create mode 100644 files/root_readme create mode 100644 manifests/init.pp create mode 100644 templates/cgitrc.epp diff --git a/files/filters/hugo-authentication.lua b/files/filters/hugo-authentication.lua new file mode 100644 index 0000000..df97579 --- /dev/null +++ b/files/filters/hugo-authentication.lua @@ -0,0 +1,331 @@ +-- This script may be used with the auth-filter. Be sure to configure it as you wish. +-- +-- Requirements: +-- luaossl +-- +-- luaposix +-- +-- +local sysstat = require("posix.sys.stat") +local unistd = require("posix.unistd") +local rand = require("openssl.rand") +local hmac = require("openssl.hmac") + +-- +-- +-- Configure these variables for your settings. +-- +-- + +-- A list of password protected repositories along with the users who can access them. +local protected_repos = { + glouglou = { laurent = true, jason = true }, + qt = { jason = true, bob = true } +} + +local public_repos = {} +public_repos["wiki-public" ] = true +public_repos["lyslib" ] = true +public_repos["calp" ] = true +public_repos["texttv" ] = true +public_repos["scheme-monad" ] = true +public_repos["scheme/math-parse" ] = true +public_repos["file-descriptor-graph"] = true + +-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`. +local users = { + hugo = "$6$13z1Pf.U8itrCRX6$g2BZpaMk1CLiT6117paWXB2qdQFRc3rsGWL4iF5h5QbHo27oljTdHk69oQWAvqlVf13aLTUF3nYw65lEp88r/1" +} + +-- Set this to a path this script can write to for storing a persistent +-- cookie secret, which should be guarded. +local secret_filename = "/var/cache/cgit/auth-secret" + +-- +-- +-- Authentication functions follow below. Swap these out if you want different authentication semantics. +-- +-- + +-- Sets HTTP cookie headers based on post and sets up redirection. +function authenticate_post() + local hash = users[post["username"]] + local redirect = validate_value("redirect", post["redirect"]) + + if redirect == nil then + not_found() + return 0 + end + + redirect_to(redirect) + + if hash == nil or hash ~= unistd.crypt(post["password"], hash) then + set_cookie("cgitauth", "") + else + -- One week expiration time + local username = secure_value("username", post["username"], os.time() + 604800) + set_cookie("cgitauth", username) + end + + html("\n") + return 0 +end + + +-- Returns 1 if the cookie is valid and 0 if it is not. +function authenticate_cookie() + + -- Everyone has access to the index page. + -- printenv(os.getenv("CGIT_REPO_NAME")); + if cgit["repo"] == "" then + return 1 + end + + ispublic = public_repos[cgit["repo"]] + -- accepted_users = protected_repos[cgit["repo"]] + if ispublic == true then + -- We return as valid if the repo is public + return 1 + end + + local username = validate_value("username", get_cookie(http["cookie"], "cgitauth")) + if username == nil then + return 0 + else + return 1 + end +end + +-- Prints the html for the login form. +function body() + html("

Authentication Required

") + -- html("HTML = ") + -- html(tostring(cgit["repo"] == "")) + html("
") + html("") + html("") + html("") + html("") + html("") + html("
") + + return 0 +end + + + +-- +-- +-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. +-- +-- + +local actions = {} +actions["authenticate-post"] = authenticate_post +actions["authenticate-cookie"] = authenticate_cookie +actions["body"] = body + +function filter_open(...) + action = actions[select(1, ...)] + + http = {} + http["cookie"] = select(2, ...) + http["method"] = select(3, ...) + http["query"] = select(4, ...) + http["referer"] = select(5, ...) + http["path"] = select(6, ...) + http["host"] = select(7, ...) + http["https"] = select(8, ...) + + cgit = {} + cgit["repo"] = select(9, ...) + cgit["page"] = select(10, ...) + cgit["url"] = select(11, ...) + cgit["login"] = select(12, ...) + +end + +function filter_close() + return action() +end + +function filter_write(str) + post = parse_qs(str) +end + + +-- +-- +-- Utility functions based on keplerproject/wsapi. +-- +-- + +function url_decode(str) + if not str then + return "" + end + str = string.gsub(str, "+", " ") + str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) + str = string.gsub(str, "\r\n", "\n") + return str +end + +function url_encode(str) + if not str then + return "" + end + str = string.gsub(str, "\n", "\r\n") + str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) + str = string.gsub(str, " ", "+") + return str +end + +function parse_qs(qs) + local tab = {} + for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do + tab[url_decode(key)] = url_decode(val) + end + return tab +end + +function get_cookie(cookies, name) + cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") + return url_decode(string.match(cookies, ";" .. name .. "=(.-);")) +end + +function tohex(b) + local x = "" + for i = 1, #b do + x = x .. string.format("%.2x", string.byte(b, i)) + end + return x +end + +-- +-- +-- Cookie construction and validation helpers. +-- +-- + +local secret = nil + +-- Loads a secret from a file, creates a secret, or returns one from memory. +function get_secret() + if secret ~= nil then + return secret + end + local secret_file = io.open(secret_filename, "r") + if secret_file == nil then + local old_umask = sysstat.umask(63) + local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) + local temporary_file = io.open(temporary_filename, "w") + if temporary_file == nil then + os.exit(177) + end + temporary_file:write(tohex(rand.bytes(32))) + temporary_file:close() + unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. + unistd.unlink(temporary_filename) + sysstat.umask(old_umask) + secret_file = io.open(secret_filename, "r") + end + if secret_file == nil then + os.exit(177) + end + secret = secret_file:read() + secret_file:close() + if secret:len() ~= 64 then + os.exit(177) + end + return secret +end + +-- Returns value of cookie if cookie is valid. Otherwise returns nil. +function validate_value(expected_field, cookie) + local i = 0 + local value = "" + local field = "" + local expiration = 0 + local salt = "" + local chmac = "" + + if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then + return nil + end + + for component in string.gmatch(cookie, "[^|]+") do + if i == 0 then + field = component + elseif i == 1 then + value = component + elseif i == 2 then + expiration = tonumber(component) + if expiration == nil then + expiration = -1 + end + elseif i == 3 then + salt = component + elseif i == 4 then + chmac = component + else + break + end + i = i + 1 + end + + if chmac == nil or chmac:len() == 0 then + return nil + end + + -- Lua hashes strings, so these comparisons are time invariant. + if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then + return nil + end + + if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then + return nil + end + + if url_decode(field) ~= expected_field then + return nil + end + + return url_decode(value) +end + +function secure_value(field, value, expiration) + if value == nil or value:len() <= 0 then + return "" + end + + local authstr = "" + local salt = tohex(rand.bytes(16)) + value = url_encode(value) + field = url_encode(field) + authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt + authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) + return authstr +end + +function set_cookie(cookie, value) + html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") + if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then + html("; secure") + end + html("\n") +end + +function redirect_to(url) + html("Status: 302 Redirect\n") + html("Cache-Control: no-cache, no-store\n") + html("Location: " .. url .. "\n") +end + +function not_found() + html("Status: 404 Not Found\n") + html("Cache-Control: no-cache, no-store\n\n") +end diff --git a/files/filters/hugo-highlighting.sh b/files/filters/hugo-highlighting.sh new file mode 100755 index 0000000..603b8ab --- /dev/null +++ b/files/filters/hugo-highlighting.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# This script can be used to implement syntax highlighting in the cgit +# tree-view by refering to this file with the source-filter or repo.source- +# filter options in cgitrc. +# +# This script requires a shell supporting the ${var##pattern} syntax. +# It is supported by at least dash and bash, however busybox environments +# might have to use an external call to sed instead. +# +# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax +# highlighting, so you'll probably want something like the following included +# in your css file: +# +# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/ +# +# table.blob .num { color:#2928ff; } +# table.blob .esc { color:#ff00ff; } +# table.blob .str { color:#ff0000; } +# table.blob .dstr { color:#818100; } +# table.blob .slc { color:#838183; font-style:italic; } +# table.blob .com { color:#838183; font-style:italic; } +# table.blob .dir { color:#008200; } +# table.blob .sym { color:#000000; } +# table.blob .kwa { color:#000000; font-weight:bold; } +# table.blob .kwb { color:#830000; } +# table.blob .kwc { color:#000000; font-weight:bold; } +# table.blob .kwd { color:#010181; } +# +# +# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/ +# +# body.hl { background-color:#ffffff; } +# pre.hl { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';} +# .hl.num { color:#2928ff; } +# .hl.esc { color:#ff00ff; } +# .hl.str { color:#ff0000; } +# .hl.dstr { color:#818100; } +# .hl.slc { color:#838183; font-style:italic; } +# .hl.com { color:#838183; font-style:italic; } +# .hl.dir { color:#008200; } +# .hl.sym { color:#000000; } +# .hl.line { color:#555555; } +# .hl.mark { background-color:#ffffbb;} +# .hl.kwa { color:#000000; font-weight:bold; } +# .hl.kwb { color:#830000; } +# .hl.kwc { color:#000000; font-weight:bold; } +# .hl.kwd { color:#010181; } +# +# +# Style definition file generated by highlight 3.8, http://www.andre-simon.de/ +# +# body.hl { background-color:#e0eaee; } +# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';} +# .hl.num { color:#b07e00; } +# .hl.esc { color:#ff00ff; } +# .hl.str { color:#bf0303; } +# .hl.pps { color:#818100; } +# .hl.slc { color:#838183; font-style:italic; } +# .hl.com { color:#838183; font-style:italic; } +# .hl.ppc { color:#008200; } +# .hl.opt { color:#000000; } +# .hl.lin { color:#555555; } +# .hl.kwa { color:#000000; font-weight:bold; } +# .hl.kwb { color:#0057ae; } +# .hl.kwc { color:#000000; font-weight:bold; } +# .hl.kwd { color:#010181; } +# +# +# Style definition file generated by highlight 3.13, http://www.andre-simon.de/ +# +# body.hl { background-color:#e0eaee; } +# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;} +# .hl.num { color:#b07e00; } +# .hl.esc { color:#ff00ff; } +# .hl.str { color:#bf0303; } +# .hl.pps { color:#818100; } +# .hl.slc { color:#838183; font-style:italic; } +# .hl.com { color:#838183; font-style:italic; } +# .hl.ppc { color:#008200; } +# .hl.opt { color:#000000; } +# .hl.ipl { color:#0057ae; } +# .hl.lin { color:#555555; } +# .hl.kwa { color:#000000; font-weight:bold; } +# .hl.kwb { color:#0057ae; } +# .hl.kwc { color:#000000; font-weight:bold; } +# .hl.kwd { color:#010181; } +# +# +# The following environment variables can be used to retrieve the configuration +# of the repository for which this script is called: +# CGIT_REPO_URL ( = repo.url setting ) +# CGIT_REPO_NAME ( = repo.name setting ) +# CGIT_REPO_PATH ( = repo.path setting ) +# CGIT_REPO_OWNER ( = repo.owner setting ) +# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting ) +# CGIT_REPO_SECTION ( = section setting ) +# CGIT_REPO_CLONE_URL ( = repo.clone-url setting ) +# + +# store filename and extension in local vars +BASENAME="$1" +EXTENSION="${BASENAME##*.}" + +[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt +[ -z "${EXTENSION}" ] && EXTENSION=txt + +# map Makefile and Makefile.* to .mk +[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk + +# highlight versions 2 and 3 have different commandline options. Specifically, +# the -X option that is used for version 2 is replaced by the -O xhtml option +# for version 3. +# +# Version 2 can be found (for example) on EPEL 5, while version 3 can be +# found (for example) on EPEL 6. +# +# This is for version 2 +#exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null + +# env + +extension=${REQUEST_URI: -3} + +case $extension in + org) + temp=$(mktemp) + cat - > $temp.org + emacs $temp.org \ + --quick \ + --batch \ + --funcall org-html-export-to-html \ + --kill + tail -n+10 $temp.html + exit 0 + ;; +esac + +exec highlight --force -f -I -O xhtml -S "$EXTENSION" # 2>/dev/null diff --git a/files/filters/hugo-pre.sh b/files/filters/hugo-pre.sh new file mode 100755 index 0000000..de94b26 --- /dev/null +++ b/files/filters/hugo-pre.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +name=$1 + +extension=${1: -3} + +case $extension in + .md) markdown ;; + *) + cat <<- EOF +
$(cat -)
+ EOF + ;; +esac + diff --git a/files/logo.png b/files/logo.png new file mode 100644 index 0000000..5c39e4a Binary files /dev/null and b/files/logo.png differ diff --git a/files/logo_large.png b/files/logo_large.png new file mode 100644 index 0000000..bbfa2b5 Binary files /dev/null and b/files/logo_large.png differ diff --git a/files/root_readme b/files/root_readme new file mode 100644 index 0000000..aabf4df --- /dev/null +++ b/files/root_readme @@ -0,0 +1,17 @@ + _______________________ +< Den som gör bestämmer > + ----------------------- +\ . . + \ / `. .' " + \ .---. < > < > .---. + \ | \ \ - ~ ~ - / / | + _____ ..-~ ~-..-~ + | | \~~~\.' `./~~~/ + --------- \__/ \__/ + .' O \ / / \ " + (_____, `._.' | } \/~~~/ + `----. / } | / \__/ + `-. | / | / `. ,~~| + ~-.__| /_ - ~ ^| /- _ `..-' + | / | / ~-. `-. _ _ _ + |_____| |_____| ~ - . _ _ _ _ _> diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 0000000..c1a981b --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,62 @@ +class cgit ( + String $root = '/var/www/cgit', + String $filterpath = '/usr/lib/cgit/extra-filters', + String $root_title, + String $root_desc, + String $about_filter, + String $auth_filter, + String $source_filter, + String $scan_path, + Array[String] $clone_url, +) { + + # TODO figure out where CSS comes from + + ensure_packages([ + 'cgit', + ], { ensure => installed }) + + file { '/etc/cgitrc': + ensure => file, + content => epp('cgit/cgitrc.epp'), + } + + file { "${root}/logo": + ensure => directory, + } + + file { "${root}/logo/logo.png": + ensure => file, + source => 'puppet:///modules/cgit/logo.png', + } + + file { "${root}/logo/logo_large.png": + ensure => file, + source => 'puppet:///modules/cgit/logo_large.png', + } + + file { "${root}/root_readme": + ensure => file, + source => 'puppet:///modules/cgit/root_readme', + } + + file { dirname($filterpath): + ensure => directory, + } + + file { $filterpath: + ensure => directory, + } + + [$about_filter, $source_filter, $auth_filter].each |$f| { + file { "${filterpath}/${f}": + ensure => file, + source => "puppet:///modules/cgit/filters/${f}", + mode => stdlib::extname($f) ? { + '.lua' => '0444', + default => '0555', + }, + } + } + +} diff --git a/templates/cgitrc.epp b/templates/cgitrc.epp new file mode 100644 index 0000000..9434213 --- /dev/null +++ b/templates/cgitrc.epp @@ -0,0 +1,64 @@ +# +# cgit config +# see cgitrc(5) for details +# +# FILE MANAGED BY PUPPET +# + +# css=/cgit.css +logo=/logo/logo.png +# favicon=/favicon.ico + +enable-index-owner=1 + +root-title=<%= $cgit::root_title %> +#root-desc=Tändes endast mot lådans plån +root-desc=<%= $cgit::root_desc %> +# Also causes the `about' page to exist +# /usr/lib/cgit/readme +root-readme=<%= $cgit::root %>/root_readme + +#source-filter=/srv/filters/dispatch.sh + +# about-filter=/usr/local/lib/cgit/filters/hugo-pre.sh +# auth-filter=lua:/usr/local/lib/cgit/filters/hugo-authentication.lua +# source-filter=/usr/local/lib/cgit/filters/hugo-highlighting.sh +about-filter=<%= $cgit::filterpath %>/<%= $cgit::about_filter %> +auth-filter=<% if stdlib::extname($cgit::auth_filter) == '.lua' { + -%>lua:<% + } -%><%= $cgit::filterpath %>/<%= $cgit::auth_filter %> +source-filter=<%= $cgit::filterpath %>/<%= $cgit::source_filter %> + +enable-follow-links=1 +enable-subject-links=1 # show commit summary for parrent + +#side-by-side-diffs=1 +enable-commit-graph=1 +enable-index-links=1 +enable-remote-branches=1 +local-time=1 + +case-sensative-sort=0 + +max-repo-count=100 + +enable-http-clone=1 +clone-url=<%= $cgit::clone_url.join(' ') %> + +readme=:README +readme=:README.md +readme=:README.txt +readme=:readme +readme=:readme.md +readme=:readme.txt + +virtual-root= +remove-suffix=1 +section-from-path=1 +enable-git-config=1 + +# section=~/git +snapshots=tar.gz tar.xz +scan-path=<%= $cgit::scan_path %> + +#scan-path=/var/www/git/repositories/ -- cgit v1.2.3