Working, trying to get CI going

This commit is contained in:
Neale Pickett 2021-08-15 11:03:25 -06:00
commit 8bf77b2f7b
10 changed files with 348 additions and 0 deletions

59
.github/workflows/build+test.yml vendored Normal file
View File

@ -0,0 +1,59 @@
name: Build/Test/Push
on:
push:
branches:
- v3
- devel
- main
tags:
- 'v*.*.*'
jobs:
test-mothd:
name: Test mothd
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.13
- name: Retrieve code
uses: actions/checkout@v2
- name: Test
run: go test ./...
publish:
name: Publish container images
runs-on: ubuntu-latest
steps:
- name: Retrieve code
uses: actions/checkout@v2
- name: Gitlab variables
id: vars
run: build/gitlab-vars
- name: Login to GitHub Packages Docker Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
# Currently required, because buildx doesn't support auto-push from docker
- name: Set up builder
uses: docker/setup-buildx-action@v1
id: buildx
- name: Build and push moth image
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
file: build/Containerfile
push: true
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
tags: |
ghcr.io/nealey/simpleauth:${{ steps.vars.outputs.tag }}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
All this does is present a login page.
Upon successful login, the browser gets a cookie,
and

9
build/Containerfile Normal file
View File

@ -0,0 +1,9 @@
FROM golang:1
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
RUN go install -v ./...
CMD ["simpleauth"]

25
build/gitlab-vars Executable file
View File

@ -0,0 +1,25 @@
#! /bin/sh
case $1 in
-h|-help|--help)
echo "Usage: $0 TARGET"
echo
echo "Sets CI build variables for gitlab"
exit 1
;;
esac
branch=$(git symbolic-ref -q --short HEAD)
if [ "$branch" = "main" ]; then
branch=latest
fi
printf "Branch: %s\n" "$branch"
printf "::set-output name=branch::%s\n" "$branch"
printf "::set-output name=tag::%s\n" "$branch"
# I think it will use whichever comes last
git tag --points-at HEAD | while read tag; do
printf "Tag: %s\n" "$tag"
printf "::set-output name=tag::%s\n" "$tag"
done

119
cmd/simpleauth/main.go Normal file
View File

@ -0,0 +1,119 @@
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/nealey/simpleauth/pkg/token"
)
const CookieName = "auth"
var secret []byte = make([]byte, 256)
var lifespan time.Duration
var password string
var loginHtml []byte
var successHtml []byte
func rootHandler(w http.ResponseWriter, req *http.Request) {
authenticated := false
log.Println(req.FormValue("passwd") == password)
if _, passwd, _ := req.BasicAuth(); passwd == password {
authenticated = true
}
if req.FormValue("passwd") == password {
authenticated = true
}
if cookie, err := req.Cookie(CookieName); err == nil {
t, _ := token.ParseString(cookie.Value)
if t.Valid(secret) {
authenticated = true
}
}
log.Println(authenticated)
if authenticated {
t := token.New(secret, time.Now().Add(lifespan))
http.SetCookie(w, &http.Cookie{
Name: CookieName,
Value: t.String(),
Secure: true,
SameSite: http.SameSiteStrictMode,
})
w.Write(successHtml)
} else {
w.WriteHeader(http.StatusUnauthorized)
w.Write(loginHtml)
}
}
func main() {
listen := flag.String(
"listen",
":8080",
"Bind address for incoming HTTP connections",
)
flag.DurationVar(
&lifespan,
"lifespan",
100*24*time.Hour,
"How long an issued token is valid",
)
passwordPath := flag.String(
"passwd",
"/run/secrets/password",
"Path to a file containing the password",
)
secretPath := flag.String(
"secret",
"/dev/urandom",
"Path to a file containing some sort of secret, for signing requests",
)
htmlPath := flag.String(
"html",
"static",
"Path to HTML files",
)
flag.Parse()
passwordBytes, err := ioutil.ReadFile(*passwordPath)
if err != nil {
log.Fatal(err)
}
password = strings.TrimSpace(string(passwordBytes))
loginHtml, err = ioutil.ReadFile(path.Join(*htmlPath, "login.html"))
if err != nil {
log.Fatal(err)
}
successHtml, err = ioutil.ReadFile(path.Join(*htmlPath, "success.html"))
if err != nil {
log.Fatal(err)
}
// Read in secret
f, err := os.Open(*secretPath)
if err != nil {
log.Fatal(err)
}
defer f.Close()
l, err := f.Read(secret)
if l == 0 {
log.Fatal("Secret file provided 0 bytes. That's not enough bytes!")
} else if err != nil {
log.Fatal(err)
}
secret = secret[:l]
http.HandleFunc("/", rootHandler)
fmt.Println("I am listening on ", *listen)
log.Fatal(http.ListenAndServe(*listen, nil))
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/nealey/simpleauth
go 1.13

76
pkg/token/token.go Normal file
View File

@ -0,0 +1,76 @@
package token
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"time"
)
type T struct {
expiration time.Time
mac []byte
}
func (t T) computeMac(secret []byte) []byte {
mac := hmac.New(sha256.New, secret)
binary.Write(mac, binary.BigEndian, t.expiration)
return mac.Sum([]byte{})
}
// String returns the string encoding of the token
func (t T) String() string {
f := new(bytes.Buffer)
binary.Write(f, binary.BigEndian, t.expiration)
f.Write(t.mac)
return base64.StdEncoding.EncodeToString(f.Bytes())
}
// Valid returns true iff the token is valid for the given secret and current time
func (t T) Valid(secret []byte) bool {
if time.Now().After(t.expiration) {
return false
}
if !hmac.Equal(t.mac, t.computeMac(secret)) {
return false
}
return true
}
// New returns a new token
func New(secret []byte, expiration time.Time) T {
t := T{
expiration: expiration,
}
t.mac = t.computeMac(secret)
return t
}
// Parse returns a new token from the given bytes
func Parse(b []byte) (T, error) {
t := T{
mac: make([]byte, sha256.Size),
}
f := bytes.NewReader(b)
if err := binary.Read(f, binary.BigEndian, &t.expiration); err != nil {
return t, err
}
if n, err := f.Read(t.mac); err != nil {
return t, err
} else {
t.mac = t.mac[:n]
}
return t, nil
}
// ParseString parses a base64-encoded string, as created by T.String()
func ParseString(s string) (T, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return T{}, nil
}
return Parse(b)
}

1
secret Normal file
View File

@ -0,0 +1 @@
goober

28
static/login.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {
font-family: sans-serif;
color: white;
background: seagreen linear-gradient(315deg, rgba(255,255,255,0.4), transparent);
}
body {
height: 100vh;
}
div {
margin: 1em;
}
</style>
</head>
<body>
<h1>Log In</h1>
<form method="POST">
<div>Password: <input type="password" name="passwd"></div>
<div><input type="submit"></div>
</form>
</body>
</html>

25
static/success.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {
font-family: sans-serif;
color: white;
background: seagreen linear-gradient(315deg, rgba(255,255,255,0.4), transparent);
}
body {
height: 100vh;
}
div {
margin: 1em;
}
</style>
</head>
<body>
<h1>Welcome</h1>
<div>You have logged in successfully.</div>
</body>
</html>