Skip to content

Commit 2be87d0

Browse files
authored
Added support to the agent for running as a continues service (#15)
* Added support to the agent for running as a continues service, updating ca, crt, blocklist ... and send SIGHUP to nebula to reload.
1 parent 3360e6d commit 2be87d0

6 files changed

Lines changed: 271 additions & 8 deletions

File tree

cmd/agent/enrollment.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,11 @@ func generateNebulaKeyPair() ([]byte, error) {
215215
var csr []byte
216216
var err error
217217

218-
exists, info := fileExists(AgentNebulaCsrPath)
218+
exists, info := fileExists(resolvePath(AgentNebulaCsrPath))
219219
if exists && info.IsDir() {
220220
return nil, fmt.Errorf("expected agent-nebula.csr to be a file")
221221
} else if exists {
222-
csr, err = ioutil.ReadFile(AgentNebulaCsrPath)
222+
csr, err = ioutil.ReadFile(resolvePath(AgentNebulaCsrPath))
223223
if err != nil {
224224
return nil, fmt.Errorf("error while reading csr: %s", err)
225225
}
@@ -230,13 +230,13 @@ func generateNebulaKeyPair() ([]byte, error) {
230230
}
231231
curve25519.ScalarBaseMult(&pubkey, &privkey)
232232

233-
err := ioutil.WriteFile(AgentNebulaKeyPath, cert.MarshalX25519PrivateKey(privkey[:]), 0600)
233+
err := ioutil.WriteFile(resolvePath(AgentNebulaKeyPath), cert.MarshalX25519PrivateKey(privkey[:]), 0600)
234234
if err != nil {
235235
return nil, fmt.Errorf("error while writing key: %s", err)
236236
}
237237

238238
csr = cert.MarshalX25519PublicKey(pubkey[:])
239-
err = ioutil.WriteFile(AgentNebulaCsrPath, csr, 0600)
239+
err = ioutil.WriteFile(resolvePath(AgentNebulaCsrPath), csr, 0600)
240240
if err != nil {
241241
return nil, fmt.Errorf("error while writing csr: %s", err)
242242
}

cmd/agent/main.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const AgentNebulaCaPath = "agent-nebula-ca.crt"
3737

3838
var (
3939
l *logrus.Logger
40+
logLevel string
4041
configPath string
4142
configDir string
4243
config *nebula.Config
@@ -58,13 +59,15 @@ func init() {
5859
cobra.OnInitialize(initConfig)
5960

6061
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Path to either a file or directory to load configuration from")
62+
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", logrus.InfoLevel.String(), "Log level (debug, info, warn, error, fatal, panic)")
6163

62-
rootCmd.AddCommand(configCmd, enrollCmd, exportCmd)
64+
rootCmd.AddCommand(configCmd, enrollCmd, exportCmd, serviceCmd)
6365
}
6466

6567
func initConfig() {
6668
l = logrus.New()
6769
l.Out = os.Stdout
70+
6871
config = nebula.NewConfig(l)
6972

7073
if configPath == "" {
@@ -78,6 +81,17 @@ func initConfig() {
7881
l.WithError(err).Errorln("failed to load config")
7982
os.Exit(1)
8083
}
84+
85+
if config.IsSet("log.level") && !rootCmd.Flag("log-level").Changed {
86+
logLevel = config.GetString("log.level", "info")
87+
}
88+
89+
level, err := logrus.ParseLevel(logLevel)
90+
if err != nil {
91+
fmt.Println(err.Error())
92+
os.Exit(1)
93+
}
94+
l.SetLevel(level)
8195
} else {
8296
l.Errorf("failed to detect config path")
8397
os.Exit(1)
@@ -186,8 +200,8 @@ func generateAgentKeyPair(cert, key string) error {
186200
NotAfter: time.Now().AddDate(5, 0, 0),
187201
SerialNumber: serial,
188202
Subject: pkix.Name{
189-
CommonName: "New Name",
190-
Organization: []string{"New Org."},
203+
CommonName: "New Name", // FIXME
204+
Organization: []string{"New Org."}, // FIXME
191205
},
192206
BasicConstraintsValid: true,
193207
}

cmd/agent/service.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"io/ioutil"
6+
"os"
7+
"os/signal"
8+
"path/filepath"
9+
"syscall"
10+
"time"
11+
12+
"github.com/mitchellh/go-ps"
13+
"github.com/spf13/cobra"
14+
"google.golang.org/protobuf/types/known/emptypb"
15+
"gopkg.in/yaml.v3"
16+
)
17+
18+
var serviceCmd = &cobra.Command{
19+
Use: "service",
20+
Short: "Service keeping nebula configuration up to date",
21+
Run: func(cmd *cobra.Command, args []string) {
22+
agent, err := NewClient(l, config)
23+
if err != nil {
24+
l.WithError(err).Error("failed to create client")
25+
os.Exit(1)
26+
}
27+
defer agent.Close()
28+
29+
templatePath := config.GetString("service.config_template", "nebula.yml.template")
30+
outputPath := config.GetString("service.config_output", "/etc/nebula/")
31+
32+
templatePath = filepath.Join(configDir, templatePath)
33+
if ok, _ := fileExists(templatePath); !ok {
34+
l.Errorf("file not found: %s", templatePath)
35+
os.Exit(1)
36+
}
37+
38+
interval := config.GetDuration("service.interval", 10*time.Minute)
39+
40+
ticker := time.NewTicker(interval)
41+
sighup := make(chan os.Signal, 1)
42+
signal.Notify(sighup, syscall.SIGHUP)
43+
44+
quit := make(chan struct{})
45+
go func() {
46+
run(agent, templatePath, outputPath)
47+
for {
48+
select {
49+
case <-sighup:
50+
l.Debug("received HUP signal, triggering update")
51+
run(agent, templatePath, outputPath)
52+
case <-ticker.C:
53+
l.Debug("received tick, triggering update")
54+
run(agent, templatePath, outputPath)
55+
case <-quit:
56+
ticker.Stop()
57+
return
58+
}
59+
}
60+
}()
61+
62+
serviceShutdownBlock()
63+
},
64+
}
65+
66+
func init() {
67+
}
68+
69+
func serviceShutdownBlock() {
70+
sigChan := make(chan os.Signal)
71+
signal.Notify(sigChan, syscall.SIGTERM)
72+
signal.Notify(sigChan, syscall.SIGINT)
73+
74+
select {
75+
case rawSig := <-sigChan:
76+
sig := rawSig.String()
77+
l.WithField("signal", sig).Info("Caught signal, shutting down")
78+
}
79+
}
80+
81+
func run(agent *agentClient, configTemplatePath, outputPath string) {
82+
l.Infoln("Generate config dir")
83+
84+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
85+
defer cancel()
86+
87+
status, err := agent.client.GetEnrollStatus(ctx, &emptypb.Empty{})
88+
if err != nil {
89+
l.WithError(err).Errorf("failed to get enrollment status")
90+
return
91+
}
92+
93+
if !status.IsEnrolled {
94+
l.Info("agent is not enrolled yet")
95+
return
96+
}
97+
98+
var nebulaConfig map[interface{}]interface{}
99+
bs, err := ioutil.ReadFile(configTemplatePath)
100+
if err != nil {
101+
l.WithError(err).Errorf("failed to read template file")
102+
return
103+
}
104+
if err := yaml.Unmarshal(bs, &nebulaConfig); err != nil {
105+
l.WithError(err).Errorf("error when reading contents of %s", configTemplatePath)
106+
return
107+
}
108+
109+
_, err = os.Stat(outputPath)
110+
if err != nil {
111+
if os.IsNotExist(err) {
112+
err = os.Mkdir(outputPath, 0700)
113+
if err != nil {
114+
l.WithError(err).Errorf("failed to create directory `%s`\n", outputPath)
115+
return
116+
}
117+
} else {
118+
l.WithError(err).Errorf("failed to create directory `%s`\n", outputPath)
119+
return
120+
}
121+
}
122+
123+
caBytes := make([]byte, 0)
124+
for i := range status.CertificateAuthorities {
125+
caBytes = append(caBytes, status.CertificateAuthorities[i].PublicKeyPEM...)
126+
caBytes = append(caBytes, '\n')
127+
}
128+
129+
caFilePath := filepath.Join(outputPath, "ca.crt")
130+
if err := ioutil.WriteFile(caFilePath, caBytes, 0600); err != nil {
131+
l.WithError(err).Errorf("failed to write ca.crt to `%s`\n", caFilePath)
132+
return
133+
}
134+
135+
if _, ok := nebulaConfig["pki"]; !ok {
136+
nebulaConfig["pki"] = make(map[string]interface{})
137+
}
138+
pki := nebulaConfig["pki"].(map[string]interface{})
139+
pki["ca"], err = filepath.Abs(caFilePath)
140+
if err != nil {
141+
l.WithError(err).Errorf("failed to get absolut path for `%s`\n", caFilePath)
142+
return
143+
}
144+
if ok, _ := fileExists(pki["ca"].(string)); !ok {
145+
l.WithError(err).Errorf("file not found `%s`\n", pki["ca"].(string))
146+
return
147+
}
148+
149+
crtFilePath := filepath.Join(outputPath, "nebula.crt")
150+
if err := ioutil.WriteFile(crtFilePath, []byte(status.SignedPEM), 0600); err != nil {
151+
l.WithError(err).Errorf("failed to write nebula.crt to `%s`\n", crtFilePath)
152+
return
153+
}
154+
pki["cert"], err = filepath.Abs(crtFilePath)
155+
if err != nil {
156+
l.WithError(err).Errorf("failed to get absolut path for `%s`\n", crtFilePath)
157+
return
158+
}
159+
if ok, _ := fileExists(pki["cert"].(string)); !ok {
160+
l.WithError(err).Errorf("file not found `%s`\n", pki["cert"].(string))
161+
return
162+
}
163+
164+
keyPath := resolvePath(AgentNebulaKeyPath)
165+
if ok, _ := fileExists(keyPath); !ok {
166+
l.Errorf("nebula key not exists: %s", keyPath)
167+
return
168+
}
169+
170+
keyFileSource, err := ioutil.ReadFile(keyPath)
171+
if err != nil {
172+
l.WithError(err).Errorf("error open file: %s", keyPath)
173+
return
174+
}
175+
176+
keyPathDst := filepath.Join(outputPath, "nebula.key")
177+
if ok, info := fileExists(keyPathDst); ok && info.Mode() != 0600 {
178+
l.Warnf("wrong permission on key %s, fixing...", keyPathDst)
179+
err = os.Chmod(keyPathDst, 0600)
180+
if err != nil {
181+
l.WithError(err).Errorf("failed to fix permission on %s", keyPathDst)
182+
}
183+
}
184+
185+
if err := ioutil.WriteFile(keyPathDst, keyFileSource, 0600); err != nil {
186+
l.WithError(err).Errorf("failed to write nebula.key to `%s`\n", keyPathDst)
187+
return
188+
}
189+
190+
pki["key"], err = filepath.Abs(keyPathDst)
191+
if err != nil {
192+
l.WithError(err).Errorf("failed to get absolut path for `%s`", keyPathDst)
193+
return
194+
}
195+
if ok, _ := fileExists(pki["key"].(string)); !ok {
196+
l.WithError(err).Errorf("file not found `%s`\n", pki["key"].(string))
197+
return
198+
}
199+
200+
blockedFingerprints := make([]string, 0)
201+
202+
for _, crl := range status.CertificateRevocationList {
203+
blockedFingerprints = append(blockedFingerprints, crl.Fingerprints...)
204+
}
205+
206+
pki["blocklist"] = blockedFingerprints
207+
208+
bs, err = yaml.Marshal(nebulaConfig)
209+
if err != nil {
210+
l.WithError(err).Error("failed to generate config")
211+
return
212+
}
213+
configFilePath := filepath.Join(outputPath, "config.yml")
214+
if err := ioutil.WriteFile(configFilePath, bs, 0600); err != nil {
215+
l.WithError(err).Errorf("failed to write config file to `%s`\n", configFilePath)
216+
return
217+
}
218+
219+
reloadNebula()
220+
}
221+
222+
func reloadNebula() {
223+
processes, err := ps.Processes()
224+
if err != nil {
225+
l.WithError(err).Error("failed to get os processes")
226+
return
227+
}
228+
229+
for i := range processes {
230+
if processes[i].Executable() == "nebula" {
231+
l.Infof("nebula pid is %d", processes[i].Pid())
232+
err = syscall.Kill(processes[i].Pid(), syscall.SIGHUP)
233+
if err != nil {
234+
l.WithError(err).Errorf("failure when trying to trigger nebula reload")
235+
}
236+
break
237+
}
238+
}
239+
}

examples/agent.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,11 @@
44
#ca: "server.crt"
55

66
# Address to the server <host:port>
7-
#server: "host:port"
7+
#server: "host:port"
8+
9+
#log:
10+
# level: debug
11+
12+
service:
13+
config_template: nebula.yml.template
14+
config_output: nebula-generated

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/gorilla/sessions v1.2.1
1111
github.com/hashicorp/vault v1.8.4
1212
github.com/lafriks/go-spaproxy v0.2.0
13+
github.com/mitchellh/go-ps v1.0.0
1314
github.com/sirupsen/logrus v1.8.1
1415
github.com/slackhq/nebula v1.4.0
1516
github.com/spf13/cobra v1.2.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YB
808808
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
809809
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
810810
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
811+
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
812+
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
811813
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
812814
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
813815
github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=

0 commit comments

Comments
 (0)