Skip to content

Commit 10976b5

Browse files
henrybarretogustavosbarreto
authored andcommitted
feat(gateway): improve logging and code readability
1 parent cb47f92 commit 10976b5

File tree

9 files changed

+471
-66
lines changed

9 files changed

+471
-66
lines changed

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ services:
9393
image: shellhubio/gateway:${SHELLHUB_VERSION}
9494
restart: unless-stopped
9595
environment:
96+
- SHELLHUB_LOG_LEVEL=${SHELLHUB_LOG_LEVEL}
97+
- SHELLHUB_LOG_FORMAT=${SHELLHUB_LOG_FORMAT}
9698
- SHELLHUB_DOMAIN=${SHELLHUB_DOMAIN}
9799
- SHELLHUB_TUNNELS=${SHELLHUB_TUNNELS}
98100
- SHELLHUB_TUNNELS_DOMAIN=${SHELLHUB_TUNNELS_DOMAIN}

gateway/certbot.go

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
"log"
65
"net"
76
"net/http"
87
"os"
@@ -11,6 +10,8 @@ import (
1110
"time"
1211

1312
"github.com/pkg/errors"
13+
log "github.com/sirupsen/logrus"
14+
"github.com/spf13/afero"
1415
)
1516

1617
// DNSProvider represents a DNS provider to generate certificates.
@@ -19,45 +20,67 @@ type DNSProvider string
1920
// DigitalOceanDNSProvider represents the Digital Ocean DNS provider.
2021
const DigitalOceanDNSProvider = "digitalocean"
2122

22-
type tunnels struct {
23-
domain string
24-
token string
23+
type Tunnels struct {
24+
// Domain is the default domain used to generate certificate for Tunnels.
25+
Domain string
26+
// Provider is the DNS provider used to generate wildcard certificates.
27+
Provider DNSProvider
28+
// Token is a DNS token used to generate wildcard certificates.
29+
Token string
30+
}
31+
32+
type Config struct {
33+
// RootDir is the root directory for CertBot configurations.
34+
RootDir string
35+
// Domain is the default domain used to generate certificate for ShellHub.
36+
Domain string
37+
// Staging defines if the CertBot will use the staging server to generate certificates.
38+
Staging bool
39+
// RenewedCallback is a callback called after certificate renew.
40+
RenewedCallback func()
41+
42+
Tunnels *Tunnels
2543
}
2644

2745
// CertBot handles the generation and renewal of SSL certificates.
2846
type CertBot struct {
29-
rootDir string
30-
domain string
31-
staging bool
32-
renewedCallback func()
33-
tunnels *tunnels
47+
Config *Config
48+
49+
fs afero.Fs
50+
}
51+
52+
func newCertBot(config *Config) *CertBot {
53+
return &CertBot{
54+
Config: config,
55+
fs: afero.NewOsFs(),
56+
}
3457
}
3558

3659
// ensureCertificates checks if the SSL certificate exists and generates it if not.
3760
func (cb *CertBot) ensureCertificates() {
38-
certPath := fmt.Sprintf("%s/live/%s/fullchain.pem", cb.rootDir, cb.domain)
39-
if _, err := os.Stat(certPath); os.IsNotExist(err) {
61+
certPath := fmt.Sprintf("%s/live/%s/fullchain.pem", cb.Config.RootDir, cb.Config.Domain)
62+
if _, err := cb.fs.Stat(certPath); os.IsNotExist(err) {
4063
cb.generateCertificate()
4164
}
4265

43-
if cb.tunnels != nil {
66+
if cb.Config.Tunnels != nil {
4467
// NOTE: We are recreating the INI file every time to ensure it has the latest token from the environment.
45-
cb.generateProviderCredentialsFile(DigitalOceanDNSProvider)
68+
cb.generateProviderCredentialsFile()
4669

47-
certPath := fmt.Sprintf("%s/live/*.%s/fullchain.pem", cb.rootDir, cb.tunnels.domain)
48-
if _, err := os.Stat(certPath); os.IsNotExist(err) {
49-
cb.generateCertificateFromDNS(DigitalOceanDNSProvider)
70+
certPath := fmt.Sprintf("%s/live/*.%s/fullchain.pem", cb.Config.RootDir, cb.Config.Tunnels.Domain)
71+
if _, err := cb.fs.Stat(certPath); os.IsNotExist(err) {
72+
cb.generateCertificateFromDNS()
5073
}
5174
}
5275
}
5376

5477
// generateCertificate generates a new SSL certificate using Certbot.
5578
func (cb *CertBot) generateCertificate() {
56-
fmt.Println("Generating SSL certificate")
79+
log.Info("generating SSL certificate")
5780

58-
challengeDir := fmt.Sprintf("%s/.well-known/acme-challenge", cb.rootDir)
59-
if err := os.MkdirAll(challengeDir, 0o755); err != nil {
60-
log.Fatal(err)
81+
challengeDir := fmt.Sprintf("%s/.well-known/acme-challenge", cb.Config.RootDir)
82+
if err := cb.fs.MkdirAll(challengeDir, 0o755); err != nil {
83+
log.WithError(err).Fatal("failed to create acme challenge on filesystem")
6184
}
6285

6386
acmeServer := cb.startACMEServer()
@@ -69,13 +92,15 @@ func (cb *CertBot) generateCertificate() {
6992
"--agree-tos",
7093
"--register-unsafely-without-email",
7194
"--webroot",
72-
"--webroot-path", cb.rootDir,
95+
"--webroot-path", cb.Config.RootDir,
7396
"--preferred-challenges", "http",
7497
"-n",
7598
"-d",
76-
cb.domain,
99+
cb.Config.Domain,
77100
)
78-
if cb.staging {
101+
if cb.Config.Staging {
102+
log.Info("running generate with staging")
103+
79104
cmd.Args = append(cmd.Args, "--staging")
80105
}
81106
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
@@ -85,12 +110,16 @@ func (cb *CertBot) generateCertificate() {
85110
}
86111

87112
cb.stopACMEServer(acmeServer)
113+
114+
log.Info("generate run")
88115
}
89116

90-
func (cb *CertBot) generateProviderCredentialsFile(provider DNSProvider) (*os.File, error) {
91-
token := fmt.Sprintf("dns_%s_token = %s", provider, cb.tunnels.token)
92-
file, err := os.Create(fmt.Sprintf("/etc/shellhub-gateway/%s.ini", string(provider)))
117+
func (cb *CertBot) generateProviderCredentialsFile() (afero.File, error) {
118+
token := fmt.Sprintf("dns_%s_token = %s", cb.Config.Tunnels.Provider, cb.Config.Tunnels.Token)
119+
file, err := cb.fs.Create(fmt.Sprintf("/etc/shellhub-gateway/%s.ini", string(cb.Config.Tunnels.Provider)))
93120
if err != nil {
121+
log.WithError(err).Error("failed to create shellhub-gateway file with dns provider token")
122+
94123
return nil, err
95124
}
96125

@@ -99,12 +128,12 @@ func (cb *CertBot) generateProviderCredentialsFile(provider DNSProvider) (*os.Fi
99128
return file, nil
100129
}
101130

102-
func (cb *CertBot) generateCertificateFromDNS(provider DNSProvider) {
103-
fmt.Println("Generating SSL certificate with DNS")
131+
func (cb *CertBot) generateCertificateFromDNS() {
132+
log.Info("generating SSL certificate with DNS")
104133

105-
file, err := cb.generateProviderCredentialsFile(provider)
134+
file, err := cb.generateProviderCredentialsFile()
106135
if err != nil {
107-
log.Fatalf("Failed to generate INI file: %v", err)
136+
log.WithError(err).Fatal("failed to generate INI file")
108137
}
109138

110139
cmd := exec.Command( //nolint:gosec
@@ -114,21 +143,26 @@ func (cb *CertBot) generateCertificateFromDNS(provider DNSProvider) {
114143
"--agree-tos",
115144
"--register-unsafely-without-email",
116145
"--cert-name",
117-
fmt.Sprintf("*.%s", cb.tunnels.domain),
118-
fmt.Sprintf("--dns-%s", provider),
119-
fmt.Sprintf("--dns-%s-credentials", provider),
146+
fmt.Sprintf("*.%s", cb.Config.Tunnels.Domain),
147+
fmt.Sprintf("--dns-%s", cb.Config.Tunnels.Provider),
148+
fmt.Sprintf("--dns-%s-credentials", cb.Config.Tunnels.Provider),
120149
file.Name(),
121150
"-d",
122-
fmt.Sprintf("*.%s", cb.tunnels.domain),
151+
fmt.Sprintf("*.%s", cb.Config.Tunnels.Domain),
123152
)
124-
if cb.staging {
153+
if cb.Config.Staging {
154+
log.Info("running generate with staging on dns")
155+
125156
cmd.Args = append(cmd.Args, "--staging")
126157
}
158+
127159
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
128160

129161
if err := cmd.Run(); err != nil {
130-
log.Fatal("Failed to generate SSL certificate")
162+
log.WithError(err).Fatal("failed to generate SSL certificate")
131163
}
164+
165+
log.Info("generate run on dns")
132166
}
133167

134168
// startACMEServer starts a local HTTP server for the ACME challenge.
@@ -139,7 +173,7 @@ func (cb *CertBot) startACMEServer() *http.Server {
139173
http.StripPrefix(
140174
"/.well-known/acme-challenge/",
141175
http.FileServer(
142-
http.Dir(filepath.Join(cb.rootDir, ".well-known/acme-challenge")),
176+
http.Dir(filepath.Join(cb.Config.RootDir, ".well-known/acme-challenge")),
143177
),
144178
),
145179
)
@@ -150,12 +184,12 @@ func (cb *CertBot) startACMEServer() *http.Server {
150184

151185
listener, err := net.Listen("tcp", ":80")
152186
if err != nil {
153-
log.Fatalf("Failed to start ACME server listener: %v", err)
187+
log.WithError(err).Fatal("failed to start ACME server listener")
154188
}
155189

156190
go func() {
157191
if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
158-
log.Fatalf("ACME server error: %v", err)
192+
log.WithError(err).Fatal("acme server error")
159193
}
160194
}()
161195

@@ -165,7 +199,7 @@ func (cb *CertBot) startACMEServer() *http.Server {
165199
// stopACMEServer stops the local ACME server.
166200
func (cb *CertBot) stopACMEServer(server *http.Server) {
167201
if err := server.Close(); err != nil {
168-
log.Fatalf("Could not stop ACME server: %v", err)
202+
log.WithError(err).Fatal("could not stop ACME server")
169203
}
170204
}
171205

@@ -175,32 +209,37 @@ func (cb *CertBot) executeRenewCertificates() error {
175209
"renew",
176210
)
177211

178-
if cb.staging {
212+
if cb.Config.Staging {
213+
log.Info("running renew with staging")
214+
179215
cmd.Args = append(cmd.Args, "--staging")
180216
}
181217

182218
if err := cmd.Run(); err != nil {
183-
log.Println("Failed to renew SSL certificate")
219+
log.WithError(err).Error("failed to renew SSL certificate")
184220

185221
return err
186222
}
187223

224+
log.Info("renew run")
225+
188226
return nil
189227
}
190228

191229
// renewCertificates periodically renews the SSL certificates.
192230
func (cb *CertBot) renewCertificates() {
193-
fmt.Println("Starting SSL certificate renewal process")
231+
log.Info("starting SSL certificate renewal process")
194232

195233
ticker := time.NewTicker(24 * time.Hour)
196234
defer ticker.Stop()
235+
197236
for range ticker.C {
198-
fmt.Println("Checking if SSL certificate needs to be renewed")
237+
log.Info("checking if SSL certificate needs to be renewed")
199238
if err := cb.executeRenewCertificates(); err != nil {
200-
log.Fatal("Failed to renew SSL certificate")
239+
log.Fatal("failed to renew SSL certificate")
201240
}
202241

203-
fmt.Println("SSL certificate successfully renewed")
204-
cb.renewedCallback()
242+
log.Info("ssl certificate successfully renewed")
243+
cb.Config.RenewedCallback()
205244
}
206245
}

gateway/certbot_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/spf13/afero"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestCertBot_generateProviderCredentialsFile(t *testing.T) {
11+
certbot := newCertBot(&Config{
12+
Tunnels: &Tunnels{
13+
Domain: "localhost",
14+
Provider: "digitalocean",
15+
Token: "test",
16+
},
17+
})
18+
certbot.fs = afero.NewMemMapFs()
19+
20+
certbot.generateProviderCredentialsFile()
21+
22+
buffer, err := afero.ReadFile(certbot.fs, "/etc/shellhub-gateway/digitalocean.ini")
23+
assert.NoError(t, err)
24+
25+
assert.Equal(t, "dns_digitalocean_token = test", string(buffer))
26+
}

gateway/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func loadGatewayConfig() (*GatewayConfig, error) {
4444
return &config, nil
4545
}
4646

47-
// ApplyDefaults sets default values for the GatewayConfig if not provided.
47+
// applyDefaults sets default values for the GatewayConfig if not provided.
4848
func (gc *GatewayConfig) applyDefaults() {
4949
if gc.WorkerProcesses == "auto" {
5050
gc.WorkerProcesses = fmt.Sprintf("%d", runtime.NumCPU())

0 commit comments

Comments
 (0)