diff --git a/cmd/run.go b/cmd/run.go index bff48fd3..409392d8 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -46,11 +46,39 @@ var runCmd = &cobra.Command{ var centralCfg state.CentralCfg file, err := os.ReadFile(centralPath) if err != nil { - panic(err) - } - err = yaml.Unmarshal(file, ¢ralCfg) - if err != nil { - panic(err) + if !os.IsNotExist(err) { + panic(err) + } + var nodeCfg state.LocalCfg + file, err = os.ReadFile(nodePath) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(file, &nodeCfg) + if err != nil { + panic(err) + } + if nodeCfg.Dist == nil { + panic("central.yaml not found and node.yaml has no dist config") + } + cfg, err := core.FetchConfig(nodeCfg.Dist.Url, nodeCfg.Dist.Key) + if err != nil { + panic(err) + } + bytes, err := yaml.Marshal(cfg) + if err != nil { + panic(err) + } + err = os.WriteFile(centralPath, bytes, 0700) + if err != nil { + panic(err) + } + centralCfg = *cfg + } else { + err = yaml.Unmarshal(file, ¢ralCfg) + if err != nil { + panic(err) + } } var nodeCfg state.LocalCfg diff --git a/core/nylon.go b/core/nylon.go index 51d8667f..fb0332dc 100644 --- a/core/nylon.go +++ b/core/nylon.go @@ -74,8 +74,8 @@ func (n *Nylon) Init(s *state.State) error { } // check for central config updates - if s.Dist != nil { - for _, repo := range s.Dist.Repos { + if s.CentralCfg.Dist != nil { + for _, repo := range s.CentralCfg.Dist.Repos { s.Log.Info("config source", "repo", repo) } s.Env.RepeatTask(checkForConfigUpdates, state.CentralUpdateDelay) diff --git a/core/nylon_distribution.go b/core/nylon_distribution.go index df296e77..57b30b12 100644 --- a/core/nylon_distribution.go +++ b/core/nylon_distribution.go @@ -12,48 +12,58 @@ import ( "gopkg.in/yaml.v3" ) +// fetches and unbundles central config from url +func FetchConfig(repoStr string, key state.NyPublicKey) (*state.CentralCfg, error) { + repo, err := url.Parse(repoStr) + if err != nil { + return nil, fmt.Errorf("failed to parse repo URL %s: %w", repoStr, err) + } + cfgBody := make([]byte, 0) + + if repo.Scheme == "file" { + file, err := os.ReadFile(repo.Opaque) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %w", repo.Opaque, err) + } + cfgBody = file + } else if repo.Scheme == "http" || repo.Scheme == "https" { + res, err := http.Get(repo.String()) + if err != nil { + return nil, fmt.Errorf("failed to fetch %s: %w", repo.String(), err) + } + cfgBody, err = io.ReadAll(res.Body) + if err != nil { + res.Body.Close() + return nil, fmt.Errorf("failed to read response from %s: %w", repo.String(), err) + } + err = res.Body.Close() + if err != nil { + return nil, fmt.Errorf("failed to close response from %s: %w", repo.String(), err) + } + } + + config, err := state.UnbundleConfig(string(cfgBody), key) + if err != nil { + return nil, fmt.Errorf("failed to unbundle config from %s: %w", repoStr, err) + } + return config, nil +} + // responsible for central config distribution func checkForConfigUpdates(s *state.State) error { - if s.Dist == nil { + if s.CentralCfg.Dist == nil { return errors.New("nylon is not configured for automatic config distribution") } - for _, repoStr := range s.Dist.Repos { - repo, err := url.Parse(repoStr) - if err != nil { - return err - } + for _, repoStr := range s.CentralCfg.Dist.Repos { e := s.Env - go func() { + go func(repo string) { err := func() error { - cfgBody := make([]byte, 0) - - if repo.Scheme == "file" { - file, err := os.ReadFile(repo.Opaque) - if err != nil { - return err - } - cfgBody = file - } else if repo.Scheme == "http" || repo.Scheme == "https" { - res, err := http.Get(repo.String()) - if err != nil { - return err - } - cfgBody, err = io.ReadAll(res.Body) - if err != nil { - return err - } - err = res.Body.Close() - if err != nil { - return err - } - } - - config, err := state.UnbundleConfig(string(cfgBody), e.Dist.Key) + config, err := FetchConfig(repo, e.CentralCfg.Dist.Key) if err != nil { return err } if config.Timestamp > e.Timestamp && !s.Updating.Swap(true) { - e.Log.Info("Found a new config update in repo", "repo", repo.String()) + e.Log.Info("Found a new config update in repo", "repo", repo) bytes, err := yaml.Marshal(config) if err != nil { e.Log.Error("Error marshalling new config", "err", err.Error()) @@ -69,14 +79,14 @@ func checkForConfigUpdates(s *state.State) error { err: s.Updating.Store(false) } else if state.DBG_log_repo_updates { - e.Log.Debug(fmt.Sprintf("found old update bundle at %s, skipping", repo.String())) + e.Log.Debug(fmt.Sprintf("found old update bundle at %s, skipping", repo)) } return nil }() if err != nil && state.DBG_log_repo_updates { e.Log.Error("Error updating config", "err", err.Error()) } - }() + }(repoStr) } return nil } diff --git a/example/sample-node.yaml b/example/sample-node.yaml index c48b1c3f..ec896df3 100644 --- a/example/sample-node.yaml +++ b/example/sample-node.yaml @@ -5,4 +5,7 @@ nonetconfigure: false # Default: false - If enabled, Nylon will not configure th usesystemrouting: false # Default: false - if enabled, Nylon will use the system route table to forward packets logpath: "" # Default: "" - If set, Nylon will log to this file interfacename: "" # Default: "" - If set, Nylon will use this interface name instead of the default "nylon" or utunx on macOS -disablerouting: false # Default: false - If true, Nylon will not route traffic through this node \ No newline at end of file +disablerouting: false # Default: false - If true, Nylon will not route traffic through this node +dist: # Optional: If set, Nylon will bootstrap central.yaml from this URL if it does not exist already + url: https://static.example.com/network1.nybundle + key: 7PaN6DmAayz4KnDnsXSXJH+Oy0TFGeoM4FEbQfLriVY= \ No newline at end of file diff --git a/state/config.go b/state/config.go index d187603f..cdf7d64a 100644 --- a/state/config.go +++ b/state/config.go @@ -29,6 +29,11 @@ type DistributionCfg struct { Repos []string } +type LocalDistributionCfg struct { + Key NyPublicKey + Url string +} + type CentralCfg struct { Dist *DistributionCfg `yaml:",omitempty"` Routers []RouterCfg @@ -65,6 +70,7 @@ type LocalCfg struct { Id NodeId // Address that the data plane can be accessed by Port uint16 + Dist *LocalDistributionCfg `yaml:",omitempty"` DisableRouting bool UseSystemRouting bool NoNetConfigure bool `yaml:",omitempty"` diff --git a/state/validation.go b/state/validation.go index e07b73c6..5b7f1e59 100644 --- a/state/validation.go +++ b/state/validation.go @@ -55,6 +55,12 @@ func NodeConfigValidator(node *LocalCfg) error { return fmt.Errorf("interface name is invalid: %v", err) } } + if node.Dist != nil { + _, err := url.Parse(node.Dist.Url) + if err != nil { + return err + } + } return nil }