package main import ( "ccsecrets/controllers" "ccsecrets/middlewares" "ccsecrets/models" "ccsecrets/utils" "context" "crypto/tls" "fmt" "io" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) // For build numbers, from https://blog.kowalczyk.info/article/vEja/embedding-build-number-in-go-executable.html var sha1ver string // sha1 revision used to build the program var buildTime string // when the executable was built func main() { // Open connection to logfile // From https://ispycode.com/GO/Logging/Logging-to-multiple-destinations logFile := os.Getenv("LOG_FILE") if logFile == "" { logFile = "./ccsecrets.log" } logfileWriter, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Println("Unable to write logfile", err) os.Exit(1) } // Initiate connection to sqlite and make sure our schema is up to date models.ConnectDatabase() // Create context that listens for the interrupt signal from the OS. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() // Creates a router without any middleware by default router := gin.New() // Global middleware // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout gin.DefaultWriter = io.MultiWriter(logfileWriter, os.Stdout) router.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. router.Use(gin.Recovery()) // TODO - think of a better default landing page router.GET("/", func(c *gin.Context) { //time.Sleep(10 * time.Second) c.String(http.StatusOK, fmt.Sprintf("Built on %s from sha1 %s\n", buildTime, sha1ver)) }) // Set some options for TLS tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, PreferServerCipherSuites: true, InsecureSkipVerify: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, }, } // Determine bind IP bindIP := os.Getenv("BIND_IP") if bindIP == "" { bindIP = utils.GetOutboundIP().String() } // Determine bind port bindPort := os.Getenv("BIND_PORT") if bindPort == "" { bindIP = "8443" } bindAddress := fmt.Sprint(bindIP, ":", bindPort) fmt.Printf("Will listen on address 'https://%s'\n", bindAddress) // Generate certificate if required tlsCertFilename := utils.GetFilePath(os.Getenv("TLS_CERT_FILE")) tlsKeyFilename := utils.GetFilePath(os.Getenv("TLS_KEY_FILE")) if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) { fmt.Printf("Specified TLS certificate (%s) or private key (%s) do not exist.\n", tlsCertFilename, tlsKeyFilename) utils.GenerateCerts(tlsCertFilename, tlsKeyFilename) } srv := &http.Server{ Addr: bindAddress, Handler: router, TLSConfig: tlsConfig, } // Register our routes public := router.Group("/api") public.POST("/login", controllers.Login) // TODO - this should be an authenticated route adminOnly := router.Group("/api/admin") adminOnly.Use(middlewares.JwtAuthAdminMiddleware()) adminOnly.POST("/register", controllers.Register) // Get secrets protected := router.Group("/api/secret") protected.Use(middlewares.JwtAuthMiddleware()) protected.GET("/retrieve", controllers.RetrieveSecret) protected.GET("/retrieveMultiple", controllers.RetrieveMultpleSecrets) protected.POST("/store", controllers.StoreSecret) // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { if err := srv.ListenAndServeTLS(tlsCertFilename, tlsKeyFilename); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // Listen for the interrupt signal. <-ctx.Done() // Restore default behavior on the interrupt signal and notify user of shutdown. stop() log.Println("shutting down gracefully, press Ctrl+C again to force") models.DisconnectDatabase() // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown: ", err) } log.Println("Server exiting") }