Working with packages and allowed IPs.
giredorectl now able to interact with giredored about: * Setting package data. There is no such thing as "create" or "update", just set. * Deleting package data. * Setting allowed IP addresses. This is the only authorization method ATM, more may come in future.
This commit is contained in:
		
							
								
								
									
										204
									
								
								internal/configuration/fileconfig.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								internal/configuration/fileconfig.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| package configuration | ||||
|  | ||||
| import ( | ||||
| 	// stdlib | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	// local | ||||
| 	"sources.dev.pztrn.name/pztrn/giredore/internal/structs" | ||||
| ) | ||||
|  | ||||
| // This structure represents configuration that will be parsed via file. | ||||
| // Despite on exporting fields there are setters and getters defined because | ||||
| // data from configuration file will be parsed in exported fields and they | ||||
| // may be accesses concurrently. In other words DO NOT USE EXPORTED FIELDS | ||||
| // DIRECTLY! | ||||
| type fileConfig struct { | ||||
| 	// HTTP describes HTTP server configuration. | ||||
| 	HTTP struct { | ||||
| 		// AllowedIPs is a list of IPs that allowed to access API. | ||||
| 		// There might be other authentication implemented in future. | ||||
| 		AllowedIPs      []string | ||||
| 		allowedipsmutex sync.RWMutex | ||||
| 		// Listen is an address on which HTTP server will listen. | ||||
| 		Listen string `envconfig:"default=127.0.0.1:62222"` | ||||
| 		// WaitForSeconds is a timeout during which we will wait for | ||||
| 		// HTTP server be up. If timeout will pass and HTTP server won't | ||||
| 		// start processing requests - giredore will exit. | ||||
| 		WaitForSeconds int `envconfig:"default=10"` | ||||
| 	} | ||||
| 	// Packages describes packages mapping. | ||||
| 	Packages      map[string]*structs.Package | ||||
| 	packagesMutex sync.RWMutex | ||||
| } | ||||
|  | ||||
| func (fc *fileConfig) AddOrUpdatePackage(pkg *structs.Package) { | ||||
| 	fc.packagesMutex.Lock() | ||||
| 	fc.Packages[pkg.OriginalPath] = pkg | ||||
| 	fc.packagesMutex.Unlock() | ||||
| } | ||||
|  | ||||
| func (fc *fileConfig) DeletePackage(req *structs.PackageDeleteRequest) []structs.Error { | ||||
| 	var errors []structs.Error | ||||
|  | ||||
| 	fc.packagesMutex.Lock() | ||||
| 	defer fc.packagesMutex.Unlock() | ||||
| 	_, found := fc.Packages[req.OriginalPath] | ||||
|  | ||||
| 	if !found { | ||||
| 		errors = append(errors, structs.ErrPackageWasntDefined) | ||||
| 		return errors | ||||
| 	} | ||||
|  | ||||
| 	delete(fc.Packages, req.OriginalPath) | ||||
|  | ||||
| 	return errors | ||||
| } | ||||
|  | ||||
| func (fc *fileConfig) GetAllowedIPs() []string { | ||||
| 	var allowedIPs []string | ||||
| 	fc.HTTP.allowedipsmutex.RLock() | ||||
| 	allowedIPs = append(allowedIPs, fc.HTTP.AllowedIPs...) | ||||
| 	fc.HTTP.allowedipsmutex.RUnlock() | ||||
|  | ||||
| 	return allowedIPs | ||||
| } | ||||
|  | ||||
| func (fc *fileConfig) GetAllPackagesInfo() map[string]*structs.Package { | ||||
| 	pkgs := make(map[string]*structs.Package) | ||||
|  | ||||
| 	fc.packagesMutex.Lock() | ||||
| 	for name, pkg := range fc.Packages { | ||||
| 		pkgs[name] = pkg | ||||
| 	} | ||||
| 	fc.packagesMutex.Unlock() | ||||
|  | ||||
| 	return pkgs | ||||
| } | ||||
|  | ||||
| func (fc *fileConfig) GetPackagesInfo(packages []string) (map[string]*structs.Package, []structs.Error) { | ||||
| 	pkgs := make(map[string]*structs.Package) | ||||
| 	var errors []structs.Error | ||||
|  | ||||
| 	fc.packagesMutex.Lock() | ||||
| 	for _, neededPkg := range packages { | ||||
| 		pkgData, found := fc.Packages[neededPkg] | ||||
| 		if !found { | ||||
| 			errors = append(errors, structs.ErrPackageWasntDefined+structs.Error(" Package was: "+neededPkg)) | ||||
| 		} else { | ||||
| 			pkgs[neededPkg] = pkgData | ||||
| 		} | ||||
| 	} | ||||
| 	fc.packagesMutex.Unlock() | ||||
|  | ||||
| 	return pkgs, errors | ||||
| } | ||||
|  | ||||
| // Initialize parses file contents into structure. | ||||
| func (fc *fileConfig) Initialize() { | ||||
| 	configPath := filepath.Join(envCfg.DataDir, "config.json") | ||||
| 	cfgLoadLog := log.With().Str("configuration path", configPath).Logger() | ||||
| 	cfgLoadLog.Info().Msg("Loading configuration file...") | ||||
|  | ||||
| 	configPath, err := fc.normalizePath(configPath) | ||||
| 	if err != nil { | ||||
| 		cfgLoadLog.Fatal().Err(err).Msg("Failed to normalize configuration file path.") | ||||
| 	} | ||||
|  | ||||
| 	// Check if file "config.json" specified in envConfig.DataDir field | ||||
| 	// exists. | ||||
| 	if _, err2 := os.Stat(configPath); os.IsNotExist(err2) { | ||||
| 		cfgLoadLog.Error().Msg("Unable to load configuration from filesystem.") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Try to load file into memory. | ||||
| 	fileData, err3 := ioutil.ReadFile(configPath) | ||||
| 	if err3 != nil { | ||||
| 		cfgLoadLog.Fatal().Err(err3).Msg("Failed to read configuration file data into memory.") | ||||
| 	} | ||||
|  | ||||
| 	// ...and parse it. | ||||
| 	err4 := json.Unmarshal(fileData, fc) | ||||
| 	if err4 != nil { | ||||
| 		cfgLoadLog.Fatal().Err(err4).Msg("Failed to parse configuration file.") | ||||
| 	} | ||||
|  | ||||
| 	if fc.Packages == nil { | ||||
| 		fc.Packages = make(map[string]*structs.Package) | ||||
| 	} | ||||
|  | ||||
| 	// Ensure that localhost (127.0.0.1) are defined in AllowedIPs. | ||||
| 	var localhostIsAllowed bool | ||||
| 	for _, ip := range fc.HTTP.AllowedIPs { | ||||
| 		if strings.Contains(ip, "127.0.0.1") { | ||||
| 			localhostIsAllowed = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !localhostIsAllowed { | ||||
| 		cfgLoadLog.Warn().Msg("Localhost (127.0.0.1) wasn't allowed to access configuration API, adding it to list of allowed IP addresses") | ||||
| 		fc.HTTP.AllowedIPs = append(fc.HTTP.AllowedIPs, "127.0.0.1") | ||||
| 	} else { | ||||
| 		cfgLoadLog.Debug().Msg("Localhost (127.0.0.1) is allowed to access configuration API") | ||||
| 	} | ||||
|  | ||||
| 	cfgLoadLog.Debug().Msgf("Configuration parsed: %+v", fc) | ||||
| 	cfgLoadLog.Info().Int("packages count", len(fc.Packages)).Msg("Packages list loaded") | ||||
| } | ||||
|  | ||||
| // Normalizes passed configuration file path. | ||||
| func (fc *fileConfig) normalizePath(configPath string) (string, error) { | ||||
| 	// Normalize configuration file path. | ||||
| 	if strings.Contains(configPath, "~") { | ||||
| 		homeDir, err := os.UserHomeDir() | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		configPath = strings.Replace(configPath, "~", homeDir, 1) | ||||
| 	} | ||||
|  | ||||
| 	absPath, err1 := filepath.Abs(configPath) | ||||
| 	if err1 != nil { | ||||
| 		return "", err1 | ||||
| 	} | ||||
|  | ||||
| 	return absPath, nil | ||||
| } | ||||
|  | ||||
| // Save saves configuration into file. | ||||
| func (fc *fileConfig) Save() { | ||||
| 	configPath := filepath.Join(envCfg.DataDir, "config.json") | ||||
| 	cfgSaveLog := log.With().Str("configuration path", configPath).Logger() | ||||
| 	cfgSaveLog.Info().Msg("Saving configuration file...") | ||||
|  | ||||
| 	data, err := json.Marshal(fc) | ||||
| 	if err != nil { | ||||
| 		cfgSaveLog.Fatal().Err(err).Msg("Failed to encode data into JSON. Configuration file won't be saved!") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	configPath, err1 := fc.normalizePath(configPath) | ||||
| 	if err1 != nil { | ||||
| 		cfgSaveLog.Fatal().Err(err1).Msg("Failed to normalize configuration file path.") | ||||
| 	} | ||||
|  | ||||
| 	err2 := ioutil.WriteFile(configPath, data, os.ModePerm) | ||||
| 	if err2 != nil { | ||||
| 		cfgSaveLog.Fatal().Err(err2).Msg("Failed to write configuration file data to file.") | ||||
| 	} | ||||
|  | ||||
| 	cfgSaveLog.Info().Msg("Configuration file saved.") | ||||
| } | ||||
|  | ||||
| func (fc *fileConfig) SetAllowedIPs(allowedIPs []string) { | ||||
| 	fc.HTTP.allowedipsmutex.Lock() | ||||
| 	fc.HTTP.AllowedIPs = allowedIPs | ||||
| 	fc.HTTP.allowedipsmutex.Unlock() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user