Skip to content

Commit 6e4ca1c

Browse files
committed
Initial commit
0 parents  commit 6e4ca1c

7 files changed

Lines changed: 215 additions & 0 deletions

File tree

config.sample.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"listen_address": "0.0.0.0:443",
3+
"backends": {
4+
"web.domain.com": {
5+
"destination": "10.42.0.10:8080",
6+
"certificate": "/etc/letsencrypt/live/web.domain.com/fullchain.pem",
7+
"key": "/etc/letsencrypt/live/web.domain.com/privkey.pem"
8+
},
9+
"api.domain.com": {
10+
"destination": "10.42.0.20:8080",
11+
"certificate": "/etc/letsencrypt/live/api.domain.com/fullchain.pem",
12+
"key": "/etc/letsencrypt/live/api.domain.com/privkey.pem"
13+
}
14+
}
15+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module macleod
2+
3+
go 1.18

macleod.service

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Unit]
2+
Description=MacLeod
3+
After=network.target
4+
StartLimitIntervalSec=0
5+
6+
[Service]
7+
LimitAS=infinity
8+
LimitRSS=infinity
9+
LimitCORE=infinity
10+
LimitNOFILE=infinity
11+
WorkingDirectory=/apps/macleod
12+
Type=simple
13+
Restart=always
14+
RestartSec=1
15+
User=root
16+
ExecStart=/apps/macleod/macleod
17+
18+
[Install]
19+
WantedBy=multi-user.target

main.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package main
2+
3+
import (
4+
. "macleod/models"
5+
)
6+
7+
func init() {
8+
Config.Load()
9+
}
10+
11+
func main() {
12+
server := Server{}
13+
server.Serve()
14+
}

models/config.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package models
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"io/ioutil"
7+
)
8+
9+
type config struct {
10+
ListenAddress string `json:"listen_address"`
11+
Backends map[string]struct {
12+
Destination string `json:"destination"`
13+
Certificate string `json:"certificate"`
14+
Key string `json:"key"`
15+
}
16+
}
17+
18+
var Config config
19+
20+
func (c *config) Load() {
21+
data, _ := ioutil.ReadFile("config.json")
22+
_ = json.Unmarshal(data, c)
23+
}
24+
25+
func (c *config) GetCertificatesForDomain(domain string) (string, string, error) {
26+
if value, ok := Config.Backends[domain]; ok {
27+
return value.Certificate, value.Key, nil
28+
}
29+
return "", "", errors.New("config not found: " + domain)
30+
}
31+
func (c *config) GetBackendForDomain(domain string) (string, error) {
32+
if value, ok := Config.Backends[domain]; ok {
33+
return value.Destination, nil
34+
}
35+
return "", errors.New("config not found: " + domain)
36+
}

models/server.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package models
2+
3+
import (
4+
"crypto/tls"
5+
"io"
6+
"log"
7+
"net"
8+
)
9+
10+
type Server struct {
11+
Listener net.Listener
12+
}
13+
14+
func (s *Server) New() {
15+
tlsConfig := &tls.Config{
16+
GetConfigForClient: getConfigForClient,
17+
}
18+
var err error
19+
s.Listener, err = tls.Listen("tcp", Config.ListenAddress, tlsConfig)
20+
if err != nil {
21+
log.Fatalf("error in tls.Listen: %s", err)
22+
}
23+
}
24+
25+
func (s *Server) Serve() {
26+
for {
27+
clientConn, err := s.Listener.Accept()
28+
if err != nil {
29+
log.Printf("error in listener.Accept: %s", err)
30+
break
31+
}
32+
33+
go s.Handle(clientConn)
34+
}
35+
}
36+
37+
func getConfigForClient(chi *tls.ClientHelloInfo) (*tls.Config, error) {
38+
domain := chi.ServerName
39+
certificate, key, err := Config.GetCertificatesForDomain(domain)
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
cert, err := tls.LoadX509KeyPair(certificate, key)
45+
if err != nil {
46+
log.Fatalf("error in tls.LoadX509KeyPair: %s", err)
47+
}
48+
return &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}, nil
49+
}
50+
51+
func (s *Server) Handle(clientConn net.Conn) {
52+
tlsConn, ok := clientConn.(*tls.Conn)
53+
if ok {
54+
err := tlsConn.Handshake()
55+
if err != nil {
56+
log.Printf("error in tls.Handshake: %s", err)
57+
_ = clientConn.Close()
58+
return
59+
}
60+
domain := tlsConn.ConnectionState().ServerName
61+
backend, err := Config.GetBackendForDomain(domain)
62+
if err != nil {
63+
log.Println(err)
64+
return
65+
}
66+
backendConn, err := net.Dial("tcp", backend)
67+
if err != nil {
68+
log.Printf("error in net.Dial: %s", err)
69+
_ = clientConn.Close()
70+
return
71+
}
72+
73+
go s.CopyIO(clientConn, backendConn)
74+
go s.CopyIO(backendConn, clientConn)
75+
}
76+
}
77+
78+
func (s *Server) CopyIO(from, to io.ReadWriteCloser) {
79+
defer func() {
80+
if r := recover(); r != nil {
81+
log.Printf("recovered while tunneling")
82+
}
83+
}()
84+
85+
_, _ = io.Copy(from, to)
86+
_ = to.Close()
87+
_ = from.Close()
88+
}

readme.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# MacLeod
2+
3+
## SSL terminator
4+
5+
#### For when **there can be only one** entry point
6+
7+
## Features
8+
9+
- Terminates SSL connections and redirects traffic internally
10+
- No external dependencies
11+
12+
## Installation
13+
14+
```sh
15+
go build .
16+
```
17+
18+
## Configuration
19+
20+
Copy **config.sample.json** to **config.json**,
21+
Create a node under backends for each domain that will be used
22+
23+
```json
24+
{
25+
"web.domain.com": {
26+
"destination": "10.42.0.10:8080",
27+
"certificate": "/etc/letsencrypt/live/web.domain.com/fullchain.pem",
28+
"key": "/etc/letsencrypt/live/web.domain.com/privkey.pem"
29+
}
30+
}
31+
```
32+
33+
| Setting | Description |
34+
| ------ |----------------------------------------------------|
35+
| web.domain.com | Set this to your domain name |
36+
| destination | IP or hostname that should receive the traffic |
37+
| certificate | Location of chain file |
38+
| key | Location of private key file |
39+
40+
You can also use **macleod.service** as reference to install as daemon

0 commit comments

Comments
 (0)