From 3df95dc685d1575c5c736e74a8d0fb51cdb39e53 Mon Sep 17 00:00:00 2001 From: Salil Ponde Date: Fri, 22 Dec 2023 21:47:41 +0530 Subject: [PATCH] 1.4.1 (#12) * Bumped version to 1.4.1 * Prevent deleting of credentials which are in use * SSL certificate Support --- .github/workflows/main.yml | 2 +- cmd/server/main.go | 1 + pkg/common/common.go | 2 +- pkg/crypto/ssl/ssl.go | 53 +++++++++++++++++++++++++++++ pkg/server/handler/credential.go | 10 ++++++ pkg/server/server.go | 57 ++++++++++++++++++++++++++++++-- pkg/server/store/credential.go | 23 +++++++++++++ pkg/server/store/environment.go | 1 - pkg/server/store/interfaces.go | 1 + runserver.ps1 | 1 + web/src/lib/version.ts | 2 +- web/vite.config.ts | 6 ++-- 12 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 pkg/crypto/ssl/ssl.go diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1151fa0..3af002d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: branches: ["main"] env: - VERSION: "1.4" + VERSION: "1.4.1" jobs: docker: diff --git a/cmd/server/main.go b/cmd/server/main.go index f154a95..3c97a3d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,6 +12,7 @@ func main() { os.Getenv("DB_CONNECTION_STRING"), os.Getenv("DATA_PATH"), os.Getenv("LOG_LEVEL"), + os.Getenv("SSL_ENABLED"), ) s.Run(os.Getenv("BIND_ADDRESS")) } \ No newline at end of file diff --git a/pkg/common/common.go b/pkg/common/common.go index 9cb437b..f939887 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -1,3 +1,3 @@ package common -const Version = "1.4" \ No newline at end of file +const Version = "1.4.1" \ No newline at end of file diff --git a/pkg/crypto/ssl/ssl.go b/pkg/crypto/ssl/ssl.go new file mode 100644 index 0000000..e542925 --- /dev/null +++ b/pkg/crypto/ssl/ssl.go @@ -0,0 +1,53 @@ +package ssl + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "time" +) + +func GenerateSelfSignedCert() (cert string, key string, err error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return "", "", err + } + + notBefore := time.Now() + notAfter := notBefore.Add(365 * 24 * 10 * time.Hour) + template := x509.Certificate{ + SerialNumber: big.NewInt(0), + Subject: pkix.Name{}, + SignatureAlgorithm: x509.SHA256WithRSA, + NotBefore: notBefore, + NotAfter: notAfter, + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment | x509.KeyUsageDataEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + } + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return "", "", err + + } + + cert = string(pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE", + Bytes: derBytes, + }, + )) + + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + key = string(pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privateKeyBytes, + }, + )) + + return cert, key, nil +} \ No newline at end of file diff --git a/pkg/server/handler/credential.go b/pkg/server/handler/credential.go index 4aaccd7..232859b 100644 --- a/pkg/server/handler/credential.go +++ b/pkg/server/handler/credential.go @@ -1,6 +1,7 @@ package handler import ( + "errors" "strconv" "github.com/productiveops/dokemon/pkg/server/model" @@ -109,6 +110,15 @@ func (h *Handler) DeleteCredentialById(c echo.Context) error { return resourceNotFound(c, "Credential") } + inUse, err := h.credentialStore.IsInUse(uint(id)) + if err != nil { + panic(err) + } + + if inUse { + return unprocessableEntity(c, errors.New("Credentials are in use and cannot be deleted")) + } + if err := h.credentialStore.DeleteById(uint(id)); err != nil { panic(err) } diff --git a/pkg/server/server.go b/pkg/server/server.go index 2894a0c..1e9d387 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -4,11 +4,13 @@ import ( "errors" "net/http" "os" + "path" "strings" "time" "github.com/productiveops/dokemon/pkg/common" "github.com/productiveops/dokemon/pkg/crypto/ske" + "github.com/productiveops/dokemon/pkg/crypto/ssl" "github.com/productiveops/dokemon/pkg/server/handler" "github.com/productiveops/dokemon/pkg/server/model" "github.com/productiveops/dokemon/pkg/server/requestutil" @@ -28,19 +30,24 @@ import ( type Server struct { Echo *echo.Echo handler *handler.Handler + dataPath string + sslEnabled bool } -func (s *Server) Init(dbConnectionString string, dataPath string, logLevel string) { +func (s *Server) Init(dbConnectionString string, dataPath string, logLevel string, sslEnabled string) { setLogLevel(logLevel) log.Info().Msg("Starting Dokemon v" + common.Version) if dataPath == "" { dataPath = "/data" } + s.dataPath = dataPath if dbConnectionString == "" { dbConnectionString = dataPath + "/db" } + + s.sslEnabled = sslEnabled == "1" // Init compose projects directory composeProjectsPath := dataPath + "/compose" @@ -126,7 +133,18 @@ func (s *Server) Run(addr string) { addr = ":9090" } - err := s.Echo.Start(addr) + var err error + if s.sslEnabled { + certsDirPath := path.Join(s.dataPath, "certs") + certPath := path.Join(certsDirPath, "server.crt") + keyPath := path.Join(certsDirPath, "server.key") + s.generateSelfSignedCerts(certsDirPath, certPath, keyPath) + + err = s.Echo.StartTLS(addr, certPath, keyPath) + } else { + err = s.Echo.Start(addr) + } + if err != nil { panic(err) } @@ -154,6 +172,41 @@ func (s *Server) authMiddleware(next echo.HandlerFunc) echo.HandlerFunc { } } +func (s *Server) generateSelfSignedCerts(certDirPath string, certPath string, keyPath string) { + if _, err := os.Stat(certDirPath); errors.Is(err, os.ErrNotExist) { + err := os.MkdirAll(certDirPath, os.ModePerm) + if err != nil { + panic(err) + } + } + if _, err := os.Stat(certPath); errors.Is(err, os.ErrNotExist) { + log.Debug().Msg("SSL certificate file does not exist. Generating self-signed certificate...") + + cert, key, err := ssl.GenerateSelfSignedCert() + if err != nil { + panic(err) + } + + certFile, err := os.Create(certPath) + if err != nil { + panic(err) + } + _, err = certFile.WriteString(cert) + if err != nil { + panic(err) + } + + keyFile, err := os.Create(keyPath) + if err != nil { + panic(err) + } + _, err = keyFile.WriteString(key) + if err != nil { + panic(err) + } + } +} + func setLogLevel(logLevel string) { log.Info().Str("level", logLevel).Msg("Setting log level") switch logLevel { diff --git a/pkg/server/store/credential.go b/pkg/server/store/credential.go index b84af74..084bc87 100644 --- a/pkg/server/store/credential.go +++ b/pkg/server/store/credential.go @@ -49,7 +49,30 @@ func (s *SqlCredentialStore) Exists(id uint) (bool, error) { return count > 0, nil } +func (s *SqlCredentialStore) IsInUse(id uint) (bool, error) { + var ncp_ref_count, cli_ref_count int64 + + if err := s.db.Model(&model.NodeComposeProject{}).Where("credential_id = ?", id).Count(&ncp_ref_count).Error; err != nil { + return false, err + } + + if err := s.db.Model(&model.ComposeLibraryItem{}).Where("credential_id = ?", id).Count(&cli_ref_count).Error; err != nil { + return false, err + } + + return (ncp_ref_count + cli_ref_count) > 0, nil +} + func (s *SqlCredentialStore) DeleteById(id uint) error { + inUse, err := s.IsInUse(id) + if err != nil { + return err + } + + if inUse { + return errors.New("Credentials are in use and cannot be deleted") + } + if err := s.db.Delete(&model.Credential{}, id).Error; err != nil { return err } diff --git a/pkg/server/store/environment.go b/pkg/server/store/environment.go index 6a435c9..3fefb55 100644 --- a/pkg/server/store/environment.go +++ b/pkg/server/store/environment.go @@ -60,7 +60,6 @@ func (s *SqlEnvironmentStore) IsInUse(id uint) (bool, error) { return node_ref_count > 0, nil } - func (s *SqlEnvironmentStore) DeleteById(id uint) error { inUse, err := s.IsInUse(id) if err != nil { diff --git a/pkg/server/store/interfaces.go b/pkg/server/store/interfaces.go index 357db1b..569e3ae 100644 --- a/pkg/server/store/interfaces.go +++ b/pkg/server/store/interfaces.go @@ -83,6 +83,7 @@ type CredentialStore interface { Update(m *model.Credential) error GetById(id uint) (*model.Credential, error) GetList(pageNo, pageSize uint) ([]model.Credential, int64, error) + IsInUse(id uint) (bool, error) DeleteById(id uint) error Exists(id uint) (bool, error) diff --git a/runserver.ps1 b/runserver.ps1 index 1bc5c27..6b48e36 100644 --- a/runserver.ps1 +++ b/runserver.ps1 @@ -14,4 +14,5 @@ go build .\cmd\server\ $env:DB_CONNECTION_STRING="c:\temp\dokemondata\db" $env:DATA_PATH="c:\temp\dokemondata" $env:LOG_LEVEL="DEBUG" +$env:SSL_ENABLED="1" .\server.exe diff --git a/web/src/lib/version.ts b/web/src/lib/version.ts index b731087..1b3b7c8 100644 --- a/web/src/lib/version.ts +++ b/web/src/lib/version.ts @@ -1 +1 @@ -export const VERSION = "1.4" +export const VERSION = "1.4.1" diff --git a/web/vite.config.ts b/web/vite.config.ts index b36480c..c4cee25 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -8,11 +8,13 @@ export default defineConfig(({ command, mode }) => { if (mode === "development") { proxy = { "/api": { - target: "http://localhost:9090", + target: "https://localhost:9090", + secure: false, // Allow self-signed certificates }, "/ws": { - target: "http://localhost:9090", + target: "https://localhost:9090", ws: true, + secure: false, // Allow self-signed certificates }, } }