Skip to content

Commit b1db418

Browse files
authored
enable TLS as default configuration (self-signed-certificate) (#136)
* enable TLS as default configuration (self-signed-certificate) redirect http requests to https
1 parent 4731477 commit b1db418

14 files changed

Lines changed: 547 additions & 110 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ pids
3333

3434
# Coverage directory used by tools like istanbul
3535
coverage
36-
*.lcov
36+
*.lcov
37+
38+
# vscode
39+
.vscode/launch.json

.golangci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ linters:
2121
- bidichk # Checks for dangerous unicode character sequences
2222
# - bodyclose # checks whether HTTP response body is closed successfully
2323
# - contextcheck # check the function whether use a non-inherited context
24-
- deadcode # Finds unused code
2524
- decorder # check declaration order and count of types, constants, variables and functions
2625
# - depguard # Go linter that checks if package imports are in a list of acceptable packages
2726
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
@@ -74,7 +73,6 @@ linters:
7473
- unconvert # Remove unnecessary type conversions
7574
- unparam # Reports unused function parameters
7675
- unused # Checks Go code for unused constants, variables, functions and types
77-
- varcheck # Finds unused global variables and constants
7876
# - wastedassign # wastedassign finds wasted assignment statements
7977
- whitespace # Tool for detection of leading and trailing whitespace
8078
- makezero # Finds slice declarations with non-zero initial length

cmd/defaultConfig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func resolveDefaultConfig(configPath string) error {
4343
return nil
4444
}
4545
configDirectoryPath := filepath.Dir(configPath)
46-
cfg := config.DefaultConfig(configDirectoryPath + "/www")
46+
cfg := config.DefaultConfig(configDirectoryPath)
4747
if err := os.WriteFile(configPath, []byte(cfg.String()), 0o600); err != nil {
4848
return fmt.Errorf("cannot write default config: %w", err)
4949
}

cmd/generateSelfSigned.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"crypto/ecdsa"
6+
"crypto/elliptic"
7+
"crypto/rand"
8+
"crypto/tls"
9+
"crypto/x509"
10+
"crypto/x509/pkix"
11+
"encoding/pem"
12+
"fmt"
13+
"math/big"
14+
"net"
15+
"os"
16+
"path"
17+
"strings"
18+
"time"
19+
20+
"github.com/apex/log"
21+
externalip "github.com/glendc/go-external-ip"
22+
)
23+
24+
func getExternalIP() (net.IP, error) {
25+
consensus := externalip.DefaultConsensus(&externalip.ConsensusConfig{
26+
Timeout: time.Second * 3,
27+
}, nil)
28+
return consensus.ExternalIP()
29+
}
30+
31+
func getExternalDomains(ip net.IP) ([]string, error) {
32+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
33+
defer cancel()
34+
dns, err := net.DefaultResolver.LookupAddr(ctx, ip.String())
35+
if err != nil {
36+
return nil, err
37+
}
38+
// remove trailing dot
39+
for i := range dns {
40+
dns[i] = strings.TrimSuffix(dns[i], ".")
41+
}
42+
return dns, nil
43+
}
44+
45+
func setupIPsFromInterface(template *x509.Certificate, iface net.Interface) {
46+
if iface.Flags&net.FlagLoopback != 0 {
47+
return
48+
}
49+
if iface.Flags&net.FlagUp == 0 {
50+
return
51+
}
52+
addrs, err := iface.Addrs()
53+
if err != nil {
54+
log.Warnf("cannot get network interface(%v) addresses: %v", iface.Name, err)
55+
return
56+
}
57+
for _, addr := range addrs {
58+
if ip, _, err3 := net.ParseCIDR(addr.String()); err3 == nil && !ip.IsUnspecified() {
59+
template.IPAddresses = append(template.IPAddresses, ip)
60+
} else {
61+
log.Warnf("cannot parse network interface(%v) address(%v): %v", iface.Name, addr.String(), err3)
62+
}
63+
}
64+
}
65+
66+
func setupIPsFromInterfaces(template *x509.Certificate) {
67+
ifaces, err := net.Interfaces()
68+
if err != nil {
69+
log.Warnf("cannot get network interfaces: %v", err)
70+
return
71+
}
72+
for _, iface := range ifaces {
73+
setupIPsFromInterface(template, iface)
74+
}
75+
}
76+
77+
func setupIPAndDomains(template *x509.Certificate) {
78+
template.DNSNames = []string{"localhost"}
79+
template.IPAddresses = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}
80+
if ip, err := getExternalIP(); err != nil {
81+
log.Warnf("cannot get external IP: %v", err)
82+
} else {
83+
template.IPAddresses = append(template.IPAddresses, ip)
84+
domains, err := getExternalDomains(ip)
85+
if err != nil {
86+
log.Warnf("cannot get external domains: %v", err)
87+
} else {
88+
template.DNSNames = append(template.DNSNames, domains...)
89+
}
90+
}
91+
setupIPsFromInterfaces(template)
92+
}
93+
94+
const cn = "self-signed-certificate"
95+
96+
func checkSelfSignedCertificate(certFile, keyFile string) bool {
97+
crt, err := tls.LoadX509KeyPair(certFile, keyFile)
98+
if err != nil {
99+
return false
100+
}
101+
cert, err := x509.ParseCertificate(crt.Certificate[0])
102+
if err != nil {
103+
return false
104+
}
105+
if cert.Subject.CommonName != cn {
106+
// it is not self signed certificate
107+
return true
108+
}
109+
now := time.Now()
110+
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
111+
return false
112+
}
113+
return true
114+
}
115+
116+
func generateSelfSigned(certFile, keyFile string) error {
117+
err := os.MkdirAll(path.Dir(certFile), 0o700)
118+
if err != nil {
119+
return fmt.Errorf("cannot create directory(%v) for certificate: %w", path.Dir(certFile), err)
120+
}
121+
err = os.MkdirAll(path.Dir(keyFile), 0o700)
122+
if err != nil {
123+
return fmt.Errorf("cannot create directory(%v) for key: %w", path.Dir(keyFile), err)
124+
}
125+
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
126+
if err != nil {
127+
return fmt.Errorf("cannot generate key: %w", err)
128+
}
129+
template := x509.Certificate{
130+
SerialNumber: big.NewInt(1),
131+
Subject: pkix.Name{
132+
Organization: []string{"plgd.dev"},
133+
CommonName: cn,
134+
},
135+
NotBefore: time.Now(),
136+
NotAfter: time.Now().Add(time.Hour * 24 * 365),
137+
138+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
139+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
140+
BasicConstraintsValid: true,
141+
IsCA: true,
142+
}
143+
setupIPAndDomains(&template)
144+
145+
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
146+
if err != nil {
147+
return fmt.Errorf("cannot create certificate: %w", err)
148+
}
149+
150+
derKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
151+
if err != nil {
152+
return fmt.Errorf("cannot marshal private key: %w", err)
153+
}
154+
155+
err = os.WriteFile(certFile, pem.EncodeToMemory(&pem.Block{
156+
Type: "CERTIFICATE",
157+
Bytes: derBytes,
158+
}), 0o600)
159+
if err != nil {
160+
return fmt.Errorf("cannot write certificate to file(%v): %w", certFile, err)
161+
}
162+
163+
err = os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{
164+
Type: "PRIVATE KEY",
165+
Bytes: derKeyBytes,
166+
}), 0o600)
167+
if err != nil {
168+
return fmt.Errorf("cannot write key to file(%v): %w", keyFile, err)
169+
}
170+
171+
return nil
172+
}

cmd/main.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,42 +37,57 @@ var (
3737
ReleaseURL = "unknown url"
3838
)
3939

40-
func main() {
40+
func loadConfig() config.Config {
4141
var opts struct {
4242
Version bool `short:"v" long:"version" description:"version"`
4343
ConfigPath string `long:"config" description:"yaml config file path"`
4444
}
4545
_, _ = flags.NewParser(&opts, flags.Default|flags.IgnoreUnknown).Parse()
4646
if opts.Version {
4747
fmt.Println(Version)
48-
return
48+
os.Exit(0)
4949
}
5050
if err := resolveDefaultConfig(opts.ConfigPath); err != nil {
5151
log.Errorf("cannot create default config: %v", err)
52-
return
52+
os.Exit(1)
5353
}
5454
// parse line arguments again because resolveDefaultConfig can set config path
5555
_, _ = flags.NewParser(&opts, flags.Default|flags.IgnoreUnknown).Parse()
5656
cfg, err := config.New(opts.ConfigPath)
5757
if err != nil {
5858
log.Errorf("cannot load config: %v", err)
59-
return
59+
os.Exit(1)
6060
}
6161
if _, err = os.Stat(cfg.APIs.HTTP.UI.Directory); cfg.APIs.HTTP.UI.Enabled && err != nil {
6262
if err = extractUI(cfg.APIs.HTTP.UI.Directory); err != nil {
6363
log.Errorf("cannot extract UI: %v", err)
64+
os.Exit(1)
65+
}
66+
}
67+
if cfg.APIs.HTTP.Enabled && cfg.APIs.HTTP.TLS.Enabled && !checkSelfSignedCertificate(cfg.APIs.HTTP.TLS.CertFile, cfg.APIs.HTTP.TLS.KeyFile) {
68+
if err = generateSelfSigned(cfg.APIs.HTTP.TLS.CertFile, cfg.APIs.HTTP.TLS.KeyFile); err != nil {
69+
log.Errorf("cannot generate self signed certificate for HTTP: %v", err)
70+
os.Exit(1)
71+
}
72+
}
73+
if cfg.APIs.GRPC.Enabled && cfg.APIs.GRPC.TLS.Enabled && !checkSelfSignedCertificate(cfg.APIs.GRPC.TLS.CertFile, cfg.APIs.GRPC.TLS.KeyFile) {
74+
if err = generateSelfSigned(cfg.APIs.GRPC.TLS.CertFile, cfg.APIs.GRPC.TLS.KeyFile); err != nil {
75+
log.Errorf("cannot generate self signed certificate for GRPC: %v", err)
76+
os.Exit(1)
6477
}
6578
}
79+
return cfg
80+
}
81+
82+
func main() {
83+
cfg := loadConfig()
6684
logger := log.NewLogger(cfg.Log)
6785
log.Set(logger)
6886
fileWatcher, err := fsnotify.NewWatcher(logger)
6987
if err != nil {
7088
log.Errorf("cannot create file fileWatcher: %v", err)
71-
return
89+
os.Exit(1)
7290
}
73-
defer func() {
74-
_ = fileWatcher.Close()
75-
}()
7691
log.Debugf("version: %v, buildDate: %v, buildRevision %v", Version, BuildDate, CommitHash)
7792
log.Debugf("config:\n%v", cfg.String())
7893
info := grpc.ServiceInformation{
@@ -86,11 +101,11 @@ func main() {
86101
s, err := service.New(context.Background(), cfg, &info, fileWatcher, logger)
87102
if err != nil {
88103
log.Errorf("cannot create service: %v", err)
89-
return
104+
os.Exit(1)
90105
}
91106
err = s.Serve()
92107
if err != nil {
93108
log.Errorf("cannot serve service: %v", err)
94-
return
109+
os.Exit(1)
95110
}
96111
}

config.yaml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ apis:
1616
idleTimeout: 30s
1717
tls:
1818
enabled: false
19-
caPool: /certs/ca.pem
20-
keyFile: /certs/key.pem
21-
certFile: /certs/crt.pem
19+
caPool: certs/ca.pem
20+
keyFile: certs/key.pem
21+
certFile: certs/crt.pem
2222
clientCertificateRequired: true
2323
cors:
2424
allowedOrigins:
@@ -59,9 +59,9 @@ apis:
5959
timeout: 0s
6060
tls:
6161
enabled: false
62-
caPool: /certs/ca.pem
63-
keyFile: /certs/key.pem
64-
certFile: /certs/crt.pem
62+
caPool: certs/ca.pem
63+
keyFile: certs/key.pem
64+
certFile: certs/crt.pem
6565
clientCertificateRequired: true
6666
clients:
6767
device:
@@ -77,9 +77,9 @@ clients:
7777
- justWorks
7878
manufacturerCertificate:
7979
tls:
80-
caPool: /certs/mfg_ca.pem
81-
keyFile: /certs/mfg_key.pem
82-
certFile: /certs/mfg_crt.pem
80+
caPool: certs/mfg_ca.pem
81+
keyFile: certs/mfg_key.pem
82+
certFile: certs/mfg_crt.pem
8383
tls:
8484
authentication: preSharedKey
8585
preSharedKey:

0 commit comments

Comments
 (0)