diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 632beca..9417e36 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -47,7 +47,7 @@ jobs: run: go get ./... - name: Build working-directory: backend - run: go build -o build/kubevoyage ./cmd/kubevoyage + run: GOOS=linux GOARCH=amd64 go build -o build/kubevoyage ./cmd/kubevoyage #- name: Test with the Go CLI # run: go test - name: Archive production artifacts @@ -86,7 +86,7 @@ jobs: push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/kubevoyage:${{ env.BRANCH_NAME }} release: - needs: build-docker + needs: helm-release runs-on: ubuntu-latest permissions: contents: write @@ -108,4 +108,35 @@ jobs: - name: Release env: GITHUB_TOKEN: ${{ secrets.PAT }} - run: npx semantic-release --debug \ No newline at end of file + run: npx semantic-release --debug + helm-release: + needs: build-docker + # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions + # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@v3 + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.5.0 + with: + charts_dir: deploy/ + env: + registryImage: ${{ secrets.DOCKERHUB_USERNAME }}/kubevoyage + imageTag: ${{ github.head_ref || github.ref_name }} + CR_TOKEN: ${{ secrets.PAT }} \ No newline at end of file diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index 9ef7aef..52bc4a4 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -41,7 +41,7 @@ jobs: run: go get ./... - name: Build working-directory: backend - run: go build -o build/kubevoyage ./cmd/kubevoyage + run: GOOS=linux GOARCH=amd64 go build -o build/kubevoyage ./cmd/kubevoyage #- name: Test with the Go CLI # run: go test - name: Archive production artifacts @@ -79,7 +79,8 @@ jobs: push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/kubevoyage:${{ env.BRANCH_NAME }} - release: + helm-release: + needs: build-docker # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: @@ -98,6 +99,8 @@ jobs: - name: Install Helm uses: azure/setup-helm@v3 + env: + GITHUB_TOKEN: ${{ secrets.PAT }} - name: Run chart-releaser uses: helm/chart-releaser-action@v1.5.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c75983..2e0664e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: run: go get ./... - name: Build working-directory: backend - run: go build -o build/kubevoyage ./cmd/kubevoyage + run: GOOS=linux GOARCH=amd64 go build -o build/kubevoyage ./cmd/kubevoyage #- name: Test with the Go CLI # run: go test - name: Archive production artifacts @@ -78,7 +78,7 @@ jobs: context: . push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/kubevoyage:${{ env.BRANCH_NAME }} - release: + helm-release: needs: build-docker # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token diff --git a/.gitignore b/.gitignore index 093383b..9d70cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea *.db -build \ No newline at end of file +build + +deploy/charts/values-local.yaml diff --git a/Dockerfile b/Dockerfile index 66f9283..bfb524b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,13 @@ FROM golang:1.21 LABEL authors="bjornurban" EXPOSE 8080:8080 -RUN mkdir /kubevoyage -RUN mkdir /kubevoyage/bin -COPY frontend/public /kubevoyage/public -COPY backend/build /kubevoyage/bin +WORKDIR /kubevoyage + +# Copy frontend and backend files +COPY frontend/public ./public +COPY backend/build ./bin + +# Ensure the binary has executable permissions +RUN chmod +x ./bin/kubevoyage + ENTRYPOINT ["./bin/kubevoyage"] \ No newline at end of file diff --git a/Readme.md b/Readme.md index a7d2ca5..a4de2d9 100644 --- a/Readme.md +++ b/Readme.md @@ -4,7 +4,7 @@ Embarking on a secure journey in Kubernetes. `KubeVoyage` is a Kubernetes authentication proxy designed to streamline user access to various sites. Built with a Svelte frontend, a Go backend, and an SQL database, it offers a robust solution for managing user access in a Kubernetes environment. - + ## Features @@ -74,7 +74,8 @@ Visit `http://localhost:8080` in your browser. Use the provided Helm chart to deploy `KubeVoyage` to your Kubernetes cluster: ```bash -helm install kubevoyage ./path_to_helm_chart +helm repo add github-burban https://B-urb.github.io/KubeVoyage/ +helm install kubevoyage github-burban/kubevoyage ``` ## Testing @@ -92,4 +93,4 @@ Pull requests are welcome! For major changes, please open an issue first to disc ## License -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/backend/cmd/kubevoyage/main.go b/backend/cmd/kubevoyage/main.go index 79f0c54..f45e97f 100644 --- a/backend/cmd/kubevoyage/main.go +++ b/backend/cmd/kubevoyage/main.go @@ -1,109 +1,135 @@ package main import ( + "fmt" + "github.com/B-Urb/KubeVoyage/internal/app" "github.com/B-Urb/KubeVoyage/internal/handlers" - "github.com/B-Urb/KubeVoyage/internal/models" "github.com/rs/cors" - "gorm.io/driver/sqlite" "gorm.io/gorm" "log" "net/http" "os" + "path/filepath" + "time" // or "gorm.io/driver/postgres" for PostgreSQL ) var db *gorm.DB +var frontendPathLocal = "./public" //"../frontend/public" + +type loggingResponseWriter struct { + http.ResponseWriter + statusCode int + length int +} + +func (lrw *loggingResponseWriter) Write(b []byte) (int, error) { + if lrw.statusCode == 0 { + // Default status code is 200 OK. + lrw.statusCode = http.StatusOK + } + size, err := lrw.ResponseWriter.Write(b) + lrw.length += size + return size, err +} + +func (lrw *loggingResponseWriter) WriteHeader(statusCode int) { + lrw.statusCode = statusCode + lrw.ResponseWriter.WriteHeader(statusCode) +} +func logMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lrw := &loggingResponseWriter{ResponseWriter: w} + start := time.Now() + + next.ServeHTTP(lrw, r) + + duration := time.Since(start) + log.Printf( + "Method: %s, Path: %s, RemoteAddr: %s, Duration: %s, StatusCode: %d, ResponseSize: %d bytes\n", + r.Method, + r.URL.Path, + r.RemoteAddr, + duration, + lrw.statusCode, + lrw.length, + ) + }) +} func main() { - var err error - db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + app, err := application.NewApp() // Assuming NewApp is in the same package if err != nil { - panic("failed to connect database") + log.Fatalf(err.Error()) + } + + handler := handlers.NewHandler(app.DB) + err = app.Init() + if err != nil { + log.Fatalf(err.Error()) } - mux := http.NewServeMux() - // Migrate the schema - db.AutoMigrate(&models.User{}, &models.Site{}, &models.UserSite{}) - //generateTestData() + mux := setupServer(handler) + + log.Println("Starting server on :8080") + log.Fatal(http.ListenAndServe(":8080", mux)) +} +func setupServer(handle *handlers.Handler) http.Handler { + mux := http.NewServeMux() handler := cors.Default().Handler(mux) // Serve static files - fs := http.FileServer(http.Dir("../frontend/public/")) // Adjust the path based on your directory structure - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fs := http.FileServer(http.Dir(frontendPathLocal)) // Adjust the path based on your directory structure + mux.Handle("/", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Check if it's an API route first if isAPIRoute(r.URL.Path) { // Handle API routes separately return } - path := "../frontend/public" + r.URL.Path - log.Println(path) - _, err := os.Stat(path) + path := frontendPathLocal + r.URL.Path + absolutePath, err := filepath.Abs(path) + if err != nil { + fmt.Println("Error getting absolute path:", err) + return + } + fmt.Println("Absolute Path:", absolutePath) + _, err = os.Stat(path) // If the file exists, serve it if !os.IsNotExist(err) { fs.ServeHTTP(w, r) return + } else { + log.Println(err) } - // Otherwise, serve index.html - http.ServeFile(w, r, "../frontend/public/index.html") - }) + http.ServeFile(w, r, frontendPathLocal+"/index.html") + }))) - mux.HandleFunc("/api/requests", func(w http.ResponseWriter, r *http.Request) { + mux.Handle("/api/requests", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlers.HandleRequests(w, r, db) - }) - mux.HandleFunc("/api/register", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleRegister(w, r, db) - }) - mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleLogin(w, r, db) - }) - mux.HandleFunc("/api/authenticate", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleAuthenticate(w, r, db) - }) - mux.HandleFunc("/api/request", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleRequestSite(w, r, db) - }) - // Start the server on port 8081 - log.Println("Starting server on :8080") - - log.Fatal(http.ListenAndServe(":8080", handler)) + }))) + mux.Handle("/api/register", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleRegister(w, r) + }))) + mux.Handle("/api/login", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleLogin(w, r) + }))) + mux.Handle("/api/authenticate", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleAuthenticate(w, r) + }))) + mux.Handle("/api/redirect", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleRedirect(w, r) + }))) + mux.Handle("/api/request", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleRequestSite(w, r, db) + }))) - // ... setup your routes and start your server + return handler } + func isAPIRoute(path string) bool { return len(path) >= 4 && path[0:4] == "/api" } -func generateTestData() { - // Insert test data for Users - users := []models.User{ - {Email: "user1@example.com", Password: "password1", Role: "admin"}, - {Email: "user2@example.com", Password: "password2", Role: "user"}, - {Email: "user3@example.com", Password: "password3", Role: "user"}, - } - for _, user := range users { - db.Create(&user) - } - - // Insert test data for Sites - sites := []models.Site{ - {URL: "https://site1.com"}, - {URL: "https://site2.com"}, - {URL: "https://site3.com"}, - } - for _, site := range sites { - db.Create(&site) - } - - // Insert test data for UserSite - userSites := []models.UserSite{ - {UserID: 1, SiteID: 1, State: "authorized"}, - {UserID: 2, SiteID: 2, State: "requested"}, - {UserID: 3, SiteID: 3, State: "authorized"}, - } - for _, userSite := range userSites { - db.Create(&userSite) - } -} diff --git a/backend/go.mod b/backend/go.mod index 1897c74..b2987fa 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,19 +3,28 @@ module github.com/B-Urb/KubeVoyage go 1.21 require ( - github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/golang-jwt/jwt/v5 v5.0.0 github.com/rs/cors v1.10.0 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.13.0 + gorm.io/driver/mysql v1.5.1 + gorm.io/driver/postgres v1.5.2 gorm.io/driver/sqlite v1.5.3 gorm.io/gorm v1.25.4 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index e3fc5b0..5887347 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,26 +1,54 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.10.0 h1:62NOS1h+r8p1mW6FM0FSB0exioXLhd/sh15KpjWBZ+8= github.com/rs/cors v1.10.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g= gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw= gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= diff --git a/backend/internal/app/app.go b/backend/internal/app/app.go new file mode 100644 index 0000000..851e9b1 --- /dev/null +++ b/backend/internal/app/app.go @@ -0,0 +1,79 @@ +package application + +import ( + "encoding/base64" + "errors" + "fmt" + "github.com/B-Urb/KubeVoyage/internal/database" + "github.com/B-Urb/KubeVoyage/internal/models" + "github.com/B-Urb/KubeVoyage/internal/util" + "golang.org/x/crypto/scrypt" + "gorm.io/gorm" +) + +type App struct { + DB *gorm.DB +} + +func NewApp() (*App, error) { + db, err := database.InitializeDatabase() + if err != nil { + return nil, fmt.Errorf("failed to initialize database: %v", err) + } + + return &App{ + DB: db, + }, nil +} + +func (app *App) Init() error { + err := app.DB.AutoMigrate(models.User{}, models.Site{}, models.UserSite{}) + if err != nil { + return err + } + err = createAdminUserIfNotExist(app.DB) + if err != nil { + return err + } + return nil +} + +func createAdminUserIfNotExist(db *gorm.DB) error { + adminEmail, _ := util.GetEnvOrDefault("ADMIN_USER", "admin@admin.de") + adminPassword, _ := util.GetEnvOrDefault("ADMIN_PASSWORD", "test") + + var existingUser models.User + if err := db.Where("email = ?", adminEmail).First(&existingUser).Error; err == nil { + // Admin user already exists + return nil + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + // Hash the password using scrypt + hash, err := hashPassword(adminPassword) + if err != nil { + return err + } + + adminUser := models.User{ + Email: adminEmail, + Password: hash, + Role: "admin", + } + + if err := db.Create(&adminUser).Error; err != nil { + return err + } + + return nil +} + +func hashPassword(password string) (string, error) { + // Hash the password using scrypt + hash, err := scrypt.Key([]byte(password), nil, 16384, 8, 1, 32) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(hash), nil +} diff --git a/backend/internal/database/database.go b/backend/internal/database/database.go new file mode 100644 index 0000000..62ccc04 --- /dev/null +++ b/backend/internal/database/database.go @@ -0,0 +1,70 @@ +package database + +import ( + "fmt" + "github.com/B-Urb/KubeVoyage/internal/util" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func InitializeDatabase() (*gorm.DB, error) { + // Read environment variables + dbType, err := util.GetEnvOrDefault("DB_TYPE", "sqlite") + if err != nil { + return nil, err + } + + var dsn string + var db *gorm.DB + + dbName, err := util.GetEnvOrDefault("DB_NAME", "kubevoyage") + if err != nil { + return nil, err + } + + switch dbType { + case "mysql", "postgres": + dbHost, err := util.GetEnvOrError("DB_HOST") + if err != nil { + return nil, err + } + + dbPort, err := util.GetEnvOrError("DB_PORT") + if err != nil { + return nil, err + } + + dbUser, err := util.GetEnvOrError("DB_USER") + if err != nil { + return nil, err + } + + dbPassword, err := util.GetEnvOrError("DB_PASSWORD") + if err != nil { + return nil, err + } + + if dbType == "mysql" { + dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbHost, dbPort, dbName) + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + } else { + dsn = fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable", dbHost, dbPort, dbUser, dbName, dbPassword) + db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) + } + + case "sqlite": + dsn = dbName // For SQLite, dbName would be the path to the .db file + db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{}) + + default: + return nil, fmt.Errorf("Unsupported DB_TYPE: %s", dbType) + } + + if err != nil { + return nil, fmt.Errorf("Failed to connect to database: %v", err) + } + + return db, nil +} diff --git a/backend/internal/handlers/auth.go b/backend/internal/handlers/auth.go index 41cf3a1..f35d5fc 100644 --- a/backend/internal/handlers/auth.go +++ b/backend/internal/handlers/auth.go @@ -8,17 +8,35 @@ import ( "errors" "fmt" "github.com/B-Urb/KubeVoyage/internal/models" - "github.com/dgrijalva/jwt-go" + "github.com/B-Urb/KubeVoyage/internal/util" + "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/scrypt" "gorm.io/gorm" + "log" "net/http" - "net/url" "time" ) -var jwtKey = []byte("your_secret_key") +type Handler struct { + db *gorm.DB + JWTKey []byte + BaseURL string +} + +func NewHandler(db *gorm.DB) *Handler { + jwtKey, err := util.GetEnvOrError("JWT_SECRET_KEY") + if err != nil { + log.Fatalf("Error reading JWT_SECRET_KEY: %v", err) + } + + baseURL, err := util.GetEnvOrError("BASE_URL") + if err != nil { + log.Fatalf("Error reading BASE_URL: %v", err) + } + return &Handler{db: db, JWTKey: []byte(jwtKey), BaseURL: baseURL} +} -func HandleLogin(w http.ResponseWriter, r *http.Request, db *gorm.DB) { +func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { var inputUser models.User var dbUser models.User @@ -30,7 +48,7 @@ func HandleLogin(w http.ResponseWriter, r *http.Request, db *gorm.DB) { } // Fetch the user from the database - result := db.Where("email = ?", inputUser.Email).First(&dbUser) + result := h.db.Where("email = ?", inputUser.Email).First(&dbUser) if result.Error != nil { sendJSONError(w, "User not found", http.StatusNotFound) return @@ -54,35 +72,45 @@ func HandleLogin(w http.ResponseWriter, r *http.Request, db *gorm.DB) { }) // Sign and get the complete encoded token as a string using the secret - tokenString, err := token.SignedString(jwtKey) + tokenString, err := token.SignedString(h.JWTKey) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } - domain, err := extractMainDomain(r.URL.String()) + siteURL, err := h.getRedirectUrl(r, w) + if err != nil { + http.Error(w, "Redirect URL missing", http.StatusBadRequest) + return + } + log.Println(siteURL) + h.setRedirectCookie(siteURL, r, w) + //if siteURL == "" { + // siteURL = r.Host + //} + w.Header().Set("X-Auth-Site", siteURL) + domain, err := extractMainDomain(siteURL) // Set the token as a cookie http.SetCookie(w, &http.Cookie{ - Name: "auth_token", + Name: "X-Auth-Token", Value: tokenString, Expires: time.Now().Add(24 * time.Hour), HttpOnly: true, - Secure: true, // Set this to true if using HTTPS - Domain: domain, // Adjust to your domain + Secure: true, // Set this to true if using HTTPS + SameSite: http.SameSiteNoneMode, // Set this to true if using HTTPS + Domain: domain, // Adjust to your domain Path: "/", }) - siteURL := r.URL.Query().Get("redirect") - if siteURL == "" { - http.Error(w, "Redirect URL missing", http.StatusBadRequest) - return - } else { - http.Redirect(w, r, url.QueryEscape(siteURL), http.StatusSeeOther) - } + w.Header().Set("X-Auth-Token", tokenString) + //http.Redirect(w, r, siteURL, http.StatusSeeOther) // Here, you'd typically generate a JWT or session token and send it back to the client. // For simplicity, we'll just send a success message. - w.Write([]byte("Login successful")) + _, err = w.Write([]byte("Login successful")) + if err != nil { + return + } } -func HandleRegister(w http.ResponseWriter, r *http.Request, db *gorm.DB) { +func (h *Handler) HandleRegister(w http.ResponseWriter, r *http.Request) { var user models.User // Parse the request body @@ -107,7 +135,7 @@ func HandleRegister(w http.ResponseWriter, r *http.Request, db *gorm.DB) { } user.Password = base64.StdEncoding.EncodeToString(hash) var existingUser models.User - if err := db.Where("email = ?", user.Email).First(&existingUser).Error; err != nil { + if err := h.db.Where("email = ?", user.Email).First(&existingUser).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { sendJSONError(w, "Database error", http.StatusInternalServerError) return @@ -117,53 +145,87 @@ func HandleRegister(w http.ResponseWriter, r *http.Request, db *gorm.DB) { return } // Save the user to the database - result := db.Create(&user) + result := h.db.Create(&user) if result.Error != nil { sendJSONError(w, result.Error.Error(), http.StatusInternalServerError) return } - sendJSONSuccess(w, "", http.StatusCreated) } -func HandleAuthenticate(w http.ResponseWriter, r *http.Request, db *gorm.DB) { +func (h *Handler) HandleRedirect(w http.ResponseWriter, r *http.Request) { + //FIXME: Not unchecked redirecting with parameter + siteURL, err := h.getRedirectFromCookie(r, w, true) + if err != nil { + + } + if siteURL == "" { + siteURL = r.Host + } + + redirect := r.Header.Get("X-Auth-Site") + log.Println(redirect) + log.Println(siteURL) + http.Redirect(w, r, siteURL, http.StatusSeeOther) + +} +func (h *Handler) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { // 1. Extract the user's email from the session or JWT token. - userEmail, err := getUserEmailFromToken(r) + siteURL, err := h.getRedirectUrl(r, w) + if err != nil { + log.Println(err.Error()) + //h.logError(w, err.Error(), nil, http.StatusBadRequest) + //return + } + userEmail, err := h.getUserEmailFromToken(r) if err != nil { - http.Error(w, err.Error(), http.StatusUnauthorized) + // If the user cannot be read from the cookie, redirect to /login with the site URL as a parameter + h.setRedirectCookie(siteURL, r, w) + http.Redirect(w, r, "/login?redirect="+siteURL, http.StatusSeeOther) return } - // 2. Extract the redirect parameter from the request to get the site URL. - siteURL := r.URL.Query().Get("redirect") - if siteURL == "" { - http.Error(w, "Redirect URL missing", http.StatusBadRequest) + // Check if the user has the role "admin" + var user models.User + err = h.db.Where("email = ?", userEmail).First(&user).Error + if err != nil { + h.logError(w, "Database error while fetching user details", err, http.StatusInternalServerError) + return + } + if user.Role == "admin" { + w.WriteHeader(http.StatusOK) return } // 3. Query the database to check if the user has an "authorized" state for the given site. var userSite models.UserSite - err = db.Joins("JOIN users ON users.id = user_sites.user_id"). + err = h.db.Joins("JOIN users ON users.id = user_sites.user_id"). Joins("JOIN sites ON sites.id = user_sites.site_id"). Where("users.email = ? AND sites.url = ? AND user_sites.state = ?", userEmail, siteURL, "authorized"). First(&userSite).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - // Redirect to /request if no authorized site is found for the user - http.Redirect(w, r, "/request?redirect="+url.QueryEscape(siteURL), http.StatusSeeOther) + // Return 401 if the user is not authorized for the requested siteURL + w.WriteHeader(http.StatusUnauthorized) return } - http.Error(w, "Database error", http.StatusInternalServerError) + h.logError(w, "Database error while checking user authorization", err, http.StatusInternalServerError) return } - http.Redirect(w, r, siteURL, http.StatusSeeOther) + w.WriteHeader(http.StatusOK) +} - // If everything is fine, return a success message. - //w.Write([]byte("Access granted")) +func (h *Handler) logError(w http.ResponseWriter, message string, err error, statusCode int) { + logMessage := message + if err != nil { + logMessage = fmt.Sprintf("%s: %v", message, err) + } + log.Println(logMessage) + http.Error(w, message, statusCode) } -func getUserEmailFromToken(r *http.Request) (string, error) { - cookie, err := r.Cookie("auth_token") +func (h *Handler) getUserEmailFromToken(r *http.Request) (string, error) { + cookie, err := r.Cookie("X-Auth-Token") if err != nil { return "", fmt.Errorf("Authentication cookie missing") } @@ -172,7 +234,7 @@ func getUserEmailFromToken(r *http.Request) (string, error) { claims := &jwt.MapClaims{} _, err = jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { - return jwtKey, nil + return h.JWTKey, nil }) if err != nil { @@ -186,3 +248,71 @@ func getUserEmailFromToken(r *http.Request) (string, error) { return userEmail, nil } + +func (h *Handler) setRedirectCookie(redirectUrl string, r *http.Request, w http.ResponseWriter) error { + w.Header().Set("X-Auth-Site", redirectUrl) + domain, err := extractMainDomain(redirectUrl) + if err != nil { + log.Println(err.Error()) + } + log.Println(domain) + http.SetCookie(w, &http.Cookie{ + Name: "X-Auth-Site", + Value: redirectUrl, + Expires: time.Now().Add(15 * time.Minute), // Shorter duration + HttpOnly: true, + Secure: true, // Set this to true if using HTTPS + SameSite: http.SameSiteNoneMode, // Set this to true if using HTTPS + Domain: domain, // Adjust to your domain + Path: "/", + }) + return nil +} +func (h *Handler) getRedirectFromCookie(r *http.Request, w http.ResponseWriter, clear bool) (string, error) { + cookie, err := r.Cookie("X-Auth-Site") + if err != nil { + if errors.Is(err, http.ErrNoCookie) { + // No cookie found + return "", nil + } + return "", err + } + + // Clear the cookie once it's read + //http.SetCookie(w, &http.Cookie{ + // Name: "X-Auth-Site", + // Value: "", + // Expires: time.Unix(0, 0), + // Path: "/", + //}) + + return cookie.Value, nil +} +func (h *Handler) getRedirectUrl(r *http.Request, w http.ResponseWriter) (string, error) { + // Extract the redirect parameter from the request to get the site URL. + printHeaders(r) + + siteURL := r.Header.Get("X-Forwarded-Uri") + if siteURL == "" { + siteURL = r.Header.Get("X-Auth-Site") + if siteURL == "" { + siteURL = r.URL.Query().Get("redirect") + if siteURL == "" { + surl, err := h.getRedirectFromCookie(r, w, false) + if err != nil { + fmt.Errorf("Redirect URL missing from both header and URL parameter") + } + siteURL = surl + } + } + } + return siteURL, nil +} +func printHeaders(r *http.Request) { + for name, values := range r.Header { + // Loop over all values for the name. + for _, value := range values { + fmt.Printf("%s: %s\n", name, value) + } + } +} diff --git a/backend/internal/handlers/middleware.go b/backend/internal/handlers/middleware.go index f884435..0627c6b 100644 --- a/backend/internal/handlers/middleware.go +++ b/backend/internal/handlers/middleware.go @@ -1,7 +1,7 @@ package handlers import ( - "github.com/dgrijalva/jwt-go" + "github.com/golang-jwt/jwt/v5" "net/http" "net/url" ) @@ -16,7 +16,7 @@ func handleUnauthenticated(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, url.QueryEscape(redirectURL), http.StatusSeeOther) } -func authenticate(next http.HandlerFunc) http.HandlerFunc { +func (h *Handler) authenticate(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("auth_token") if err != nil { @@ -28,7 +28,7 @@ func authenticate(next http.HandlerFunc) http.HandlerFunc { claims := &jwt.MapClaims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { - return jwtKey, nil + return h.JWTKey, nil }) if err != nil || !token.Valid { diff --git a/backend/internal/handlers/requests.go b/backend/internal/handlers/requests.go index b3a4a6d..80f43ae 100644 --- a/backend/internal/handlers/requests.go +++ b/backend/internal/handlers/requests.go @@ -31,8 +31,8 @@ func HandleRequests(w http.ResponseWriter, r *http.Request, db *gorm.DB) { } -func HandleRequestSite(w http.ResponseWriter, r *http.Request, db *gorm.DB) { - userEmail, err := getUserEmailFromToken(r) +func (h *Handler) HandleRequestSite(w http.ResponseWriter, r *http.Request, db *gorm.DB) { + userEmail, err := h.getUserEmailFromToken(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return diff --git a/backend/internal/handlers/utils.go b/backend/internal/handlers/utils.go index 3121a43..31cb1f2 100644 --- a/backend/internal/handlers/utils.go +++ b/backend/internal/handlers/utils.go @@ -3,29 +3,57 @@ package handlers import ( "encoding/json" "fmt" + "log" "net/http" "net/url" "strings" ) func extractMainDomain(u string) (string, error) { - //TODO: This function currently does not work with double tlds like .co.uk + log.Println("Parsing input: " + u) + // Prepend http:// if no scheme is provided, this ensures url.Parse succeeds + if !strings.Contains(u, "//") { + u = "http://" + u + } + + // Parse the URL and validate it parsedURL, err := url.Parse(u) if err != nil { - return "", err + return "", fmt.Errorf("error parsing URL: %v", err) } + // Split the hostname into parts parts := strings.Split(parsedURL.Hostname(), ".") - if len(parts) < 2 { - return "", fmt.Errorf("invalid domain") + partsLength := len(parts) + + // Check if the URL has at least a domain and a TLD + if partsLength < 2 { + return "", fmt.Errorf("invalid domain: domain and TLD not found in URL") } - // Extract the main domain and TLD - domain := parts[len(parts)-2] // Second to last part is the main domain - tld := parts[len(parts)-1] // Last part is the TLD + // Handle second-level domains (SLDs) like ".co.uk", ".com.au", etc. + if partsLength > 2 { + // List of common SLDs + secondLevelDomains := map[string]bool{ + "com.au": true, + "co.uk": true, + "com.br": true, + // ... add more second-level domains as needed + } - return fmt.Sprintf(".%s.%s", domain, tld), nil + // Check if the last two parts match a known second-level domain + if secondLevelDomains[parts[partsLength-2]+"."+parts[partsLength-1]] { + if partsLength < 3 { + return "", fmt.Errorf("invalid domain: missing main domain before second-level domain") + } + return fmt.Sprintf("%s.%s.%s", parts[partsLength-3], parts[partsLength-2], parts[partsLength-1]), nil + } + } + + // For non-SLDs, return the domain and TLD + return fmt.Sprintf("%s.%s", parts[partsLength-2], parts[partsLength-1]), nil } + func sendJSONError(w http.ResponseWriter, message string, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) diff --git a/backend/internal/test/test.go b/backend/internal/test/test.go new file mode 100644 index 0000000..01de882 --- /dev/null +++ b/backend/internal/test/test.go @@ -0,0 +1,33 @@ +package test + +//func generateTestData() { +// // Insert test data for Users +// users := []models.User{ +// {Email: "user1@example.com", Password: "password1", Role: "admin"}, +// {Email: "user2@example.com", Password: "password2", Role: "user"}, +// {Email: "user3@example.com", Password: "password3", Role: "user"}, +// } +// for _, user := range users { +// db.Create(&user) +// } +// +// // Insert test data for Sites +// sites := []models.Site{ +// {URL: "https://site1.com"}, +// {URL: "https://site2.com"}, +// {URL: "https://site3.com"}, +// } +// for _, site := range sites { +// db.Create(&site) +// } +// +// // Insert test data for UserSite +// userSites := []models.UserSite{ +// {UserID: 1, SiteID: 1, State: "authorized"}, +// {UserID: 2, SiteID: 2, State: "requested"}, +// {UserID: 3, SiteID: 3, State: "authorized"}, +// } +// for _, userSite := range userSites { +// db.Create(&userSite) +// } +//} diff --git a/backend/internal/util/util.go b/backend/internal/util/util.go new file mode 100644 index 0000000..cceac86 --- /dev/null +++ b/backend/internal/util/util.go @@ -0,0 +1,22 @@ +package util + +import ( + "fmt" + "os" +) + +func GetEnvOrError(key string) (string, error) { + value := os.Getenv(key) + if value == "" { + return "", fmt.Errorf("environment variable %s not set", key) + } + return value, nil +} + +func GetEnvOrDefault(key string, defaultValue string) (string, error) { + value := os.Getenv(key) + if value == "" { + return defaultValue, nil + } + return value, nil +} diff --git a/deploy/charts/Chart.yaml b/deploy/charts/Chart.yaml index 31a42ab..c6100a1 100644 --- a/deploy/charts/Chart.yaml +++ b/deploy/charts/Chart.yaml @@ -1,4 +1,4 @@ -apiVersion: v3 +apiVersion: v2 name: kubevoyage description: A Helm chart for Kubernetes auth proxy -version: 0.1.0 \ No newline at end of file +version: 0.7.0 \ No newline at end of file diff --git a/deploy/charts/templates/_helpers.tpl b/deploy/charts/templates/_helpers.tpl index fd8a6a5..16f15f2 100644 --- a/deploy/charts/templates/_helpers.tpl +++ b/deploy/charts/templates/_helpers.tpl @@ -1,2 +1,11 @@ {{/* Generate the best default app name */}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} + +{{- define "kubevoyage.fullname" -}} +{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "kubevoyage.labels" -}} +app.kubernetes.io/name: {{ include "kubevoyage.fullname" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/templates/deployment.yaml b/deploy/charts/templates/deployment.yaml index 9ab5d8d..f79ef5d 100644 --- a/deploy/charts/templates/deployment.yaml +++ b/deploy/charts/templates/deployment.yaml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "kubevoyage.fullname" . }} + namespace: {{ .Values.app.namespace}} labels: app: {{ include "kubevoyage.fullname" . }} spec: @@ -21,6 +22,8 @@ spec: ports: - containerPort: 8080 env: + - name: BASE_URL + value: {{ .Values.additionalEnvVars.baseUrl }} - name: DB_TYPE value: {{ .Values.database.type }} - name: DB_HOST @@ -34,7 +37,22 @@ spec: - name: DB_PASSWORD valueFrom: secretKeyRef: - name: {{ include "kubevoyage.fullname" . }}-db-secret + name: {{ include "kubevoyage.fullname" . }}-secret key: db-password + - name: JWT_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "kubevoyage.fullname" . }}-secret + key: jwt-secret + - name: ADMIN_USER + valueFrom: + secretKeyRef: + name: {{ include "kubevoyage.fullname" . }}-secret + key: admin-user + - name: ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "kubevoyage.fullname" . }}-secret + key: admin-password diff --git a/deploy/charts/templates/ingress.yaml b/deploy/charts/templates/ingress.yaml new file mode 100644 index 0000000..7380371 --- /dev/null +++ b/deploy/charts/templates/ingress.yaml @@ -0,0 +1,39 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "kubevoyage.fullname" . }} + namespace: {{ .Values.app.namespace }} + labels: + {{- include "kubevoyage.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ .service.name }} + port: + number: {{ .service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deploy/charts/templates/namespace.yaml b/deploy/charts/templates/namespace.yaml new file mode 100644 index 0000000..5ddd77b --- /dev/null +++ b/deploy/charts/templates/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.app.namespace }} \ No newline at end of file diff --git a/deploy/charts/templates/secret.yaml b/deploy/charts/templates/secret.yaml index 8d327e8..64f95e7 100644 --- a/deploy/charts/templates/secret.yaml +++ b/deploy/charts/templates/secret.yaml @@ -1,7 +1,11 @@ apiVersion: v1 kind: Secret metadata: - name: {{ include "kubevoyage.fullname" . }}-db-secret + name: {{ include "kubevoyage.fullname" . }}-secret + namespace: {{ .Values.app.namespace }} type: Opaque -data: - db-password: {{ .Values.database.password | b64enc | quote }} \ No newline at end of file +stringData: + db-password: {{ .Values.database.password }} + jwt-secret: {{ .Values.additionalEnvVars.jwtSecret }} + admin-user: {{ .Values.auth.adminUser }} + admin-password: {{ .Values.auth.adminPassword }} \ No newline at end of file diff --git a/deploy/charts/templates/service.yaml b/deploy/charts/templates/service.yaml index f9e05f0..0023b84 100644 --- a/deploy/charts/templates/service.yaml +++ b/deploy/charts/templates/service.yaml @@ -1,7 +1,8 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "kubevoyage.fullname" . }} + name: kubevoyage + namespace: {{ .Values.app.namespace }} labels: app: {{ include "kubevoyage.fullname" . }} spec: diff --git a/deploy/charts/values.yaml b/deploy/charts/values.yaml index 54c2f35..ba8ee3d 100644 --- a/deploy/charts/values.yaml +++ b/deploy/charts/values.yaml @@ -1,5 +1,8 @@ replicaCount: 1 + +app: + namespace: image: repository: kubevoyage tag: "latest" @@ -9,6 +12,32 @@ service: type: ClusterIP port: 80 +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + service: + name: kubevoyage + port: 80 + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + +auth: + adminUser: + adminPassword: + +additionalEnvVars: + jwtSecret: + baseUrl: database: type: postgres # default type, can be changed to mysql, sqlite, etc. host: diff --git a/frontend/public/Kubevoyage.png b/frontend/public/Kubevoyage.png new file mode 100644 index 0000000..258dc71 Binary files /dev/null and b/frontend/public/Kubevoyage.png differ diff --git a/frontend/public/index.html b/frontend/public/index.html index 5da7ed3..a52ad3e 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -4,7 +4,7 @@ -