New system submodule to support package files.

This commit is contained in:
Kleissner
2022-03-26 00:21:25 +01:00
parent a05944c318
commit 1a4fde0c93
7 changed files with 313 additions and 24 deletions

View File

@@ -5,6 +5,7 @@ BlockchainGlobal: "data/blockchain global/" # Blockchain global caches block
WarehouseMain: "data/warehouse main/" # Warehouse main stores the actual data of files shared by the end-user.
SearchIndex: "data/search index/" # Local search index of blockchain records. Empty to disable.
GeoIPDatabase: "data/GeoLite2-City.mmdb" # GeoLite2 City database to provide GeoIP information.
DataFolder: "data/" # Data folder.
# Listen defines all IP:Port combinations to listen on. If empty, it will listen on all IPs automatically on available ports.
# IPv6 must be in the form "[IPv6]:Port". This setting is only recommended to be set on servers.

View File

@@ -29,6 +29,7 @@ type Config struct {
WarehouseMain string `yaml:"WarehouseMain"` // Warehouse main stores the actual data of files shared by the end-user.
SearchIndex string `yaml:"SearchIndex"` // Local search index of blockchain records. Empty to disable.
GeoIPDatabase string `yaml:"GeoIPDatabase"` // GeoLite2 City database to provide GeoIP information.
DataFolder string `yaml:"DataFolder"` // Data folder.
// Target for the log messages: 0 = Log file, 1 = Stdout, 2 = Log file + Stdout, 3 = None
LogTarget int `yaml:"LogTarget"`

16
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/PeernetOfficial/core
go 1.16
go 1.18
require (
github.com/IncSW/geoip2 v0.1.2
@@ -8,10 +8,16 @@ require (
github.com/enfipy/locker v1.1.0
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
github.com/gorilla/websocket v1.5.0
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/net v0.0.0-20220325170049-de3da57026de
golang.org/x/sys v0.0.0-20220325203850-36772127a21f
gopkg.in/ini.v1 v1.66.4
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
lukechampine.com/blake3 v1.1.7
)
require (
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/stretchr/testify v1.7.1 // indirect
)

39
go.sum
View File

@@ -2,34 +2,35 @@ github.com/IncSW/geoip2 v0.1.2 h1:v7iAyDiNZjHES45P1JPM3SMvkw0VNeJtz0XSVxkRwOY=
github.com/IncSW/geoip2 v0.1.2/go.mod h1:adcasR40vXiUBjtzdaTTKL/6wSf+fgO4M8Gve/XzPUk=
github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/enfipy/locker v1.1.0 h1:2zVJ0ky7cS1Vjs0x6OQWFiT2dSEiHrI5/O2KCz1fgGc=
github.com/enfipy/locker v1.1.0/go.mod h1:uuj+dvWHECshK8rkHcw+ZOb9SLo16yc0Em/JGUqRqko=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=

91
system/Execute.go Normal file
View File

@@ -0,0 +1,91 @@
/*
File Name: Execute.go
Copyright: 2021 Peernet s.r.o.
Author: Peter Kleissner
*/
package system
import (
"archive/zip"
"errors"
"io"
"os"
"path/filepath"
"strings"
)
// Execute the actions described in the info header
func (update *UpdatePackage) Execute(DataFolder, PluginFolder string) (err error) {
for _, action := range update.Header.Actions {
switch action.Action {
case "extract":
destination := resolveFolders(action.Target, DataFolder, PluginFolder)
for _, f := range update.Reader.File {
// filter the filename based on source
if !strings.HasPrefix(f.Name, action.Source) {
continue
}
err := unzipFile(f, destination)
if err != nil {
return err
}
}
}
}
return nil
}
// Deletes the update package file
func (update *UpdatePackage) Delete() (err error) {
return os.Remove(update.Filename)
}
func unzipFile(f *zip.File, destination string) error {
// 4. Check if file paths are not vulnerable to Zip Slip
filePath := filepath.Join(destination, f.Name)
if !strings.HasPrefix(filePath, filepath.Clean(destination)+string(os.PathSeparator)) {
return errors.New("invalid file path: " + filePath)
}
// 5. Create directory tree
if f.FileInfo().IsDir() {
if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
return err
}
return nil
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
return err
}
// 6. Create a destination file for unzipped content
destinationFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer destinationFile.Close()
// 7. Unzip the content of a file and copy it to the destination file
zippedFile, err := f.Open()
if err != nil {
return err
}
defer zippedFile.Close()
if _, err := io.Copy(destinationFile, zippedFile); err != nil {
return err
}
return nil
}
func resolveFolders(folder, dataFolder, pluginFolder string) (resolved string) {
resolved = strings.Replace(folder, "%plugin%", pluginFolder, 1)
resolved = strings.Replace(resolved, "%data%", dataFolder, 1)
return resolved
}

137
system/Read Package.go Normal file
View File

@@ -0,0 +1,137 @@
/*
File Name: Read Package.go
Copyright: 2021 Peernet s.r.o.
Author: Peter Kleissner
*/
package system
import (
"archive/zip"
"io/ioutil"
"path"
"strings"
"gopkg.in/ini.v1"
)
type UpdatePackage struct {
Filename string // Filename of the update package
Err error // Parsing error if any
Header *IniFile // Header info
Reader *zip.ReadCloser // Access to files in the ZIP file
}
// IniFile contains the parsed data from the info.ini file
type IniFile struct {
Name string // Name of the package
Organization string // Organization that created this package
Architecture string // Target architecture. For example "windows/amd64".
Actions []IniAction // Actions to take
}
type IniAction struct {
Action string `ini:"action"` // Action: extract, delete
Source string `ini:"source"` // Folder or file in the ZIP file
Target string `ini:"target"` // Target folder or file. Certain virtual folders such as "%plugins%" are supported.
}
const IniFilename = "info.ini"
// ParseUpdateFiles returns a list of parsed update packages.
// It will check each file in the directory if a ZIP file containing a valid info.ini file.
// The caller must close all returned readers.
func ParseUpdateFiles(Directory string) (files []UpdatePackage, err error) {
// check all files in the directory
filesDir, err := ioutil.ReadDir(Directory)
if err != nil {
return nil, err
}
for _, file := range filesDir {
if file.IsDir() {
continue
}
filename := file.Name()
// ZIP file?
if strings.ToLower(path.Ext(filename)) == ".zip" {
filenamePath := path.Join(Directory, filename)
// check if the ZIP archive contains a info.ini file
reader, err := zip.OpenReader(filenamePath)
if err != nil { // invalid ZIP file
continue
}
// read info.ini file
file, err := reader.Open(IniFilename)
if err != nil {
continue
}
data, err := ioutil.ReadAll(file)
if err != nil {
continue
}
header, err := ParseIniFile(data)
files = append(files, UpdatePackage{Filename: filenamePath, Err: err, Header: header, Reader: reader})
}
}
return files, nil
}
func ParseIniFile(data []byte) (header *IniFile, err error) {
inidata, err := ini.Load(data)
if err != nil {
return nil, err
}
// parse the main section first
section, err := inidata.GetSection("main")
if err != nil {
return nil, err
}
name, err := section.GetKey("name")
if err != nil {
return nil, err
}
organization, err := section.GetKey("organization")
if err != nil {
return nil, err
}
architecture, err := section.GetKey("architecture")
if err != nil {
return nil, err
}
header = &IniFile{
Name: name.String(),
Organization: organization.String(),
Architecture: architecture.String(),
}
// parse any other section
for _, section := range inidata.Sections() {
if section.Name() == "main" || section.Name() == "DEFAULT" {
continue
}
var action IniAction
if section.MapTo(&action) != nil {
continue
}
header.Actions = append(header.Actions, action)
}
return header, nil
}

52
system/readme.md Normal file
View File

@@ -0,0 +1,52 @@
# System
This submodule defines how files and plugins are managed and updated.
## Package Files
Package files are ZIP files containing a file `info.ini` that describes which actions to take. They provide updates and installation of individual files, plugins, or entire clients.
Actions may specify target files or folders. Such paths may use virtual paths that will be automatically resolved.
```
%plugin% = Plugin folder
%data% = Data folder
```
## Sample Package File
A sample package file `TextViewer.zip` looks like this:
```
│ info.ini
└───TextViewer
Microsoft.Extensions.DependencyInjection.Abstractions.dll
Peernet.Browser.Plugins.TextViewer.deps.json
Peernet.Browser.Plugins.TextViewer.dll
Peernet.SDK.dll
```
The `info.ini` file:
```ini
[main]
name = "Text Viewer Plugin"
organization = "Peernet s.r.o."
architecture = "windows/amd64"
[action1]
action = extract
source = TextViewer
target = "%plugin%"
```
## Security Implications
The actions currently only allow to extract any arbitrary file to the local disk. Later, the destination directory will be limited to the Peernet installation directory.
Signatures for packages are not implemented yet. They will be added in a subsequent version.
## Target OS
It is up to the client to select and download the appropriate packages.