diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 5a467011..cc1fe7d5 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -26,7 +26,7 @@ jobs: actions: read contents: read security-events: write - + strategy: fail-fast: false matrix: @@ -39,40 +39,12 @@ jobs: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout repository - uses: actions/checkout@2d7d9f7ff5b310f983d059b68785b3c74d8b8edd + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Initialize CodeQL - uses: github/codeql-action/init@e1f83c153a6cb7134f035e16e2626b216e7168c9 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 with: languages: ${{ matrix.language }} - - name: Autobuild - uses: github/codeql-action/autobuild@9e39a05578dd315aad814d3c71bd03472cc5b815 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4067cdab784c667cf1b7fa95169f3a0e0a381d63 - with: - category: "/language:${{matrix.language}}" - output: sarif-results - upload: failure-only - - - name: filter-sarif - uses: advanced-security/filter-sarif@59d0a64b3c0a34d787819f6659708915b6210582 - with: - patterns: | - +**/*.go - -artifact/artifact.go - input: sarif-results/go.sarif - output: sarif-results/go.sarif - - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@2bbafcdd7fbf96243689e764c2f15d9735164f33 - with: - sarif_file: sarif-results/go.sarif - - - name: Upload loc as a Build Artifact - uses: actions/upload-artifact@b18b1d32f3f31abcdc29dee3f2484801fe7822f4 - with: - name: sarif-results - path: sarif-results - retention-days: 1 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 \ No newline at end of file diff --git a/apis/handlers/addprog.go b/apis/handlers/addprog.go index 3e40e5ca..1c3d37d8 100644 --- a/apis/handlers/addprog.go +++ b/apis/handlers/addprog.go @@ -42,7 +42,7 @@ func AddEbpfPrograms(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http.Handl }(&mesg, &statusCode) if models.IsReadOnly { log.Warn().Msgf("We are in Between Restart Please try after some time") - mesg = "We are in Between Restart Please try after some time" + mesg = "We are currently in the middle of a restart. Please attempt again after a while." return } defer DecWriteReq() diff --git a/apis/handlers/deleteprog.go b/apis/handlers/deleteprog.go index fe0ed052..43614e75 100644 --- a/apis/handlers/deleteprog.go +++ b/apis/handlers/deleteprog.go @@ -42,7 +42,7 @@ func DeleteEbpfPrograms(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http.Ha }(&mesg, &statusCode) if models.IsReadOnly { log.Warn().Msgf("We are in Between Restart Please try after some time") - mesg = "We are in Between Restart Please try after some time" + mesg = "We are currently in the middle of a restart. Please attempt again after a while." return } defer DecWriteReq() diff --git a/apis/handlers/restart_linux.go b/apis/handlers/restart_linux.go index 5df78da1..4c73b80a 100644 --- a/apis/handlers/restart_linux.go +++ b/apis/handlers/restart_linux.go @@ -11,14 +11,13 @@ import ( "net" "os" "os/exec" - "regexp" + "path/filepath" "strconv" "strings" "syscall" "time" "net/http" - "net/url" "github.com/rs/zerolog/log" @@ -50,7 +49,7 @@ func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc { }(&mesg, &statusCode) if models.IsReadOnly { log.Warn().Msgf("We are in Between Restart Please try after some time") - mesg = "We are in Between Restart Please try after some time" + mesg = "We are currently in the middle of a restart. Please attempt again after a while." statusCode = http.StatusInternalServerError return } @@ -73,14 +72,6 @@ func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc { statusCode = http.StatusInternalServerError return } - - match, _ := regexp.MatchString(`^v\d+\.\d+\.\d+$`, t.Version) - if !match { - mesg = "version naming convention is wrong it will like vx.y.z" - log.Error().Msg(mesg) - statusCode = http.StatusInternalServerError - return - } machineHostname, err := os.Hostname() if err != nil { mesg = "failed to get os hostname" @@ -94,25 +85,6 @@ func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc { statusCode = http.StatusInternalServerError return } - URL, err := url.Parse(t.ArtifactURL) - if err != nil { - mesg = "url format is wrong" - log.Error().Msg(mesg) - statusCode = http.StatusInternalServerError - return - } - if URL.Scheme != models.HttpScheme && URL.Scheme != models.FileScheme && URL.Scheme != models.HttpsScheme { - mesg = "currently only http,https,file is supported" - log.Error().Msg(mesg) - statusCode = http.StatusInternalServerError - return - } - if strings.Contains(t.ArtifactURL, "..") { - mesg = "bad string" - log.Error().Msg(mesg) - statusCode = http.StatusInternalServerError - return - } defer func() { models.IsReadOnly = false }() @@ -128,14 +100,14 @@ func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc { time.Sleep(time.Millisecond) } - oldCfgPath, err := restart.ReadSymlink(bpfcfg.HostConfig.BasePath + "/latest/l3afd.cfg") + oldCfgPath, err := restart.ReadSymlink(filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd.cfg")) if err != nil { mesg = fmt.Sprintf("failed read symlink: %v", err) log.Error().Msg(mesg) statusCode = http.StatusInternalServerError return } - oldBinPath, err := restart.ReadSymlink(bpfcfg.HostConfig.BasePath + "/latest/l3afd") + oldBinPath, err := restart.ReadSymlink(filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd")) if err != nil { mesg = fmt.Sprintf("failed to read symlink: %v", err) log.Error().Msg(mesg) @@ -143,10 +115,10 @@ func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc { return } oldVersion := strings.Split(strings.Trim(oldBinPath, bpfcfg.HostConfig.BasePath+"/"), "/")[0] - - err = restart.GetNewVersion(t.ArtifactURL, oldVersion, t.Version, bpfcfg.HostConfig) + fmt.Println("my old version is this ", oldVersion) + err = restart.GetNewVersion(t.ArtifactName, oldVersion, t.Version, bpfcfg.HostConfig) if err != nil { - mesg = fmt.Sprintf("failed to getNewVersion: %v", err) + mesg = fmt.Sprintf("failed to get new version: %v", err) log.Error().Msg(mesg) statusCode = http.StatusInternalServerError err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, t.Version, bpfcfg.HostConfig) @@ -209,7 +181,7 @@ func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc { return } // we have added - cmd := exec.Command(bpfcfg.HostConfig.BasePath+"/latest/l3afd", "--config", bpfcfg.HostConfig.BasePath+"/latest/l3afd.cfg") + cmd := exec.Command(filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd"), "--config", filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd.cfg")) cmd.SysProcAttr = &syscall.SysProcAttr{ Setsid: true, } diff --git a/apis/handlers/updateconfig.go b/apis/handlers/updateconfig.go index c0d5152c..b6f7e16f 100644 --- a/apis/handlers/updateconfig.go +++ b/apis/handlers/updateconfig.go @@ -42,7 +42,7 @@ func UpdateConfig(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http.HandlerF }(&mesg, &statusCode) if models.IsReadOnly { log.Warn().Msgf("We are in Between Restart Please try after some time") - mesg = "We are in Between Restart Please try after some time" + mesg = "We are currently in the middle of a restart. Please attempt again after a while." return } defer DecWriteReq() diff --git a/artifact/artifact.go b/artifact/artifact.go deleted file mode 100644 index c0e31c5c..00000000 --- a/artifact/artifact.go +++ /dev/null @@ -1,206 +0,0 @@ -package artifact - -import ( - "archive/tar" - "archive/zip" - "bytes" - "compress/gzip" - "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/l3af-project/l3afd/v2/models" -) - -var ( - copyBufPool sync.Pool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }} -) - -func DownloadArtifact(urlpath string, timeout time.Duration, buf *bytes.Buffer) error { - URL, err := ValidateURL(urlpath) - if err != nil { - return err - } - switch URL.Scheme { - case models.HttpScheme, models.HttpsScheme: - { - timeOut := time.Duration(timeout) * time.Second - var netTransport = &http.Transport{ - ResponseHeaderTimeout: timeOut, - } - client := http.Client{Transport: netTransport, Timeout: timeOut} - // Get the data - resp, err := client.Get(URL.String()) - if err != nil { - return fmt.Errorf("download failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("get request returned unexpected status code: %d (%s), %d was expected\n\tResponse Body: %s", resp.StatusCode, http.StatusText(resp.StatusCode), http.StatusOK, buf.Bytes()) - } - buf.ReadFrom(resp.Body) - return nil - } - case models.FileScheme: - { - if FileExists(URL.Path) { - f, err := os.Open(URL.Path) - if err != nil { - return fmt.Errorf("opening err : %w", err) - } - buf.ReadFrom(f) - f.Close() - } else { - return fmt.Errorf("artifact is not found") - } - return nil - } - default: - return fmt.Errorf("unknown url scheme") - } -} -func ExtractArtifact(artifactName string, buf *bytes.Buffer, tempDir string) error { - switch artifact := artifactName; { - case strings.HasSuffix(artifact, ".zip"): - { - c := bytes.NewReader(buf.Bytes()) - zipReader, err := zip.NewReader(c, int64(c.Len())) - if err != nil { - return fmt.Errorf("failed to create zip reader: %w", err) - } - for _, file := range zipReader.File { - - zippedFile, err := file.Open() - if err != nil { - return fmt.Errorf("unzip failed: %w", err) - } - defer zippedFile.Close() - - extractedFilePath, err := ValidatePath(file.Name, tempDir) - if err != nil { - return err - } - - if file.FileInfo().IsDir() { - os.MkdirAll(extractedFilePath, file.Mode()) - } else { - outputFile, err := os.OpenFile( - extractedFilePath, - os.O_WRONLY|os.O_CREATE|os.O_TRUNC, - file.Mode(), - ) - if err != nil { - return fmt.Errorf("unzip failed to create file: %w", err) - } - defer outputFile.Close() - - buf := copyBufPool.Get().(*bytes.Buffer) - _, err = io.CopyBuffer(outputFile, zippedFile, buf.Bytes()) - if err != nil { - return fmt.Errorf("GetArtifacts failed to copy files: %w", err) - } - copyBufPool.Put(buf) - } - } - return nil - } - case strings.HasSuffix(artifact, ".tar.gz"): - { - archive, err := gzip.NewReader(buf) - if err != nil { - return fmt.Errorf("failed to create Gzip reader: %w", err) - } - defer archive.Close() - tarReader := tar.NewReader(archive) - - for { - header, err := tarReader.Next() - - if err == io.EOF { - break - } else if err != nil { - return fmt.Errorf("untar failed: %w", err) - } - - fPath, err := ValidatePath(header.Name, tempDir) - if err != nil { - return err - } - - info := header.FileInfo() - if info.IsDir() { - if err = os.MkdirAll(fPath, info.Mode()); err != nil { - return fmt.Errorf("untar failed to create directories: %w", err) - } - continue - } - - file, err := os.OpenFile(fPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) - if err != nil { - return fmt.Errorf("untar failed to create file: %w", err) - } - defer file.Close() - - buf := copyBufPool.Get().(*bytes.Buffer) - _, err = io.CopyBuffer(file, tarReader, buf.Bytes()) - if err != nil { - return fmt.Errorf("GetArtifacts failed to copy files: %w", err) - } - copyBufPool.Put(buf) - } - return nil - } - default: - return fmt.Errorf("unknown artifact format") - } -} - -func ValidateURL(urlpath string) (*url.URL, error) { - URL, err := url.Parse(urlpath) - if err != nil { - return nil, fmt.Errorf("unknown url format : %w", err) - } - if URL.Scheme == "" { - return nil, fmt.Errorf("URL scheme is missing") - } - if URL.Scheme == models.HttpScheme || URL.Scheme == models.HttpsScheme { - if URL.Host == "" { - return nil, fmt.Errorf("URL host is missing") - } - // Forbid fragment in the URL to prevent potential attacks - if URL.Fragment != "" { - return nil, fmt.Errorf("URL must not contain a fragment") - } - } - if strings.Contains(URL.Path, "..") { - return nil, fmt.Errorf("URL path must not contain '..'") - } - return URL, nil -} - -func ValidatePath(filePath string, destination string) (string, error) { - destpath := filepath.Join(destination, filePath) - if strings.Contains(filePath, "..") { - return "", fmt.Errorf(" file contains filepath (%s) that includes (..)", filePath) - } - if !strings.HasPrefix(destpath, filepath.Clean(destination)+string(os.PathSeparator)) { - return "", fmt.Errorf("%s: illegal file path", filePath) - } - return destpath, nil -} - -// fileExists checks if a file exists or not -func FileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} diff --git a/bpfprogs/bpf.go b/bpfprogs/bpf.go index 119ed532..5d8c061a 100644 --- a/bpfprogs/bpf.go +++ b/bpfprogs/bpf.go @@ -5,12 +5,17 @@ package bpfprogs import ( + "archive/tar" + "archive/zip" "bytes" + "compress/gzip" "container/ring" "context" "errors" "fmt" + "io" "net" + "net/http" "net/url" "os" "os/exec" @@ -18,10 +23,10 @@ import ( "path/filepath" "strconv" "strings" + "sync" "time" "unsafe" - "github.com/l3af-project/l3afd/v2/artifact" "github.com/l3af-project/l3afd/v2/config" "github.com/l3af-project/l3afd/v2/models" "github.com/l3af-project/l3afd/v2/stats" @@ -34,7 +39,8 @@ import ( ) var ( - execCommand = exec.Command + execCommand = exec.Command + copyBufPool sync.Pool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }} ) //lint:ignore U1000 avoid false linter error on windows, since this variable is only used in linux code @@ -165,7 +171,7 @@ func LoadRootProgram(ifaceName string, direction string, progType string, conf * // On l3afd crashing scenario verify root program are unloaded properly by checking existence of persisted maps // if map file exists then root program didn't clean up pinned map files - if artifact.FileExists(rootProgBPF.MapNamePath) { + if fileExists(rootProgBPF.MapNamePath) { log.Warn().Msgf("previous instance of root program %s persisted map %s file exists", rootProgBPF.Program.Name, rootProgBPF.MapNamePath) if err := rootProgBPF.RemoveRootProgMapFile(ifaceName); err != nil { log.Warn().Err(err).Msgf("previous instance of root program %s map file not removed successfully - %s ", rootProgBPF.Program.Name, rootProgBPF.MapNamePath) @@ -620,12 +626,12 @@ func (b *BPF) GetArtifacts(conf *config.Config) error { urlpath := path.Join(RepoURL, b.Program.Name, b.Program.Version, platform, b.Program.Artifact) log.Info().Msgf("Retrieving artifact - %s", urlpath) - err = artifact.DownloadArtifact(urlpath, conf.HttpClientTimeout, buf) + err = DownloadArtifact(urlpath, conf.HttpClientTimeout, buf) if err != nil { return err } tempDir := filepath.Join(conf.BPFDir, b.Program.Name, b.Program.Version) - err = artifact.ExtractArtifact(b.Program.Artifact, buf, tempDir) + err = ExtractArtifact(b.Program.Artifact, buf, tempDir) if err != nil { return fmt.Errorf("unable to extract artifact %w", err) } @@ -651,6 +657,15 @@ func (b *BPF) createUpdateRulesFile(direction string) (string, error) { } +// fileExists checks if a file exists or not +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + // Add eBPF map into BPFMaps list func (b *BPF) AddBPFMap(mapName string) error { bpfMap, err := b.GetBPFMap(mapName) @@ -1380,7 +1395,7 @@ func (b *BPF) PinBpfMaps(ifaceName string) error { mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, ifaceName, k) } // In case one of the program pins the map then other program will skip - if !artifact.FileExists(mapFilename) { + if !fileExists(mapFilename) { if err := v.Pin(mapFilename); err != nil { return fmt.Errorf("eBPF program %s map %s:failed to pin the map err - %w", b.Program.Name, mapFilename, err) } @@ -1497,3 +1512,170 @@ func (b *BPF) StopUserProgram(ifaceName, direction string) error { } return nil } + +func DownloadArtifact(urlpath string, timeout time.Duration, buf *bytes.Buffer) error { + URL, err := url.Parse(urlpath) + if err != nil { + return fmt.Errorf("unknown url format : %w", err) + } + if URL.Scheme == "" { + return fmt.Errorf("URL scheme is missing") + } + if URL.Scheme == models.HttpScheme || URL.Scheme == models.HttpsScheme { + if URL.Host == "" { + return fmt.Errorf("URL host is missing") + } + // Forbid fragment in the URL to prevent potential attacks + if URL.Fragment != "" { + return fmt.Errorf("URL must not contain a fragment") + } + } + if strings.Contains(URL.Path, "..") { + return fmt.Errorf("URL path must not contain '..'") + } + switch URL.Scheme { + case models.HttpScheme, models.HttpsScheme: + { + timeOut := time.Duration(timeout) * time.Second + var netTransport = &http.Transport{ + ResponseHeaderTimeout: timeOut, + } + client := http.Client{Transport: netTransport, Timeout: timeOut} + // Get the data + resp, err := client.Get(URL.String()) + if err != nil { + return fmt.Errorf("download failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("get request returned unexpected status code: %d (%s), %d was expected\n\tResponse Body: %s", resp.StatusCode, http.StatusText(resp.StatusCode), http.StatusOK, buf.Bytes()) + } + buf.ReadFrom(resp.Body) + return nil + } + case models.FileScheme: + { + fmt.Println("ftp ATUL", URL.Path) + if fileExists(URL.Path) { + f, err := os.Open(URL.Path) + if err != nil { + return fmt.Errorf("opening err : %w", err) + } + buf.ReadFrom(f) + f.Close() + } else { + return fmt.Errorf("artifact is not found") + } + return nil + } + default: + return fmt.Errorf("unknown url scheme") + } +} +func ExtractArtifact(artifactName string, buf *bytes.Buffer, tempDir string) error { + switch artifact := artifactName; { + case strings.HasSuffix(artifact, ".zip"): + { + c := bytes.NewReader(buf.Bytes()) + zipReader, err := zip.NewReader(c, int64(c.Len())) + if err != nil { + return fmt.Errorf("failed to create zip reader: %w", err) + } + for _, file := range zipReader.File { + + zippedFile, err := file.Open() + if err != nil { + return fmt.Errorf("unzip failed: %w", err) + } + defer zippedFile.Close() + + extractedFilePath, err := ValidatePath(file.Name, tempDir) + if err != nil { + return err + } + + if file.FileInfo().IsDir() { + os.MkdirAll(extractedFilePath, file.Mode()) + } else { + outputFile, err := os.OpenFile( + extractedFilePath, + os.O_WRONLY|os.O_CREATE|os.O_TRUNC, + file.Mode(), + ) + if err != nil { + return fmt.Errorf("unzip failed to create file: %w", err) + } + defer outputFile.Close() + + buf := copyBufPool.Get().(*bytes.Buffer) + _, err = io.CopyBuffer(outputFile, zippedFile, buf.Bytes()) + if err != nil { + return fmt.Errorf("GetArtifacts failed to copy files: %w", err) + } + copyBufPool.Put(buf) + } + } + return nil + } + case strings.HasSuffix(artifact, ".tar.gz"): + { + archive, err := gzip.NewReader(buf) + if err != nil { + return fmt.Errorf("failed to create Gzip reader: %w", err) + } + defer archive.Close() + tarReader := tar.NewReader(archive) + + for { + header, err := tarReader.Next() + + if err == io.EOF { + break + } else if err != nil { + return fmt.Errorf("untar failed: %w", err) + } + + fPath, err := ValidatePath(header.Name, tempDir) + if err != nil { + return err + } + + info := header.FileInfo() + if info.IsDir() { + if err = os.MkdirAll(fPath, info.Mode()); err != nil { + return fmt.Errorf("untar failed to create directories: %w", err) + } + continue + } + + file, err := os.OpenFile(fPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) + if err != nil { + return fmt.Errorf("untar failed to create file: %w", err) + } + defer file.Close() + + buf := copyBufPool.Get().(*bytes.Buffer) + _, err = io.CopyBuffer(file, tarReader, buf.Bytes()) + if err != nil { + return fmt.Errorf("GetArtifacts failed to copy files: %w", err) + } + copyBufPool.Put(buf) + } + return nil + } + default: + return fmt.Errorf("unknown artifact format") + } +} + +func ValidatePath(filePath string, destination string) (string, error) { + destpath := filepath.Join(destination, filePath) + if strings.Contains(filePath, "..") { + return "", fmt.Errorf(" file contains filepath (%s) that includes (..)", filePath) + } + if !strings.HasPrefix(destpath, filepath.Clean(destination)+string(os.PathSeparator)) { + return "", fmt.Errorf("%s: illegal file path", filePath) + } + return destpath, nil +} diff --git a/bpfprogs/bpf_test.go b/bpfprogs/bpf_test.go index 6c7455b9..ab237644 100644 --- a/bpfprogs/bpf_test.go +++ b/bpfprogs/bpf_test.go @@ -19,7 +19,6 @@ import ( "testing" "github.com/golang/mock/gomock" - "github.com/l3af-project/l3afd/v2/artifact" "github.com/l3af-project/l3afd/v2/config" "github.com/l3af-project/l3afd/v2/mocks" "github.com/l3af-project/l3afd/v2/models" @@ -646,7 +645,7 @@ func Test_fileExists(t *testing.T) { }, } for _, tt := range tests { - if artifact.FileExists(tt.fileName) != tt.exist { + if fileExists(tt.fileName) != tt.exist { t.Errorf("Invalid filename") } } diff --git a/bpfprogs/bpfdebug.go b/bpfprogs/bpfdebug.go index 1384ccfb..4d047383 100644 --- a/bpfprogs/bpfdebug.go +++ b/bpfprogs/bpfdebug.go @@ -28,7 +28,7 @@ func SetupBPFDebug(ebpfChainDebugAddr string, BPFConfigs *NFConfigs) { } listener, err := net.ListenTCP("tcp", tcpAddr) if err != nil { - log.Fatal().Err(err).Msgf("unable to create net Listen") + log.Fatal().Err(err).Msgf("unable to create tcp listener") } models.AllNetListeners.Store("debug_http", listener) } diff --git a/bpfprogs/nfconfig.go b/bpfprogs/nfconfig.go index 199e79db..4c0be1b3 100644 --- a/bpfprogs/nfconfig.go +++ b/bpfprogs/nfconfig.go @@ -1485,7 +1485,8 @@ func (c *NFConfigs) DownloadAndStartProbes(element *list.Element) error { return nil } -func SerilazeProgram(e *list.Element) *models.L3AFMetaData { +// SerialzeProgram this function wil serialize the program +func SerialzeProgram(e *list.Element) *models.L3AFMetaData { tmp := &models.L3AFMetaData{} bpf := e.Value.(*BPF) tmp.BpfMaps = make([]string, 0) @@ -1543,6 +1544,7 @@ func SerilazeProgram(e *list.Element) *models.L3AFMetaData { return tmp } +// GetL3AFHOSTDATA this function will give serialize form of current l3afd state func (c *NFConfigs) GetL3AFHOSTDATA() models.L3AFALLHOSTDATA { result := models.L3AFALLHOSTDATA{} result.HostName = c.HostName @@ -1556,7 +1558,7 @@ func (c *NFConfigs) GetL3AFHOSTDATA() models.L3AFALLHOSTDATA { for k, v := range c.IngressXDPBpfs { ls := make([]*models.L3AFMetaData, 0) for e := v.Front(); e != nil; e = e.Next() { - ls = append(ls, SerilazeProgram(e)) + ls = append(ls, SerialzeProgram(e)) } result.IngressXDPBpfs[k] = ls } @@ -1565,7 +1567,7 @@ func (c *NFConfigs) GetL3AFHOSTDATA() models.L3AFALLHOSTDATA { for k, v := range c.IngressTCBpfs { ls := make([]*models.L3AFMetaData, 0) for e := v.Front(); e != nil; e = e.Next() { - ls = append(ls, SerilazeProgram(e)) + ls = append(ls, SerialzeProgram(e)) } result.IngressTCBpfs[k] = ls } @@ -1574,13 +1576,13 @@ func (c *NFConfigs) GetL3AFHOSTDATA() models.L3AFALLHOSTDATA { for k, v := range c.EgressTCBpfs { ls := make([]*models.L3AFMetaData, 0) for e := v.Front(); e != nil; e = e.Next() { - ls = append(ls, SerilazeProgram(e)) + ls = append(ls, SerialzeProgram(e)) } result.EgressTCBpfs[k] = ls } } for e := c.ProbesBpfs.Front(); e != nil; e = e.Next() { - result.ProbesBpfs = append(result.ProbesBpfs, *SerilazeProgram(e)) + result.ProbesBpfs = append(result.ProbesBpfs, *SerialzeProgram(e)) } metrics, _ := prometheus.DefaultGatherer.Gather() result.AllStats = make([]models.MetricVec, 0) @@ -1612,6 +1614,7 @@ func (c *NFConfigs) GetL3AFHOSTDATA() models.L3AFALLHOSTDATA { return result } +// StartAllUserProgramsAndProbes this function will restart all the User Programs and probes func (c *NFConfigs) StartAllUserProgramsAndProbes() error { if c.IngressXDPBpfs != nil { for iface, v := range c.IngressXDPBpfs { @@ -1749,6 +1752,7 @@ func (c *NFConfigs) StartAllUserProgramsAndProbes() error { return nil } +// StopAllProbes this function will stop all the probes func (c *NFConfigs) StopAllProbes() { if c.IngressXDPBpfs != nil { for _, v := range c.IngressXDPBpfs { diff --git a/config/config.go b/config/config.go index ec69e2a3..aa57a5e7 100644 --- a/config/config.go +++ b/config/config.go @@ -34,6 +34,7 @@ type Config struct { BpfChainingEnabled bool TimetoRestart int BasePath string + RestartArtifactURL string FileLogLocation string FileLogMaxSize int @@ -119,6 +120,7 @@ func ReadConfig(configPath string) (*Config, error) { FileLogMaxAge: LoadOptionalConfigInt(confReader, "l3afd", "file-log-max-age", 60), TimetoRestart: LoadOptionalConfigInt(confReader, "l3afd", "time-to-restart", 7), EBPFRepoURL: LoadConfigString(confReader, "ebpf-repo", "url"), + RestartArtifactURL: LoadOptionalConfigString(confReader, "ebpf-repo", "restart-artifacts-url", "file:///srv/l3afd"), HttpClientTimeout: LoadOptionalConfigDuration(confReader, "l3afd", "http-client-timeout", 30*time.Second), MaxEBPFReStartCount: LoadOptionalConfigInt(confReader, "l3afd", "max-ebpf-restart-count", 3), BpfChainingEnabled: LoadConfigBool(confReader, "l3afd", "bpf-chaining-enabled"), diff --git a/config/l3afd.cfg b/config/l3afd.cfg index 85a40b7d..01b8b1ef 100644 --- a/config/l3afd.cfg +++ b/config/l3afd.cfg @@ -19,6 +19,7 @@ basepath: /usr/local/l3afd [ebpf-repo] url: file:///srv/l3afd +restart-artifacts-url: file:///srv/l3afd [web] metrics-addr: 0.0.0.0:8898 diff --git a/main.go b/main.go index cf63e56c..71c6aa92 100644 --- a/main.go +++ b/main.go @@ -103,7 +103,7 @@ func main() { } if err = pidfile.CheckPIDConflict(conf.PIDFilename); err != nil { - if err = setupForRestart(ctx, conf); err != nil { + if err = setupForRestartOuter(ctx, conf); err != nil { log.Warn().Msg("Doing Normal Startup") } else { log.Fatal().Err(err).Msgf("The PID file: %s, is in an unacceptable state", conf.PIDFilename) @@ -247,42 +247,60 @@ func ReadConfigsFromConfigStore(conf *config.Config) ([]models.L3afBPFPrograms, return t, nil } -func HandleErr(e error, msg string) { - if e == nil { - return - } - log.Fatal().Err(e).Msgf(msg) - sendState("Failed") - os.Exit(0) -} -func setupForRestart(ctx context.Context, conf *config.Config) error { +func setupForRestartOuter(ctx context.Context, conf *config.Config) error { if _, err := os.Stat(models.HostSock); os.IsNotExist(err) { return err } stateSockPath = models.StateSock models.IsReadOnly = true + err := setupForRestart(ctx, conf) + if err != nil { + sendState("Failed") + log.Fatal().Err(err).Msg("unable to restart the l3afd") + } + // we need to write code to send ready status + sendState("Ready") + models.IsReadOnly = false + <-models.CloseForRestart + os.Exit(0) + return nil +} + +func setupForRestart(ctx context.Context, conf *config.Config) error { // Now you need to write client side code conn, err := net.Dial("unix", models.HostSock) - HandleErr(err, "unable to dial unix domain socket") + if err != nil { + return fmt.Errorf("unable to dial unix domain socket : %w", err) + } defer conn.Close() decoder := gob.NewDecoder(conn) var t models.L3AFALLHOSTDATA err = decoder.Decode(&t) - HandleErr(err, "unable to decode") + if err != nil { + return fmt.Errorf("unable to decode") + } machineHostname, err := os.Hostname() - HandleErr(err, "unable to fetch the hostname") + if err != nil { + return fmt.Errorf("unable to fetch the hostname") + } l, err := restart.Getnetlistener(3, "stat_server") - HandleErr(err, "getting stat_server listener failed") + if err != nil { + return fmt.Errorf("getting stat_server listener failed") + } models.AllNetListeners.Store("stat_http", l) l, err = restart.Getnetlistener(4, "main_server") - HandleErr(err, "getting main_server listener failed") + if err != nil { + return fmt.Errorf("getting main_server listener failed") + } models.AllNetListeners.Store("main_http", l) if conf.EBPFChainDebugEnabled { l, err = restart.Getnetlistener(5, "debug_server") - HandleErr(err, "getting main_server listener failed") + if err != nil { + return fmt.Errorf("getting main_server listener failed") + } models.AllNetListeners.Store("debug_http", l) } // setup Metrics endpoint @@ -294,25 +312,30 @@ func setupForRestart(ctx context.Context, conf *config.Config) error { ebpfConfigs, err := restart.Convert(ctx, t, conf) ebpfConfigs.BpfMetricsMon = bpfM ebpfConfigs.ProcessMon = pMon - HandleErr(err, "Failed to convert deserilaze the state") + if err != nil { + return fmt.Errorf("failed to convert deserilaze the state") + } err = ebpfConfigs.StartAllUserProgramsAndProbes() - HandleErr(err, "failed to start all the user programs and probes") + if err != nil { + return fmt.Errorf("failed to start all the user programs and probes") + } err = apis.StartConfigWatcher(ctx, machineHostname, daemonName, conf, ebpfConfigs) - HandleErr(err, "Starting config Watcher failed") + if err != nil { + return fmt.Errorf("starting config Watcher failed") + } err = handlers.InitConfigs(ebpfConfigs) - HandleErr(err, "L3afd failed to initialise configs") + if err != nil { + return fmt.Errorf("l3afd failed to initialise configs") + } if conf.EBPFChainDebugEnabled { bpfprogs.SetupBPFDebug(conf.EBPFChainDebugAddr, ebpfConfigs) } ebpfConfigs.ProcessMon.PCheckStart(ebpfConfigs.IngressXDPBpfs, ebpfConfigs.IngressTCBpfs, ebpfConfigs.EgressTCBpfs, &ebpfConfigs.ProbesBpfs) ebpfConfigs.BpfMetricsMon.BpfMetricsStart(ebpfConfigs.IngressXDPBpfs, ebpfConfigs.IngressTCBpfs, ebpfConfigs.EgressTCBpfs, &ebpfConfigs.ProbesBpfs) err = pidfile.CreatePID(conf.PIDFilename) - HandleErr(err, fmt.Sprintf("The PID file: %s, could not be created", conf.PIDFilename)) - // we need to write code to send ready status - sendState("Ready") - models.IsReadOnly = false - <-models.CloseForRestart - os.Exit(0) + if err != nil { + return fmt.Errorf("the PID file: %s, could not be created", conf.PIDFilename) + } return nil } diff --git a/models/l3afd.go b/models/l3afd.go index eece2acb..275eb665 100644 --- a/models/l3afd.go +++ b/models/l3afd.go @@ -179,7 +179,7 @@ const HostSock string = "/tmp/l3afd.sock" const StateSock string = "/tmp/l3afstate.sock" type RestartConfig struct { - HostName string `json:"hostname"` - Version string `json:"version"` - ArtifactURL string `json:"artifacturl"` + HostName string `json:"hostname"` + Version string `json:"version"` + ArtifactName string `json:"artifactname"` } diff --git a/restart/restart.go b/restart/restart.go index 4da84865..5bad4572 100644 --- a/restart/restart.go +++ b/restart/restart.go @@ -8,13 +8,14 @@ import ( "fmt" "net" "os" + "path" "path/filepath" + "regexp" "strings" "sync" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" - "github.com/l3af-project/l3afd/v2/artifact" "github.com/l3af-project/l3afd/v2/bpfprogs" "github.com/l3af-project/l3afd/v2/config" "github.com/l3af-project/l3afd/v2/models" @@ -117,7 +118,7 @@ func getMetricsMaps(input map[string]models.MetaMetricsBPFMap, b *bpfprogs.BPF, return nil } -func DeserilazeProgram(ctx context.Context, r *models.L3AFMetaData, hostconfig *config.Config, iface string) (*bpfprogs.BPF, error) { +func DeserializeProgram(ctx context.Context, r *models.L3AFMetaData, hostconfig *config.Config, iface string) (*bpfprogs.BPF, error) { g := &bpfprogs.BPF{} g.Program = r.Program g.FilePath = r.FilePath @@ -207,9 +208,9 @@ func Convert(ctx context.Context, t models.L3AFALLHOSTDATA, hostconfig *config.C for k, v := range t.IngressXDPBpfs { l := list.New() for _, r := range v { - f, err := DeserilazeProgram(ctx, r, hostconfig, k) + f, err := DeserializeProgram(ctx, r, hostconfig, k) if err != nil { - log.Err(err).Msg("Deserialization failed for xdpingress") + log.Err(err).Msg("Deserialization failed for xdp ingress programs") return nil, err } l.PushBack(f) @@ -221,9 +222,9 @@ func Convert(ctx context.Context, t models.L3AFALLHOSTDATA, hostconfig *config.C for k, v := range t.IngressTCBpfs { l := list.New() for _, r := range v { - f, err := DeserilazeProgram(ctx, r, hostconfig, k) + f, err := DeserializeProgram(ctx, r, hostconfig, k) if err != nil { - log.Err(err).Msg("Deserialization failed for tcingress") + log.Err(err).Msg("Deserialization failed for tc ingress programs") return nil, err } l.PushBack(f) @@ -235,9 +236,9 @@ func Convert(ctx context.Context, t models.L3AFALLHOSTDATA, hostconfig *config.C for k, v := range t.EgressTCBpfs { l := list.New() for _, r := range v { - f, err := DeserilazeProgram(ctx, r, hostconfig, k) + f, err := DeserializeProgram(ctx, r, hostconfig, k) if err != nil { - log.Err(err).Msg("Deserialization failed for tcegress") + log.Err(err).Msg("Deserialization failed for tc egress programs") return nil, err } l.PushBack(f) @@ -277,7 +278,7 @@ func Getnetlistener(fd int, fname string) (*net.TCPListener, error) { } lf, e := l.(*net.TCPListener) if !e { - return nil, fmt.Errorf("unable to covert to tcp listner") + return nil, fmt.Errorf("unable to convert to tcp listener") } file.Close() return lf, nil @@ -301,12 +302,17 @@ func ReadSymlink(symlink string) (string, error) { return originalPath, nil } -func GetNewVersion(urlpath string, oldVersion, newVersion string, conf *config.Config) error { +func GetNewVersion(artifactName, oldVersion, newVersion string, conf *config.Config) error { if oldVersion == newVersion { return nil } - newVersionPath := conf.BasePath + "/" + newVersion - if !strings.HasPrefix(conf.BasePath, filepath.Clean(newVersionPath)+string(os.PathSeparator)) { + match, _ := regexp.MatchString(`^v\d+\.\d+\.\d+$`, newVersion) + if !match { + return fmt.Errorf("version naming convention is wrong it will like vx.y.z") + } + fmt.Println("Downloading new version & stuff") + newVersionPath := filepath.Clean(filepath.Join(conf.BasePath, newVersion)) + if !strings.HasPrefix(filepath.Clean(newVersionPath)+string(os.PathSeparator), conf.BasePath) { return fmt.Errorf("malicious input given to the restart api") } err := os.RemoveAll(newVersionPath) @@ -319,35 +325,39 @@ func GetNewVersion(urlpath string, oldVersion, newVersion string, conf *config.C } // now I need to download artifacts buf := &bytes.Buffer{} - err = artifact.DownloadArtifact(urlpath, conf.HttpClientTimeout, buf) + platform, err := bpfprogs.GetPlatform() + if err != nil { + return fmt.Errorf("failed to identify platform type: %w", err) + } + urlpath := path.Join(conf.RestartArtifactURL, newVersion, platform, artifactName) + if !strings.HasPrefix(urlpath, conf.RestartArtifactURL) { + return fmt.Errorf("malicious url path %v", urlpath) + } + err = bpfprogs.DownloadArtifact(urlpath, conf.HttpClientTimeout, buf) if err != nil { return fmt.Errorf("unable to download artifacts %w", err) } - sp := strings.Split(urlpath, "/") - artifactName := sp[len(sp)-1] - err = artifact.ExtractArtifact(artifactName, buf, newVersionPath) - dir := strings.Split(artifactName, ".")[0] + err = bpfprogs.ExtractArtifact(artifactName, buf, newVersionPath) if err != nil { return fmt.Errorf("unable to extract artifacts %w", err) } // you need to store the old path for rollback purposes // we will remove symlink - err = RemoveSymlink(conf.BasePath + "/latest/l3afd") + err = RemoveSymlink(filepath.Join(conf.BasePath, "latest/l3afd")) if err != nil { return fmt.Errorf("unable to remove symlink %w", err) } - err = RemoveSymlink(conf.BasePath + "/latest/l3afd.cfg") + err = RemoveSymlink(filepath.Join(conf.BasePath, "latest/l3afd.cfg")) if err != nil { return fmt.Errorf("unable to remove symlink %w", err) } // add new symlink - - err = AddSymlink(newVersionPath+"/"+dir+"/l3afd", conf.BasePath+"/latest/l3afd") + err = AddSymlink(filepath.Join(newVersionPath, "l3afd", "l3afd"), filepath.Join(conf.BasePath, "latest/l3afd")) if err != nil { return fmt.Errorf("unable to add symlink %w", err) } - err = AddSymlink(newVersionPath+"/"+dir+"/l3afd.cfg", conf.BasePath+"/latest/l3afd.cfg") + err = AddSymlink(filepath.Join(newVersionPath, "l3afd", "l3afd.cfg"), filepath.Join(conf.BasePath, "latest/l3afd.cfg")) if err != nil { return fmt.Errorf("unable to add symlink %w", err) } @@ -359,29 +369,28 @@ func RollBackSymlink(oldCfgPath, oldBinPath string, oldVersion, newVersion strin if oldVersion == newVersion { return nil } - // you need to store the old path for rollback purposes - // we will remove symlink - err := RemoveSymlink(conf.BasePath + "/latest/l3afd") + + err := RemoveSymlink(filepath.Join(conf.BasePath, "latest/l3afd")) if err != nil { return fmt.Errorf("unable to remove symlink %w", err) } - err = RemoveSymlink(conf.BasePath + "/latest/l3afd.cfg") + err = RemoveSymlink(filepath.Join(conf.BasePath, "latest/l3afd.cfg")) if err != nil { return fmt.Errorf("unable to remove symlink %w", err) } // add new symlink - err = AddSymlink(oldBinPath, conf.BasePath+"/latest/l3afd") + err = AddSymlink(oldBinPath, filepath.Join(conf.BasePath, "latest/l3afd")) if err != nil { return fmt.Errorf("unable to add symlink %w", err) } - err = AddSymlink(oldCfgPath, conf.BasePath+"/latest/l3afd.cfg") + err = AddSymlink(oldCfgPath, filepath.Join(conf.BasePath, "latest/l3afd.cfg")) if err != nil { return fmt.Errorf("unable to add symlink %w", err) } - newVersionPath := conf.BasePath + "/" + newVersion + newVersionPath := filepath.Join(conf.BasePath, newVersion) if strings.Contains(newVersionPath, "..") { return fmt.Errorf("malicious path") } diff --git a/stats/metrics.go b/stats/metrics.go index d49ecec4..4f0d3354 100644 --- a/stats/metrics.go +++ b/stats/metrics.go @@ -67,7 +67,7 @@ func SetupMetrics(hostname, daemonName, metricsAddr string) { Name: "BPFUpdateFailedCount", Help: "The count of Failed eBPF programs updates", }, - []string{"host", "ebpf_program", "direction", "interface_name"}, + []string{"host", "bpf_program", "direction", "interface_name"}, ) BPFUpdateFailedCount = bpfUpdateFailedCountVec.MustCurryWith(prometheus.Labels{"host": hostname})