瀏覽代碼

v0.0.8 Automatic HTTPS certificate provisioning

Yann Stepienik 2 年之前
父節點
當前提交
1fea4ef573

+ 4 - 0
.github/ISSUE_TEMPLATE/feature request.yml

@@ -0,0 +1,4 @@
+name: Feature Request
+about: Request a new feature
+title: "[FEAT]: "
+labels: [feature, review]

+ 60 - 0
.github/ISSUE_TEMPLATE/issue report.yml

@@ -0,0 +1,60 @@
+name: Issue Report
+description: File an issue report
+title: "[BUG]: "
+labels: [bug, review]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for taking the time to fill out this bug report!
+  - type: textarea
+    id: what-happened
+    attributes:
+      label: What happened?
+      description: "A clear and concise description of what the bug is."
+      placeholder: "When I do X, I see Y."
+    validations:
+      required: true
+  - type: textarea
+    id: what-should-happen
+    attributes:
+      label: What should have happened?
+      description: "A clear and concise description of what you expected to happen."
+      placeholder: "When I do X, I should see Z."
+    validations:
+      required: true
+  - type: textarea
+    id: reproduction
+    attributes:
+      label: How to reproduce the bug?
+      description: "A clear and concise description of how to reproduce the bug."
+      placeholder: "Steps to reproduce the behavior:"
+      value: |
+        1. Go to '...'
+        2. Click on '....'
+        3. Scroll down to '....'
+        4. See error
+    validations:
+      required: true
+  - type: textarea
+    id: logs
+    attributes:
+      label: Relevant log output
+      description: "Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks."
+      render: shell
+  - type: textarea
+    id: other-details
+    attributes:
+      label: Other details
+      description: "Anything else you'd like to add?"
+  - type: textarea
+    id: system-details
+    attributes:
+      label: System details
+      description: "Please fill out the following details about your system depending on how relevant (always include Cosmos Version!)."
+      value: |
+        - OS: [e.g. iOS]
+        - Browser [e.g. chrome, safari]
+        - Version [e.g. 22]
+    validations:
+      required: true

+ 10 - 1
client/src/menu-items/support.jsx

@@ -1,5 +1,5 @@
 // assets
 // assets
-import { GithubOutlined, QuestionOutlined } from '@ant-design/icons';
+import { GithubOutlined, QuestionOutlined, BugOutlined } from '@ant-design/icons';
 import DiscordOutlined from '../assets/images/icons/discord.svg'
 import DiscordOutlined from '../assets/images/icons/discord.svg'
 import DiscordOutlinedWhite from '../assets/images/icons/discord_white.svg'
 import DiscordOutlinedWhite from '../assets/images/icons/discord_white.svg'
 import { useTheme } from '@mui/material/styles';
 import { useTheme } from '@mui/material/styles';
@@ -45,6 +45,15 @@ const support = {
             icon: QuestionOutlined,
             icon: QuestionOutlined,
             external: true,
             external: true,
             target: true
             target: true
+        },
+        {
+            id: 'bug',
+            title: 'Found a Bug?',
+            type: 'item',
+            url: 'https://github.com/azukaar/Cosmos-Server/issues/new/choose',
+            icon: BugOutlined,
+            external: true,
+            target: true
         }
         }
     ]
     ]
 };
 };

+ 60 - 45
client/src/pages/config/users/configman.jsx

@@ -28,6 +28,7 @@ import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
 import AnimateButton from '../../../components/@extended/AnimateButton';
 import AnimateButton from '../../../components/@extended/AnimateButton';
 import RestartModal from './restart';
 import RestartModal from './restart';
 import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
 import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
+import { CosmosInputText, CosmosSelect } from './formShortcuts';
 
 
 
 
 const ConfigManagement = () => {
 const ConfigManagement = () => {
@@ -61,6 +62,8 @@ const ConfigManagement = () => {
           GenerateMissingAuthCert: config.HTTPConfig.GenerateMissingAuthCert,
           GenerateMissingAuthCert: config.HTTPConfig.GenerateMissingAuthCert,
           HTTPPort: config.HTTPConfig.HTTPPort,
           HTTPPort: config.HTTPConfig.HTTPPort,
           HTTPSPort: config.HTTPConfig.HTTPSPort,
           HTTPSPort: config.HTTPConfig.HTTPSPort,
+          SSLEmail: config.HTTPConfig.SSLEmail,
+          HTTPSCertificateMode: config.HTTPConfig.HTTPSCertificateMode,
         }}
         }}
         validationSchema={Yup.object().shape({
         validationSchema={Yup.object().shape({
           Hostname: Yup.string().max(255).required('Hostname is required'),
           Hostname: Yup.string().max(255).required('Hostname is required'),
@@ -76,10 +79,11 @@ const ConfigManagement = () => {
               HTTPConfig: {
               HTTPConfig: {
                 ...config.HTTPConfig,
                 ...config.HTTPConfig,
                 Hostname: values.Hostname,
                 Hostname: values.Hostname,
-                GenerateMissingTLSCert: values.GenerateMissingTLSCert,
                 GenerateMissingAuthCert: values.GenerateMissingAuthCert,
                 GenerateMissingAuthCert: values.GenerateMissingAuthCert,
                 HTTPPort: values.HTTPPort,
                 HTTPPort: values.HTTPPort,
                 HTTPSPort: values.HTTPSPort,
                 HTTPSPort: values.HTTPSPort,
+                SSLEmail: values.SSLEmail,
+                HTTPSCertificateMode: values.HTTPSCertificateMode,
               }
               }
             }
             }
             
             
@@ -106,8 +110,8 @@ const ConfigManagement = () => {
           }
           }
         }}
         }}
       >
       >
-        {({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
-          <form noValidate onSubmit={handleSubmit}>
+        {(formik) => (
+          <form noValidate onSubmit={formik.handleSubmit}>
             <MainCard title="General">
             <MainCard title="General">
               <Grid container spacing={3}>
               <Grid container spacing={3}>
                 <Grid item xs={12}>
                 <Grid item xs={12}>
@@ -119,17 +123,17 @@ const ConfigManagement = () => {
                     <OutlinedInput
                     <OutlinedInput
                       id="MongoDB-login"
                       id="MongoDB-login"
                       type="password"
                       type="password"
-                      value={values.MongoDB}
+                      value={formik.values.MongoDB}
                       name="MongoDB"
                       name="MongoDB"
-                      onBlur={handleBlur}
-                      onChange={handleChange}
+                      onBlur={formik.handleBlur}
+                      onChange={formik.handleChange}
                       placeholder="MongoDB"
                       placeholder="MongoDB"
                       fullWidth
                       fullWidth
-                      error={Boolean(touched.MongoDB && errors.MongoDB)}
+                      error={Boolean(formik.touched.MongoDB && formik.errors.MongoDB)}
                     />
                     />
-                    {touched.MongoDB && errors.MongoDB && (
+                    {formik.touched.MongoDB && formik.errors.MongoDB && (
                       <FormHelperText error id="standard-weight-helper-text-MongoDB-login">
                       <FormHelperText error id="standard-weight-helper-text-MongoDB-login">
-                        {errors.MongoDB}
+                        {formik.errors.MongoDB}
                       </FormHelperText>
                       </FormHelperText>
                     )}
                     )}
                   </Stack>
                   </Stack>
@@ -144,14 +148,14 @@ const ConfigManagement = () => {
                       name="LoggingLevel"
                       name="LoggingLevel"
                       id="LoggingLevel"
                       id="LoggingLevel"
                       select
                       select
-                      value={values.LoggingLevel}
-                      onChange={handleChange}
+                      value={formik.values.LoggingLevel}
+                      onChange={formik.handleChange}
                       error={
                       error={
-                        touched.LoggingLevel &&
-                        Boolean(errors.LoggingLevel)
+                        formik.touched.LoggingLevel &&
+                        Boolean(formik.errors.LoggingLevel)
                       }
                       }
                       helperText={
                       helperText={
-                        touched.LoggingLevel && errors.LoggingLevel
+                        formik.touched.LoggingLevel && formik.errors.LoggingLevel
                       }
                       }
                     >
                     >
                       <MenuItem key={"DEBUG"} value={"DEBUG"}>
                       <MenuItem key={"DEBUG"} value={"DEBUG"}>
@@ -182,17 +186,17 @@ const ConfigManagement = () => {
                     <OutlinedInput
                     <OutlinedInput
                       id="Hostname-login"
                       id="Hostname-login"
                       type="text"
                       type="text"
-                      value={values.Hostname}
+                      value={formik.values.Hostname}
                       name="Hostname"
                       name="Hostname"
-                      onBlur={handleBlur}
-                      onChange={handleChange}
+                      onBlur={formik.handleBlur}
+                      onChange={formik.handleChange}
                       placeholder="Hostname"
                       placeholder="Hostname"
                       fullWidth
                       fullWidth
-                      error={Boolean(touched.Hostname && errors.Hostname)}
+                      error={Boolean(formik.touched.Hostname && formik.errors.Hostname)}
                     />
                     />
-                    {touched.Hostname && errors.Hostname && (
+                    {formik.touched.Hostname && formik.errors.Hostname && (
                       <FormHelperText error id="standard-weight-helper-text-Hostname-login">
                       <FormHelperText error id="standard-weight-helper-text-Hostname-login">
-                        {errors.Hostname}
+                        {formik.errors.Hostname}
                       </FormHelperText>
                       </FormHelperText>
                     )}
                     )}
                   </Stack>
                   </Stack>
@@ -204,17 +208,17 @@ const ConfigManagement = () => {
                     <OutlinedInput
                     <OutlinedInput
                       id="HTTPPort-login"
                       id="HTTPPort-login"
                       type="text"
                       type="text"
-                      value={values.HTTPPort}
+                      value={formik.values.HTTPPort}
                       name="HTTPPort"
                       name="HTTPPort"
-                      onBlur={handleBlur}
-                      onChange={handleChange}
+                      onBlur={formik.handleBlur}
+                      onChange={formik.handleChange}
                       placeholder="HTTPPort"
                       placeholder="HTTPPort"
                       fullWidth
                       fullWidth
-                      error={Boolean(touched.HTTPPort && errors.HTTPPort)}
+                      error={Boolean(formik.touched.HTTPPort && formik.errors.HTTPPort)}
                     />
                     />
-                    {touched.HTTPPort && errors.HTTPPort && (
+                    {formik.touched.HTTPPort && formik.errors.HTTPPort && (
                       <FormHelperText error id="standard-weight-helper-text-HTTPPort-login">
                       <FormHelperText error id="standard-weight-helper-text-HTTPPort-login">
-                        {errors.HTTPPort}
+                        {formik.errors.HTTPPort}
                       </FormHelperText>
                       </FormHelperText>
                     )}
                     )}
                   </Stack>
                   </Stack>
@@ -226,17 +230,17 @@ const ConfigManagement = () => {
                     <OutlinedInput
                     <OutlinedInput
                       id="HTTPSPort-login"
                       id="HTTPSPort-login"
                       type="text"
                       type="text"
-                      value={values.HTTPSPort}
+                      value={formik.values.HTTPSPort}
                       name="HTTPSPort"
                       name="HTTPSPort"
-                      onBlur={handleBlur}
-                      onChange={handleChange}
+                      onBlur={formik.handleBlur}
+                      onChange={formik.handleChange}
                       placeholder="HTTPSPort"
                       placeholder="HTTPSPort"
                       fullWidth
                       fullWidth
-                      error={Boolean(touched.HTTPSPort && errors.HTTPSPort)}
+                      error={Boolean(formik.touched.HTTPSPort && formik.errors.HTTPSPort)}
                     />
                     />
-                    {touched.HTTPSPort && errors.HTTPSPort && (
+                    {formik.touched.HTTPSPort && formik.errors.HTTPSPort && (
                       <FormHelperText error id="standard-weight-helper-text-HTTPSPort-login">
                       <FormHelperText error id="standard-weight-helper-text-HTTPSPort-login">
-                        {errors.HTTPSPort}
+                        {formik.errors.HTTPSPort}
                       </FormHelperText>
                       </FormHelperText>
                     )}
                     )}
                   </Stack>
                   </Stack>
@@ -249,17 +253,28 @@ const ConfigManagement = () => {
                 <Grid item xs={12}>
                 <Grid item xs={12}>
                   <Alert severity="info">For security reasons, It is not possible to remotely change the Private keys of any certificates on your instance. It is advised to manually edit the config file, or better, use Environment Variables to store them.</Alert>
                   <Alert severity="info">For security reasons, It is not possible to remotely change the Private keys of any certificates on your instance. It is advised to manually edit the config file, or better, use Environment Variables to store them.</Alert>
                 </Grid>
                 </Grid>
-                <Grid item xs={12}>
-                  <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
-                    <Field
-                      type="checkbox"
-                      name="GenerateMissingTLSCert"
-                      as={FormControlLabel}
-                      control={<Checkbox size="large" />}
-                      label="Generate missing HTTPS Certificates automatically (Default: true)"
+
+                <CosmosSelect
+                  name="HTTPSCertificateMode"
+                  label="HTTPS Certificates"
+                  formik={formik}
+                  options={[
+                    ["LETSENCRYPT", "Automatically generate certificates using Let's Encrypt (Recommended)"],
+                    ["SELFSIGNED", "Locally self-sign certificates (unsecure)"],
+                    ["PROVIDED", "I have my own certificates"],
+                    ["DISABLED", "Do not use HTTPS (very unsecure)"],
+                  ]}
+                />
+
+                {
+                  formik.values.HTTPSCertificateMode === "LETSENCRYPT" && (
+                    <CosmosInputText
+                      name="SSLEmail"
+                      label="Email address for Let's Encrypt"
+                      formik={formik}
                     />
                     />
-                  </Stack>
-                </Grid>
+                  )
+                }
 
 
                 <Grid item xs={12}>
                 <Grid item xs={12}>
                   <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
                   <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
@@ -297,16 +312,16 @@ const ConfigManagement = () => {
             <br /><br />
             <br /><br />
 
 
             <MainCard>
             <MainCard>
-              {errors.submit && (
+              {formik.errors.submit && (
                 <Grid item xs={12}>
                 <Grid item xs={12}>
-                  <FormHelperText error>{errors.submit}</FormHelperText>
+                  <FormHelperText error>{formik.errors.submit}</FormHelperText>
                 </Grid>
                 </Grid>
               )}
               )}
               <Grid item xs={12}>
               <Grid item xs={12}>
                 <AnimateButton>
                 <AnimateButton>
                   <Button
                   <Button
                     disableElevation
                     disableElevation
-                    disabled={isSubmitting}
+                    disabled={formik.isSubmitting}
                     fullWidth
                     fullWidth
                     size="large"
                     size="large"
                     type="submit"
                     type="submit"

+ 2 - 3
client/src/pages/servapps/servapps.jsx

@@ -1,5 +1,5 @@
 // material-ui
 // material-ui
-import { Typography } from '@mui/material';
+import { Alert, Typography } from '@mui/material';
 import { useState } from 'react';
 import { useState } from 'react';
 
 
 // project import
 // project import
@@ -11,9 +11,8 @@ const ServeApps = () => {
   const {serveApps, setServeApps} = useState([]);
   const {serveApps, setServeApps} = useState([]);
 
 
   return <div>
   return <div>
-    Nothing Yet :)
+    <Alert severity="info">Implementation currently in progress! If you want to voice your opinion on where Cosmos is going, please join us on Discord!</Alert>
   </div>
   </div>
-
 }
 }
 
 
 export default ServeApps;
 export default ServeApps;

+ 6 - 0
client/src/routes/MainRoutes.jsx

@@ -7,6 +7,7 @@ import UserManagement from '../pages/config/users/usermanagement';
 import ConfigManagement from '../pages/config/users/configman';
 import ConfigManagement from '../pages/config/users/configman';
 import ProxyManagement from '../pages/config/users/proxyman';
 import ProxyManagement from '../pages/config/users/proxyman';
 import ServeApps from '../pages/servapps/servapps';
 import ServeApps from '../pages/servapps/servapps';
+import { Navigate } from 'react-router';
 
 
 // render - dashboard
 // render - dashboard
 const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard')));
 const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard')));
@@ -26,6 +27,11 @@ const MainRoutes = {
     path: '/',
     path: '/',
     element: <MainLayout />,
     element: <MainLayout />,
     children: [
     children: [
+        {
+            path: '/',
+            // redirect to /ui
+            element: <Navigate to="/ui" />
+        },
         {
         {
             path: '/ui/',
             path: '/ui/',
             element: <DashboardDefault />
             element: <DashboardDefault />

二進制
diag_SN.png


二進制
diag_SN2.png


+ 1 - 1
docker.sh

@@ -18,7 +18,7 @@ sh build arm64.sh
 docker build \
 docker build \
   -t azukaar/cosmos-server:$VERSION-arm64 \
   -t azukaar/cosmos-server:$VERSION-arm64 \
   -t azukaar/cosmos-server:latest-arm64 \
   -t azukaar/cosmos-server:latest-arm64 \
-  -f Dockerfile.arm64 \
+  -f dockerfile.arm64 \
   --platform linux/arm64 \
   --platform linux/arm64 \
   .
   .
 
 

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "cosmos-server",
   "name": "cosmos-server",
-  "version": "0.0.7",
+  "version": "0.0.8",
   "description": "",
   "description": "",
   "main": "test-server.js",
   "main": "test-server.js",
   "bugs": {
   "bugs": {

+ 11 - 31
readme.md

@@ -14,11 +14,12 @@ Cosmos is a server platform for running self-hosted applications securely and wi
 
 
 Whether you have a **server**, a **NAS**, or a **Raspberry Pi** with applications such as **Plex**, **HomeAssistant** or even a blog, Cosmos is the perfect solution to secure it all. Simply install Cosmos on your server and connect to your applications through it to enjoy built-in security and robustness for all your services, right out of the box.
 Whether you have a **server**, a **NAS**, or a **Raspberry Pi** with applications such as **Plex**, **HomeAssistant** or even a blog, Cosmos is the perfect solution to secure it all. Simply install Cosmos on your server and connect to your applications through it to enjoy built-in security and robustness for all your services, right out of the box.
 
 
- * **Authentication** Connect to all your application with the same account, including strong security and **multi-factor authentication**
- * **Automatic HTTPS** certificates provision
- * **Anti-bot** protections such as Captcha and IP rate limiting
- * **Anti-DDOS** protections such as variable timeouts/throttling, IP rate limiting and IP blacklisting
- * **Proper user management** to invite your friends and family to your applications without awkardly sharing credentials. Let them request a password change with an email rather than having you unlock their account manually!
+ * **Authentication** 👦👩 Connect to all your application with the same account, including strong security and **multi-factor authentication**
+ * **Automatic HTTPS** 🔑📜 certificates provision
+ * **Anti-Bot** 🤖❌ protections such as Captcha and IP rate limiting
+ * **Anti-DDOS** 🔥⛔️ protections such as variable timeouts/throttling, IP rate limiting and IP blacklisting
+ * **Proper User Management** 🪪 ❎ to invite your friends and family to your applications without awkardly sharing credentials. Let them request a password change with an email rather than having you unlock their account manually!
+ * **Container Management** 🧱🔧 to easily manage your containers and their settings, keep them up to date as well as audit their security.
 
 
 And a **lot more planned features** are coming!
 And a **lot more planned features** are coming!
 
 
@@ -34,6 +35,10 @@ It is becoming an important **threat to you**. Managing servers, applications an
 
 
 It is even more important since most tools used to self-host **not specifically designed to be secure for your scenario**. Entreprise tools such as Traefik, NGinx, etc... Are designed for different use-cases that assume that the code you are running behind them is **not malicious**. But who knows what server apps you might be running? On top of that, a lot of reverse-proxies and security tools lock important security features behind 3 to 4 figures business subscriptions that are not realistic for selfhosting. Here's a simple example of how Cosmos can help you:
 It is even more important since most tools used to self-host **not specifically designed to be secure for your scenario**. Entreprise tools such as Traefik, NGinx, etc... Are designed for different use-cases that assume that the code you are running behind them is **not malicious**. But who knows what server apps you might be running? On top of that, a lot of reverse-proxies and security tools lock important security features behind 3 to 4 figures business subscriptions that are not realistic for selfhosting. Here's a simple example of how Cosmos can help you:
 
 
+![diag_SN](./diag_SN2.png)
+
+Another example:
+
 ![diag_SN](./diag_SN.png)
 ![diag_SN](./diag_SN.png)
 
 
 Another big issue is, because every new self-hosted applications **re-invent the wheel** and implement **crucial systems** such as authentication **from scratch** everytime, the **large majority** of them are very succeptible to being **hacked without too much trouble**.
 Another big issue is, because every new self-hosted applications **re-invent the wheel** and implement **crucial systems** such as authentication **from scratch** everytime, the **large majority** of them are very succeptible to being **hacked without too much trouble**.
@@ -47,35 +52,10 @@ That is the issue Cosmos Server is trying to solve: by providing a secure and ro
 Installation is simple using Docker:
 Installation is simple using Docker:
 
 
 ```
 ```
-docker run -d -p 80:80 -p 443:443 -v /path/to/cosmos/config:/config azukaar/cosmos-server:latest
+docker run -d -p 80:80 -p 443:443 --name cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/cosmos/config:/config azukaar/cosmos-server:latest
 ```
 ```
 
 
 you can use `latest-arm64` for arm architecture (ex: NAS or Raspberry)
 you can use `latest-arm64` for arm architecture (ex: NAS or Raspberry)
 
 
 You can thing tweak the config file accordingly. Some settings can be changed before end with env var. [see here](https://github.com/azukaar/Cosmos-Server/wiki/Configuration).
 You can thing tweak the config file accordingly. Some settings can be changed before end with env var. [see here](https://github.com/azukaar/Cosmos-Server/wiki/Configuration).
 
 
-# How to contribute
-
-## Setup
-
-You need [GuPM](https://github.com/azukaar/GuPM) with the [provider-go](https://github.com/azukaar/GuPM-official#provider-go) plugin to run this project.
-
-```
-g make
-```
-
-## Run locally
-
-First create a file called dev.json with:
-
-```json
-{
-  "MONGODB": "your mongodb connection string"
-}
-```
-
-```
-g build
-g start # this will run server
-g client # this will run the client
-```

二進制
schema.png


+ 7 - 1
src/docker/docker.go

@@ -47,7 +47,13 @@ func connect() error {
 		DockerClient = client
 		DockerClient = client
 		DockerContext = ctx
 		DockerContext = ctx
 
 
-		CreateCosmosNetwork()
+		ping, err := DockerClient.Ping(DockerContext)
+		if ping.APIVersion != "" && err == nil {
+			utils.Log("Docker Connected")
+		} else {
+			utils.Error("Docker Connection - Cannot ping Daemon. Is it running?", nil)
+			return errors.New("Docker Connection - Cannot ping Daemon. Is it running?")
+		}
 		
 		
 		// if running in Docker, connect to main network
 		// if running in Docker, connect to main network
 		// if os.Getenv("HOSTNAME") != "" {
 		// if os.Getenv("HOSTNAME") != "" {

+ 5 - 1
src/docker/events.go

@@ -11,6 +11,7 @@ import (
 func DockerListenEvents() error {
 func DockerListenEvents() error {
 	errD := connect()
 	errD := connect()
 	if errD != nil {
 	if errD != nil {
+		utils.Error("Docker did not connect. Not listening", errD)
 		return errD
 		return errD
 	}
 	}
 	
 	
@@ -20,7 +21,10 @@ func DockerListenEvents() error {
 		for {
 		for {
 			select {
 			select {
 				case err := <-errs:
 				case err := <-errs:
-					utils.Error("Docker Events", err)
+					if err == nil {
+						return
+					}
+					utils.Error("Docker Event Error", err)
 				case msg := <-msgs:
 				case msg := <-msgs:
 					utils.Debug("Docker Event: " + msg.Type + " " + msg.Action + " " + msg.Actor.ID)
 					utils.Debug("Docker Event: " + msg.Type + " " + msg.Action + " " + msg.Actor.ID)
 					if msg.Type == "container" && msg.Action == "start" {
 					if msg.Type == "container" && msg.Action == "start" {

+ 78 - 63
src/httpServer.go

@@ -8,16 +8,17 @@ import (
 		"github.com/azukaar/cosmos-server/src/proxy"
 		"github.com/azukaar/cosmos-server/src/proxy"
 		"github.com/azukaar/cosmos-server/src/docker"
 		"github.com/azukaar/cosmos-server/src/docker"
 		"github.com/gorilla/mux"
 		"github.com/gorilla/mux"
-		"strings"
 		"strconv"
 		"strconv"
 		"time"
 		"time"
     "encoding/json"
     "encoding/json"
 		"os"
 		"os"
+		"strings"
 		"github.com/go-chi/chi/middleware"
 		"github.com/go-chi/chi/middleware"
 		"github.com/go-chi/httprate"
 		"github.com/go-chi/httprate"
 		"crypto/tls"
 		"crypto/tls"
 		spa "github.com/roberthodgen/spa-server"
 		spa "github.com/roberthodgen/spa-server"
-		// "github.com/foomo/simplecert"
+		"github.com/foomo/simplecert"
+		"github.com/foomo/tlsconfig"
 )
 )
 
 
 var serverPortHTTP = ""
 var serverPortHTTP = ""
@@ -34,73 +35,87 @@ func startHTTPServer(router *mux.Router) {
 }
 }
 
 
 func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
 func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
-	// cfg := simplecert.Default
-	// cfg.Domains = []string{"yourdomain.com", "www.yourdomain.com"}
-	// cfg.CacheDir = "/etc/letsencrypt/live/yourdomain.com"
-	// cfg.SSLEmail = "you@emailprovider.com"
-	// cfg.DNSProvider = "cloudflare"
-	// certReloader, err := simplecert.Init(cfg, nil)
-	// if err != nil {
-	// 		utils.Fatal("simplecert init failed: ", err)
-	// }	
+	config  := utils.GetMainConfig()
+
+	// check if Docker overwrite Hostname
+	serverHostname := "0.0.0.0" //utils.GetMainConfig().HTTPConfig.Hostname
+	// if os.Getenv("HOSTNAME") != "" {
+	// 	serverHostname = os.Getenv("HOSTNAME")
+	// }
+
+	cfg := simplecert.Default
+
+	cfg.Domains = utils.GetAllHostnames()
+	cfg.CacheDir = "/config/certificates"
+	cfg.SSLEmail = config.HTTPConfig.SSLEmail
+	cfg.HTTPAddress = serverHostname+":"+serverPortHTTP
+	cfg.TLSAddress = serverHostname+":"+serverPortHTTPS
+
+	var certReloader *simplecert.CertReloader 
+	var errSimCert error
+	if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
+		certReloader, errSimCert = simplecert.Init(cfg, nil)
+		if errSimCert != nil {
+				utils.Fatal("simplecert init failed: ", errSimCert)
+		}
+	}
 		
 		
-		// redirect http to https
-		go (func () {
-			err := http.ListenAndServe("0.0.0.0:" + serverPortHTTP, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				// change port in host
-				if strings.HasSuffix(r.Host, ":" + serverPortHTTP) {
-					if serverPortHTTPS != "443" {
-						r.Host = r.Host[:len(r.Host)-len(":" + serverPortHTTP)] + ":" + serverPortHTTPS
-						} else {
-						r.Host = r.Host[:len(r.Host)-len(":" + serverPortHTTP)]
-					}
+	// redirect http to https
+	go (func () {
+		// err := http.ListenAndServe("0.0.0.0:" + serverPortHTTP, http.HandlerFunc(simplecert.Redirect))
+		err := http.ListenAndServe("0.0.0.0:" + serverPortHTTP, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			// change port in host
+			if strings.HasSuffix(r.Host, ":" + serverPortHTTP) {
+				if serverPortHTTPS != "443" {
+					r.Host = r.Host[:len(r.Host)-len(":" + serverPortHTTP)] + ":" + serverPortHTTPS
+					} else {
+					r.Host = r.Host[:len(r.Host)-len(":" + serverPortHTTP)]
 				}
 				}
-				
-				http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently)
-			}))
-			if err != nil {
-				utils.Fatal("Listening to HTTP (Red)", err)
 			}
 			}
-		})()
+			
+			http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently)
+		}))
+		
+		if err != nil {
+			utils.Fatal("Listening to HTTP (Redirecting to HTTPS)", err)
+		}
+	})()
 
 
-		utils.Log("Listening to HTTP on :" + serverPortHTTP)
-		utils.Log("Listening to HTTPS on :" + serverPortHTTPS)
+	utils.Log("Listening to HTTP on :" + serverPortHTTP)
+	utils.Log("Listening to HTTPS on :" + serverPortHTTPS)
+
+	utils.IsHTTPS = true
 
 
-		utils.IsHTTPS = true
+	tlsConf := tlsconfig.NewServerTLSConfig(tlsconfig.TLSModeServerStrict)
 
 
+	if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
+		tlsConf.GetCertificate = certReloader.GetCertificateFunc()
+	} else {
 		cert, errCert := tls.X509KeyPair(([]byte)(tlsCert), ([]byte)(tlsKey))
 		cert, errCert := tls.X509KeyPair(([]byte)(tlsCert), ([]byte)(tlsKey))
 		if errCert != nil {
 		if errCert != nil {
 			utils.Fatal("Getting Certificate pair", errCert)
 			utils.Fatal("Getting Certificate pair", errCert)
 		}
 		}
 
 
-		tlsConfig := &tls.Config{
-			Certificates: []tls.Certificate{cert},
-			// Other options
-		}
-		
-		// check if Docker overwrite Hostname
-		serverHostname := utils.GetMainConfig().HTTPConfig.Hostname
-		if os.Getenv("HOSTNAME") != "" {
-			serverHostname = os.Getenv("HOSTNAME")
-		}
-		
-		server := http.Server{
-			TLSConfig: tlsConfig,
-			Addr: serverHostname + ":" + serverPortHTTPS,
-			ReadTimeout: 0,
-			ReadHeaderTimeout: 10 * time.Second,
-			WriteTimeout: 0,
-			IdleTimeout: 30 * time.Second,
-			Handler: router,
-			DisableGeneralOptionsHandler: true,
-		}
+		tlsConf.Certificates = []tls.Certificate{cert}
+	}
+	
+	server := http.Server{
+		TLSConfig: tlsConf,
+		Addr: serverHostname + ":" + serverPortHTTPS,
+		ReadTimeout: 0,
+		ReadHeaderTimeout: 10 * time.Second,
+		WriteTimeout: 0,
+		IdleTimeout: 30 * time.Second,
+		Handler: router,
+		DisableGeneralOptionsHandler: true,
+	}
 
 
-		// start https server
-		err := server.ListenAndServeTLS("", "")
+	// start https server
+	errServ := server.ListenAndServeTLS("", "")
 
 
-		if err != nil {
-			utils.Fatal("Listening to HTTPS", err)
-		}
+	if errServ != nil {
+		utils.Fatal("Listening to HTTPS", errServ)
+	}
 }
 }
 
 
 func tokenMiddleware(next http.Handler) http.Handler {
 func tokenMiddleware(next http.Handler) http.Handler {
@@ -127,13 +142,13 @@ func StartServer() {
 	serverPortHTTP = config.HTTPPort
 	serverPortHTTP = config.HTTPPort
 	serverPortHTTPS = config.HTTPSPort
 	serverPortHTTPS = config.HTTPSPort
 
 
-	var tlsCert = config.TLSCert
-	var tlsKey= config.TLSKey
-
 	configJson, _ := json.MarshalIndent(config, "", "  ")
 	configJson, _ := json.MarshalIndent(config, "", "  ")
 	utils.Debug("Configuration" + (string)(configJson))
 	utils.Debug("Configuration" + (string)(configJson))
 
 
-	if((tlsCert == "" || tlsKey == "") && config.GenerateMissingTLSCert) {
+	var tlsCert = config.TLSCert
+	var tlsKey= config.TLSKey
+
+	if((tlsCert == "" || tlsKey == "") && config.HTTPSCertificateMode == utils.HTTPSCertModeList["SELFSIGNED"]) {
 		utils.Log("Generating new TLS certificate")
 		utils.Log("Generating new TLS certificate")
 		pub, priv := utils.GenerateRSAWebCertificates()
 		pub, priv := utils.GenerateRSAWebCertificates()
 		
 		
@@ -184,7 +199,6 @@ func StartServer() {
 	srapi.HandleFunc("/api/servapps/{container}/secure", docker.SecureContainerRoute)
 	srapi.HandleFunc("/api/servapps/{container}/secure", docker.SecureContainerRoute)
 	srapi.HandleFunc("/api/servapps", docker.ContainersRoute)
 	srapi.HandleFunc("/api/servapps", docker.ContainersRoute)
 
 
-	// srapi.Use(utils.AcceptHeader("*/*"))
 	srapi.Use(tokenMiddleware)
 	srapi.Use(tokenMiddleware)
 	srapi.Use(utils.CORSHeader(utils.GetMainConfig().HTTPConfig.Hostname))
 	srapi.Use(utils.CORSHeader(utils.GetMainConfig().HTTPConfig.Hostname))
 	srapi.Use(utils.MiddlewareTimeout(20 * time.Second))
 	srapi.Use(utils.MiddlewareTimeout(20 * time.Second))
@@ -209,11 +223,12 @@ func StartServer() {
 
 
 	router = proxy.BuildFromConfig(router, config.ProxyConfig)
 	router = proxy.BuildFromConfig(router, config.ProxyConfig)
 
 
-	if tlsCert != "" && tlsKey != "" {
+	if ((config.HTTPSCertificateMode == utils.HTTPSCertModeList["SELFSIGNED"] || config.HTTPSCertificateMode == utils.HTTPSCertModeList["PROVIDED"]) &&
+			 tlsCert != "" && tlsKey != "") || (config.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
 		utils.Log("TLS certificate exist, starting HTTPS servers and redirecting HTTP to HTTPS")
 		utils.Log("TLS certificate exist, starting HTTPS servers and redirecting HTTP to HTTPS")
 		startHTTPSServer(router, tlsCert, tlsKey)
 		startHTTPSServer(router, tlsCert, tlsKey)
 	} else {
 	} else {
-		utils.Log("TLS certificate does not exist, starting HTTP server only")
+		utils.Log("TLS certificates do not exists or are disabled, starting HTTP server only")
 		startHTTPServer(router)
 		startHTTPServer(router)
 	}
 	}
 }
 }

+ 1 - 1
src/index.go

@@ -18,7 +18,7 @@ func main() {
 
 
 		docker.DockerListenEvents()
 		docker.DockerListenEvents()
 
 
-		// docker.BootstrapAllContainersFromTags()
+		docker.BootstrapAllContainersFromTags()
 		
 		
 		StartServer()
 		StartServer()
 }
 }

+ 11 - 1
src/utils/types.go

@@ -34,6 +34,15 @@ var ProxyModeList = map[string]string{
 	"PROXY": "PROXY",
 	"PROXY": "PROXY",
 	"SPA": "SPA",
 	"SPA": "SPA",
 	"STATIC": "STATIC",
 	"STATIC": "STATIC",
+	"SERVAPP": "SERVAPP",
+	"REDIRECT": "REDIRECT",
+}
+
+var HTTPSCertModeList = map[string]string{
+	"DISABLED": "DISABLED",
+	"PROVIDED": "PROVIDED",
+	"SELFSIGNED": "SELFSIGNED",
+	"LETSENCRYPT": "LETSENCRYPT",
 }
 }
 
 
 type FileStats struct {
 type FileStats struct {
@@ -72,12 +81,13 @@ type HTTPConfig struct {
 	TLSKey string
 	TLSKey string
 	AuthPrivateKey string
 	AuthPrivateKey string
 	AuthPublicKey string
 	AuthPublicKey string
-	GenerateMissingTLSCert bool
 	GenerateMissingAuthCert bool
 	GenerateMissingAuthCert bool
+	HTTPSCertificateMode string
 	HTTPPort string
 	HTTPPort string
 	HTTPSPort string
 	HTTPSPort string
 	ProxyConfig ProxyConfig
 	ProxyConfig ProxyConfig
 	Hostname string
 	Hostname string
+	SSLEmail string
 } 
 } 
 
 
 type ProxyConfig struct {
 type ProxyConfig struct {

+ 15 - 2
src/utils/utils.go

@@ -17,7 +17,7 @@ var IsHTTPS = false
 var DefaultConfig = Config{
 var DefaultConfig = Config{
 	LoggingLevel: "INFO",
 	LoggingLevel: "INFO",
 	HTTPConfig: HTTPConfig{
 	HTTPConfig: HTTPConfig{
-		GenerateMissingTLSCert: true,
+		HTTPSCertificateMode: "DISABLED",
 		GenerateMissingAuthCert: true,
 		GenerateMissingAuthCert: true,
 		HTTPPort: "80",
 		HTTPPort: "80",
 		HTTPSPort: "443",
 		HTTPSPort: "443",
@@ -95,7 +95,7 @@ func LoadBaseMainConfig(config Config){
 		MainConfig.HTTPConfig.Hostname = os.Getenv("COSMOS_HOSTNAME")
 		MainConfig.HTTPConfig.Hostname = os.Getenv("COSMOS_HOSTNAME")
 	}
 	}
 	if os.Getenv("COSMOS_GENERATE_MISSING_TLS_CERT") != "" {
 	if os.Getenv("COSMOS_GENERATE_MISSING_TLS_CERT") != "" {
-		MainConfig.HTTPConfig.GenerateMissingTLSCert = os.Getenv("COSMOS_GENERATE_MISSING_TLS_CERT") == "true"
+		MainConfig.HTTPConfig.HTTPSCertificateMode = os.Getenv("COSMOS_HTTPSCertificateMode")
 	}
 	}
 	if os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") != "" {
 	if os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") != "" {
 		MainConfig.HTTPConfig.GenerateMissingAuthCert = os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") == "true"
 		MainConfig.HTTPConfig.GenerateMissingAuthCert = os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") == "true"
@@ -266,4 +266,17 @@ func AdminOrItselfOnly(w http.ResponseWriter, req *http.Request, nickname string
 	}
 	}
 
 
 	return nil
 	return nil
+}
+
+func GetAllHostnames() []string {
+	hostnames := []string{
+		GetMainConfig().HTTPConfig.Hostname,
+	}
+	proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
+	for _, proxy := range proxies {
+		if proxy.UseHost {
+			hostnames = append(hostnames, proxy.Host)
+		}
+	}
+	return hostnames
 }
 }