added docker compose and ci/cd

This commit is contained in:
Valentin Kolb 2023-11-20 16:32:32 +01:00
parent b6157c67ae
commit bcb143ca76
7 changed files with 156 additions and 22 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.env.local
.gitignore
.gitlab-ci.yml
docker-compose.yml
Readme.md

15
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,15 @@
stages:
- build
build-container:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.9.0-debug
entrypoint: [ "" ]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
only:
- main

40
Dockerfile Normal file
View File

@ -0,0 +1,40 @@
# Start from the official Go image to create a build artifact.
# This is the first stage of a multi-stage build.
FROM golang:1.19 as builder
# Set the working directory inside the container.
WORKDIR /build
# Copy go.mod and go.sum to download dependencies.
COPY go.mod .
# Download Go modules (dependencies).
RUN go mod download
# Copy the rest of the source code.
COPY . .
# https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host
ENV CGO_ENABLED=0
# Build the Go app as a static binary.
# You might need to add tags or other build arguments depending on your application.
RUN go build -o pocketbase main/main.go
# Start from a smaller image to make the final image smaller.
FROM alpine:latest
# Set the working directory inside the container.
WORKDIR /app
# Copy the statically-linked binary from the builder stage.
COPY --from=builder /build/pocketbase .
# Expose port 8090 to the outside world.
EXPOSE 8090
# Update Path
ENV PATH="/app:${PATH}"
# Command to run the executable.
CMD ["./pocketbase", "serve", "--http=0.0.0.0:8090", "--dir=/pb_data"]

View File

@ -42,13 +42,28 @@ Pocketbase selbst bietet folgende Features:
- Pocketbase bietet ein JavaScript SDK an, das alle Features von - Pocketbase bietet ein JavaScript SDK an, das alle Features von
Pocketbase unterstützt. Pocketbase unterstützt.
## Admin UI
Pocketbase bietet eine Admin UI an, die unter `https://<pocketbase-url>/_/` erreichbar ist.
Der Login hierzu ist im SysPass unter *Pocketbase* zu finden.
In der Admin UI können Tabellen erstellt, bearbeitet und gelöscht werden.
Außerdem können die API Rules bearbeitet werden, die festlegen, welche
Funktionen für die einzelnen Tabellen und User erlaubt sind.
## Entwickeln ## Entwickeln
todo ```bash
git clone ...
cd backend
go run main.go serve # --debug=0 for no debug output
```
## Installation ## Installation
todo Das Projekt kann mittels Docker installiert werden.
Durch die Gitlab CI/CD Pipeline wird bei jedem Push auf `main` ein neues Docker
Image erstellt. Dieses muss dann nur noch auf dem Server deployt werden.
## Projekt Struktur ## Projekt Struktur
@ -152,6 +167,12 @@ für diese Collection deaktiviert werden (Username, E-Mail, OAuth2).
Mehr dazu unter dem LDAP Login Package. Mehr dazu unter dem LDAP Login Package.
#### DB Util
In der `db.go` Datei sind alle Datenbank Funktionen implementiert.
Hier gibt es Methoden um User oder Groups zu finden, die dabei automatisch
die Beziehungen zu anderen Tabellen laden (`memberOf`).
### LDAP Login ### LDAP Login
Dieses Package ist für die Authentifizierung zuständig. Dieses Package ist für die Authentifizierung zuständig.
@ -168,7 +189,7 @@ Das Package benötigt folgende ENV Variablen:
Dieses Package für der Api die folgende Route hinzu: Dieses Package für der Api die folgende Route hinzu:
`GET /api/ldap/login` `POST /api/ldap/login`
Diese Route nimmt die Parameter `username` und `password` als json Body entgegen: Diese Route nimmt die Parameter `username` und `password` als json Body entgegen:
@ -179,11 +200,9 @@ Diese Route nimmt die Parameter `username` und `password` als json Body entgegen
} }
``` ```
Dabei wird das Passwort mit dem LDAP Server abgeglichen und falls Das Passwort wird mit dem LDAP Server abgeglichen und nach einer
erfolgreich ein Token und das ldap_user Modell zurückgegeben. erfolgreichen Authentifizierung ein Token und das ldap_user Modell
zurückgegeben. Die `memberOf` Beziehungen werden dabei automatisch geladen.
Das Passwort wird nicht in der Datenbank gespeichert.
Diese Response ist identisch mit der standard Pocketbase Authentifizierung. Diese Response ist identisch mit der standard Pocketbase Authentifizierung (z.B. AuthWithPassword).
#### Token Refresh
todo

33
docker-compose.yml Normal file
View File

@ -0,0 +1,33 @@
services:
pocketbase:
image: gitlab.uni-ulm.de:5050/stuve-it/it-tools/backend
#command:
# - "--debug"
container_name: stuve_it_backend
restart: unless-stopped
volumes:
- pb_data:/pb_data
ports:
- 8090:8090
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8090/api/health || exit 1
interval: 5s
timeout: 5s
retries: 5
environment:
LDAP_URL: "ldap://dc.stuve.uni-ulm.de"
LDAP_BIND_DN: "cn=ldapsync,ou=systemaccounts,ou=user,dc=stuve,dc=uni-ulm,dc=de"
LDAP_BIND_PASSWORD: "************"
LDAP_BASE_DN: "ou=useraccounts,ou=user,dc=stuve,dc=uni-ulm,dc=de"
LDAP_USER_FILTER: "(|(objectCategory=person)(objectClass=user))"
LDAP_GROUP_FILTER: "(objectClass=group)"
LDAP_GROUP_BASE_DN: "ou=groups,ou=user,dc=stuve,dc=uni-ulm,dc=de"
LDAP_SYNC_SCHEDULE: "*/1 * * * *"
volumes:
pb_data:

View File

@ -8,6 +8,7 @@ import (
"github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapSync" "gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapSync"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger"
"os" "os"
) )
@ -27,11 +28,16 @@ import (
// if the user is authenticated successfully the endpoint returns and apis.RecordAuthResponse // if the user is authenticated successfully the endpoint returns and apis.RecordAuthResponse
func InitLDAPLogin(app *pocketbase.PocketBase, e *core.ServeEvent) error { func InitLDAPLogin(app *pocketbase.PocketBase, e *core.ServeEvent) error {
e.Router.GET("/api/ldap/login", func(c echo.Context) error { // add endpoint to app
logger.LogInfoF("Adding LDAP Login Endpoint")
e.Router.POST("/api/ldap/login", func(c echo.Context) error {
logger.LogInfoF("LDAP Login")
// step 1: get data from request // step 1: get data from request
data := struct { data := struct {
CN string `json:"cn" form:"cn"` Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"` Password string `json:"password" form:"password"`
}{} }{}
if err := c.Bind(&data); err != nil { if err := c.Bind(&data); err != nil {
@ -39,7 +45,7 @@ func InitLDAPLogin(app *pocketbase.PocketBase, e *core.ServeEvent) error {
} }
// step 2: get ldap user by cn from ldapUsers table // step 2: get ldap user by cn from ldapUsers table
record, err := ldapSync.GetLdapUserByCN(app, data.CN) record, err := ldapSync.GetLdapUserByCN(app, data.Username)
// if user does not exist in ldapUsers table return error // if user does not exist in ldapUsers table return error
if err != nil { if err != nil {
@ -57,7 +63,7 @@ func InitLDAPLogin(app *pocketbase.PocketBase, e *core.ServeEvent) error {
defer conn.Close() defer conn.Close()
// step 4: bind to ldap server with user credentials from request // step 4: bind to ldap server with user credentials from request
err = conn.Bind(data.CN, data.Password) err = conn.Bind(data.Username, data.Password)
if err != nil { if err != nil {
// if bind fails return error - invalid credentials // if bind fails return error - invalid credentials
return apis.NewBadRequestError("Invalid credentials", err) return apis.NewBadRequestError("Invalid credentials", err)

View File

@ -89,6 +89,12 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error {
record = res record = res
} else { // if record does not exist, create it } else { // if record does not exist, create it
record = models.NewRecord(collection) record = models.NewRecord(collection)
// refresh token key only if new record is created
// if record is updated, the token key should not be changed because it is used to authenticate the user
// if the token key is changed, the user will be logged out
if err := record.RefreshTokenKey(); err != nil {
return err
}
} }
accountExpires, _ := ldapTimeToUnixTime(ldapUser.accountExpires) accountExpires, _ := ldapTimeToUnixTime(ldapUser.accountExpires)
@ -117,8 +123,6 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error {
record.Set("cn", ldapUser.cn) record.Set("cn", ldapUser.cn)
record.Set("memberOf", groups) record.Set("memberOf", groups)
record.RefreshTokenKey()
if err := app.Dao().SaveRecord(record); err != nil { if err := app.Dao().SaveRecord(record); err != nil {
return fmt.Errorf("failed to upsert user with dn: %s - %w", ldapUser.dn, err) return fmt.Errorf("failed to upsert user with dn: %s - %w", ldapUser.dn, err)
} }
@ -126,18 +130,30 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error {
return nil return nil
} }
// GetLdapGroupByDN This function returns a record from the ldap groups table by dn. It also expands the memberOf field.
func GetLdapGroupByDN(app *pocketbase.PocketBase, dn string) (*models.Record, error) { func GetLdapGroupByDN(app *pocketbase.PocketBase, dn string) (*models.Record, error) {
return app.Dao().FindFirstRecordByFilter( record, err := app.Dao().FindFirstRecordByFilter(
ldapGroupsTableName, ldapGroupsTableName,
"dn = {:dn}", "dn = {:dn}",
dbx.Params{"dn": dn}, dbx.Params{"dn": dn},
) )
if err != nil {
return nil, err
}
if errs := app.Dao().ExpandRecord(record, []string{"memberOf"}, nil); len(errs) > 0 {
return nil, fmt.Errorf("ldap group - failed to expand: %v", errs)
}
return record, nil
} }
// GetLdapUserByCN This function returns a record from the ldap users table by cn. It also expands the memberOf field.
func GetLdapUserByCN(app *pocketbase.PocketBase, cn string) (*models.Record, error) { func GetLdapUserByCN(app *pocketbase.PocketBase, cn string) (*models.Record, error) {
return app.Dao().FindFirstRecordByFilter( record, err := app.Dao().FindAuthRecordByUsername(ldapUsersTableName, cn)
ldapUsersTableName, if err != nil {
"cn = {:cn}", return nil, err
dbx.Params{"cn": cn}, }
) if errs := app.Dao().ExpandRecord(record, []string{"memberOf"}, nil); len(errs) > 0 {
return nil, fmt.Errorf("ldap user - failed to expand: %v", errs)
}
return record, nil
} }