v0.0.8 Automatic HTTPS certificate provisioning
This commit is contained in:
parent
eeec876234
commit
1fea4ef573
18 changed files with 272 additions and 151 deletions
4
.github/ISSUE_TEMPLATE/feature request.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/feature request.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: Feature Request
|
||||
about: Request a new feature
|
||||
title: "[FEAT]: "
|
||||
labels: [feature, review]
|
60
.github/ISSUE_TEMPLATE/issue report.yml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/issue report.yml
vendored
Normal file
|
@ -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
|
|
@ -1,5 +1,5 @@
|
|||
// 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 DiscordOutlinedWhite from '../assets/images/icons/discord_white.svg'
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
@ -45,6 +45,15 @@ const support = {
|
|||
icon: QuestionOutlined,
|
||||
external: 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
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@ import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
|||
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||
import RestartModal from './restart';
|
||||
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
|
||||
import { CosmosInputText, CosmosSelect } from './formShortcuts';
|
||||
|
||||
|
||||
const ConfigManagement = () => {
|
||||
|
@ -61,6 +62,8 @@ const ConfigManagement = () => {
|
|||
GenerateMissingAuthCert: config.HTTPConfig.GenerateMissingAuthCert,
|
||||
HTTPPort: config.HTTPConfig.HTTPPort,
|
||||
HTTPSPort: config.HTTPConfig.HTTPSPort,
|
||||
SSLEmail: config.HTTPConfig.SSLEmail,
|
||||
HTTPSCertificateMode: config.HTTPConfig.HTTPSCertificateMode,
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
Hostname: Yup.string().max(255).required('Hostname is required'),
|
||||
|
@ -76,10 +79,11 @@ const ConfigManagement = () => {
|
|||
HTTPConfig: {
|
||||
...config.HTTPConfig,
|
||||
Hostname: values.Hostname,
|
||||
GenerateMissingTLSCert: values.GenerateMissingTLSCert,
|
||||
GenerateMissingAuthCert: values.GenerateMissingAuthCert,
|
||||
HTTPPort: values.HTTPPort,
|
||||
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">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
|
@ -119,17 +123,17 @@ const ConfigManagement = () => {
|
|||
<OutlinedInput
|
||||
id="MongoDB-login"
|
||||
type="password"
|
||||
value={values.MongoDB}
|
||||
value={formik.values.MongoDB}
|
||||
name="MongoDB"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
onChange={formik.handleChange}
|
||||
placeholder="MongoDB"
|
||||
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">
|
||||
{errors.MongoDB}
|
||||
{formik.errors.MongoDB}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -144,14 +148,14 @@ const ConfigManagement = () => {
|
|||
name="LoggingLevel"
|
||||
id="LoggingLevel"
|
||||
select
|
||||
value={values.LoggingLevel}
|
||||
onChange={handleChange}
|
||||
value={formik.values.LoggingLevel}
|
||||
onChange={formik.handleChange}
|
||||
error={
|
||||
touched.LoggingLevel &&
|
||||
Boolean(errors.LoggingLevel)
|
||||
formik.touched.LoggingLevel &&
|
||||
Boolean(formik.errors.LoggingLevel)
|
||||
}
|
||||
helperText={
|
||||
touched.LoggingLevel && errors.LoggingLevel
|
||||
formik.touched.LoggingLevel && formik.errors.LoggingLevel
|
||||
}
|
||||
>
|
||||
<MenuItem key={"DEBUG"} value={"DEBUG"}>
|
||||
|
@ -182,17 +186,17 @@ const ConfigManagement = () => {
|
|||
<OutlinedInput
|
||||
id="Hostname-login"
|
||||
type="text"
|
||||
value={values.Hostname}
|
||||
value={formik.values.Hostname}
|
||||
name="Hostname"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
onChange={formik.handleChange}
|
||||
placeholder="Hostname"
|
||||
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">
|
||||
{errors.Hostname}
|
||||
{formik.errors.Hostname}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -204,17 +208,17 @@ const ConfigManagement = () => {
|
|||
<OutlinedInput
|
||||
id="HTTPPort-login"
|
||||
type="text"
|
||||
value={values.HTTPPort}
|
||||
value={formik.values.HTTPPort}
|
||||
name="HTTPPort"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
onChange={formik.handleChange}
|
||||
placeholder="HTTPPort"
|
||||
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">
|
||||
{errors.HTTPPort}
|
||||
{formik.errors.HTTPPort}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -226,17 +230,17 @@ const ConfigManagement = () => {
|
|||
<OutlinedInput
|
||||
id="HTTPSPort-login"
|
||||
type="text"
|
||||
value={values.HTTPSPort}
|
||||
value={formik.values.HTTPSPort}
|
||||
name="HTTPSPort"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
onChange={formik.handleChange}
|
||||
placeholder="HTTPSPort"
|
||||
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">
|
||||
{errors.HTTPSPort}
|
||||
{formik.errors.HTTPSPort}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -249,17 +253,28 @@ const ConfigManagement = () => {
|
|||
<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>
|
||||
</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}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
|
@ -297,16 +312,16 @@ const ConfigManagement = () => {
|
|||
<br /><br />
|
||||
|
||||
<MainCard>
|
||||
{errors.submit && (
|
||||
{formik.errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||
<FormHelperText error>{formik.errors.submit}</FormHelperText>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={isSubmitting}
|
||||
disabled={formik.isSubmitting}
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// material-ui
|
||||
import { Typography } from '@mui/material';
|
||||
import { Alert, Typography } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
|
||||
// project import
|
||||
|
@ -11,9 +11,8 @@ const ServeApps = () => {
|
|||
const {serveApps, setServeApps} = useState([]);
|
||||
|
||||
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>
|
||||
|
||||
}
|
||||
|
||||
export default ServeApps;
|
||||
|
|
|
@ -7,6 +7,7 @@ import UserManagement from '../pages/config/users/usermanagement';
|
|||
import ConfigManagement from '../pages/config/users/configman';
|
||||
import ProxyManagement from '../pages/config/users/proxyman';
|
||||
import ServeApps from '../pages/servapps/servapps';
|
||||
import { Navigate } from 'react-router';
|
||||
|
||||
// render - dashboard
|
||||
const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard')));
|
||||
|
@ -26,6 +27,11 @@ const MainRoutes = {
|
|||
path: '/',
|
||||
element: <MainLayout />,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
// redirect to /ui
|
||||
element: <Navigate to="/ui" />
|
||||
},
|
||||
{
|
||||
path: '/ui/',
|
||||
element: <DashboardDefault />
|
||||
|
|
BIN
diag_SN.png
BIN
diag_SN.png
Binary file not shown.
Before Width: | Height: | Size: 700 KiB After Width: | Height: | Size: 700 KiB |
BIN
diag_SN2.png
Normal file
BIN
diag_SN2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 643 KiB |
|
@ -18,7 +18,7 @@ sh build arm64.sh
|
|||
docker build \
|
||||
-t azukaar/cosmos-server:$VERSION-arm64 \
|
||||
-t azukaar/cosmos-server:latest-arm64 \
|
||||
-f Dockerfile.arm64 \
|
||||
-f dockerfile.arm64 \
|
||||
--platform linux/arm64 \
|
||||
.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
42
readme.md
42
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.
|
||||
|
||||
* **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!
|
||||
|
||||
|
@ -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:
|
||||
|
||||

|
||||
|
||||
Another example:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
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 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
|
||||
```
|
||||
|
|
BIN
schema.png
BIN
schema.png
Binary file not shown.
Before Width: | Height: | Size: 714 KiB After Width: | Height: | Size: 658 KiB |
|
@ -47,7 +47,13 @@ func connect() error {
|
|||
DockerClient = client
|
||||
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 os.Getenv("HOSTNAME") != "" {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
func DockerListenEvents() error {
|
||||
errD := connect()
|
||||
if errD != nil {
|
||||
utils.Error("Docker did not connect. Not listening", errD)
|
||||
return errD
|
||||
}
|
||||
|
||||
|
@ -20,7 +21,10 @@ func DockerListenEvents() error {
|
|||
for {
|
||||
select {
|
||||
case err := <-errs:
|
||||
utils.Error("Docker Events", err)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
utils.Error("Docker Event Error", err)
|
||||
case msg := <-msgs:
|
||||
utils.Debug("Docker Event: " + msg.Type + " " + msg.Action + " " + msg.Actor.ID)
|
||||
if msg.Type == "container" && msg.Action == "start" {
|
||||
|
|
|
@ -8,16 +8,17 @@ import (
|
|||
"github.com/azukaar/cosmos-server/src/proxy"
|
||||
"github.com/azukaar/cosmos-server/src/docker"
|
||||
"github.com/gorilla/mux"
|
||||
"strings"
|
||||
"strconv"
|
||||
"time"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/httprate"
|
||||
"crypto/tls"
|
||||
spa "github.com/roberthodgen/spa-server"
|
||||
// "github.com/foomo/simplecert"
|
||||
"github.com/foomo/simplecert"
|
||||
"github.com/foomo/tlsconfig"
|
||||
)
|
||||
|
||||
var serverPortHTTP = ""
|
||||
|
@ -34,73 +35,87 @@ func startHTTPServer(router *mux.Router) {
|
|||
}
|
||||
|
||||
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))
|
||||
if errCert != nil {
|
||||
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 {
|
||||
|
@ -127,13 +142,13 @@ func StartServer() {
|
|||
serverPortHTTP = config.HTTPPort
|
||||
serverPortHTTPS = config.HTTPSPort
|
||||
|
||||
var tlsCert = config.TLSCert
|
||||
var tlsKey= config.TLSKey
|
||||
|
||||
configJson, _ := json.MarshalIndent(config, "", " ")
|
||||
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")
|
||||
pub, priv := utils.GenerateRSAWebCertificates()
|
||||
|
||||
|
@ -184,7 +199,6 @@ func StartServer() {
|
|||
srapi.HandleFunc("/api/servapps/{container}/secure", docker.SecureContainerRoute)
|
||||
srapi.HandleFunc("/api/servapps", docker.ContainersRoute)
|
||||
|
||||
// srapi.Use(utils.AcceptHeader("*/*"))
|
||||
srapi.Use(tokenMiddleware)
|
||||
srapi.Use(utils.CORSHeader(utils.GetMainConfig().HTTPConfig.Hostname))
|
||||
srapi.Use(utils.MiddlewareTimeout(20 * time.Second))
|
||||
|
@ -209,11 +223,12 @@ func StartServer() {
|
|||
|
||||
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")
|
||||
startHTTPSServer(router, tlsCert, tlsKey)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func main() {
|
|||
|
||||
docker.DockerListenEvents()
|
||||
|
||||
// docker.BootstrapAllContainersFromTags()
|
||||
docker.BootstrapAllContainersFromTags()
|
||||
|
||||
StartServer()
|
||||
}
|
|
@ -34,6 +34,15 @@ var ProxyModeList = map[string]string{
|
|||
"PROXY": "PROXY",
|
||||
"SPA": "SPA",
|
||||
"STATIC": "STATIC",
|
||||
"SERVAPP": "SERVAPP",
|
||||
"REDIRECT": "REDIRECT",
|
||||
}
|
||||
|
||||
var HTTPSCertModeList = map[string]string{
|
||||
"DISABLED": "DISABLED",
|
||||
"PROVIDED": "PROVIDED",
|
||||
"SELFSIGNED": "SELFSIGNED",
|
||||
"LETSENCRYPT": "LETSENCRYPT",
|
||||
}
|
||||
|
||||
type FileStats struct {
|
||||
|
@ -72,12 +81,13 @@ type HTTPConfig struct {
|
|||
TLSKey string
|
||||
AuthPrivateKey string
|
||||
AuthPublicKey string
|
||||
GenerateMissingTLSCert bool
|
||||
GenerateMissingAuthCert bool
|
||||
HTTPSCertificateMode string
|
||||
HTTPPort string
|
||||
HTTPSPort string
|
||||
ProxyConfig ProxyConfig
|
||||
Hostname string
|
||||
SSLEmail string
|
||||
}
|
||||
|
||||
type ProxyConfig struct {
|
||||
|
|
|
@ -17,7 +17,7 @@ var IsHTTPS = false
|
|||
var DefaultConfig = Config{
|
||||
LoggingLevel: "INFO",
|
||||
HTTPConfig: HTTPConfig{
|
||||
GenerateMissingTLSCert: true,
|
||||
HTTPSCertificateMode: "DISABLED",
|
||||
GenerateMissingAuthCert: true,
|
||||
HTTPPort: "80",
|
||||
HTTPSPort: "443",
|
||||
|
@ -95,7 +95,7 @@ func LoadBaseMainConfig(config Config){
|
|||
MainConfig.HTTPConfig.Hostname = os.Getenv("COSMOS_HOSTNAME")
|
||||
}
|
||||
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") != "" {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
Loading…
Add table
Reference in a new issue