added docker compose and ci/cd
This commit is contained in:
parent
b6157c67ae
commit
bcb143ca76
|
@ -0,0 +1,5 @@
|
||||||
|
.env.local
|
||||||
|
.gitignore
|
||||||
|
.gitlab-ci.yml
|
||||||
|
docker-compose.yml
|
||||||
|
Readme.md
|
|
@ -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
|
|
@ -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"]
|
39
Readme.md
39
Readme.md
|
@ -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
|
|
|
@ -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:
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue