backport antivirus from experimental

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2023-03-15 15:21:45 +01:00
parent e7fd4f93f4
commit f4ba4e0f64
20 changed files with 842 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ L10N_MODULES := \
# if you add a module here please also add it to the .drone.star file
OCIS_MODULES = \
services/antivirus \
services/app-provider \
services/app-registry \
services/audit \

View File

@@ -2,6 +2,7 @@ package config
import (
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
antivirus "github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config"
appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config"
audit "github.com/owncloud/ocis/v2/services/audit/pkg/config"
@@ -72,6 +73,7 @@ type Config struct {
AdminUserID string `yaml:"admin_user_id" env:"OCIS_ADMIN_USER_ID" desc:"ID of a user, that should receive admin privileges. Consider that the UUID can be encoded in some LDAP deployment configurations like in .ldif files. These need to be decoded beforehand."`
Runtime Runtime `yaml:"runtime"`
Antivirus *antivirus.Config `yaml:"antivirus"`
AppProvider *appProvider.Config `yaml:"app_provider"`
AppRegistry *appRegistry.Config `yaml:"app_registry"`
Audit *audit.Config `yaml:"audit"`

View File

@@ -1,6 +1,7 @@
package config
import (
antivirus "github.com/owncloud/ocis/v2/services/antivirus/pkg/config/defaults"
appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults"
appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults"
audit "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults"
@@ -45,6 +46,7 @@ func DefaultConfig() *Config {
Host: "localhost",
},
Antivirus: antivirus.DefaultConfig(),
AppProvider: appProvider.DefaultConfig(),
AppRegistry: appRegistry.DefaultConfig(),
Audit: audit.DefaultConfig(),

View File

@@ -0,0 +1,30 @@
package command
import (
"github.com/owncloud/ocis/v2/ocis-pkg/config"
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
"github.com/owncloud/ocis/v2/ocis-pkg/config/parser"
"github.com/owncloud/ocis/v2/ocis/pkg/command/helper"
"github.com/owncloud/ocis/v2/ocis/pkg/register"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/command"
"github.com/urfave/cli/v2"
)
// AntivirusCommand is the entrypoint for the antivirus command.
func AntivirusCommand(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: cfg.Antivirus.Service.Name,
Usage: helper.SubcommandDescription(cfg.Antivirus.Service.Name),
Category: "services",
Before: func(c *cli.Context) error {
configlog.Error(parser.ParseConfig(cfg, true))
//cfg.Antivirus.Commons = cfg.Commons
return nil
},
Subcommands: command.GetCommands(cfg.Antivirus),
}
}
func init() {
register.AddCommand(AntivirusCommand)
}

View File

@@ -0,0 +1,37 @@
SHELL := bash
NAME := antivirus
include ../../.make/recursion.mk
############ tooling ############
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include ../../.bingo/Variables.mk
endif
############ go tooling ############
include ../../.make/go.mk
############ release ############
include ../../.make/release.mk
############ docs generate ############
include ../../.make/docs.mk
.PHONY: docs-generate
docs-generate: config-docs-generate
############ generate ############
include ../../.make/generate.mk
.PHONY: ci-go-generate
ci-go-generate: # CI runs ci-node-generate automatically before this target
.PHONY: ci-node-generate
ci-node-generate:
############ licenses ############
.PHONY: ci-node-check-licenses
ci-node-check-licenses:
.PHONY: ci-node-save-licenses
ci-node-save-licenses:

View File

View File

@@ -0,0 +1,14 @@
package main
import (
"os"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/command"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config/defaults"
)
func main() {
if err := command.Execute(defaults.DefaultConfig()); err != nil {
os.Exit(1)
}
}

View File

@@ -0,0 +1,61 @@
package command
import (
"fmt"
"net/http"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config/parser"
"github.com/urfave/cli/v2"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "check health status",
Category: "info",
Before: func(c *cli.Context) error {
return configlog.ReturnError(parser.ParseConfig(cfg))
},
Action: func(c *cli.Context) error {
logger := log.NewLogger(
log.Name(cfg.Service.Name),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
log.File(cfg.Log.File),
)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}

View File

@@ -0,0 +1,54 @@
package command
import (
"context"
"os"
"github.com/owncloud/ocis/v2/ocis-pkg/clihelper"
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
"github.com/thejerf/suture/v4"
"github.com/urfave/cli/v2"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) cli.Commands {
return []*cli.Command{
Server(cfg),
Health(cfg),
Version(cfg),
}
}
// Execute is the entry point for the antivirus command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cli.App{
Name: "antivirus",
Usage: "Serve ownCloud antivirus for oCIS",
Commands: GetCommands(cfg),
})
return app.Run(os.Args)
}
// SutureService allows for the web command to be embedded and supervised by a suture supervisor tree.
type SutureService struct {
cfg *config.Config
}
// NewSutureService creates a new web.SutureService
func NewSutureService(cfg *ociscfg.Config) suture.Service {
cfg.Policies.Commons = cfg.Commons
return SutureService{
cfg: cfg.Antivirus,
}
}
func (s SutureService) Serve(ctx context.Context) error {
s.cfg.Context = ctx
if err := Execute(s.cfg); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,107 @@
package command
import (
"context"
"fmt"
"io"
"net/http"
"github.com/oklog/run"
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config/parser"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/service"
"github.com/urfave/cli/v2"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", "authz"),
Category: "server",
Before: func(c *cli.Context) error {
return configlog.ReturnFatal(parser.ParseConfig(cfg))
},
Action: func(c *cli.Context) error {
var (
gr = run.Group{}
ctx, cancel = func() (context.Context, context.CancelFunc) {
if cfg.Context == nil {
return context.WithCancel(context.Background())
}
return context.WithCancel(cfg.Context)
}()
logger = log.NewLogger(
log.Name(cfg.Service.Name),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
log.File(cfg.Log.File),
)
)
defer cancel()
{
svc, err := service.NewAntivirus(cfg, logger)
if err != nil {
return err
}
gr.Add(svc.Run, func(_ error) {
cancel()
})
}
{
server := debug.NewService(
debug.Logger(logger),
debug.Name(cfg.Service.Name),
debug.Version(version.GetString()),
debug.Address(cfg.Debug.Addr),
debug.Token(cfg.Debug.Token),
debug.Pprof(cfg.Debug.Pprof),
debug.Zpages(cfg.Debug.Zpages),
debug.Health(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO: check if services are up and running
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
// io.WriteString should not fail but if it does we want to know.
if err != nil {
panic(err)
}
},
),
debug.Ready(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO: check if services are up and running
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
// io.WriteString should not fail but if it does we want to know.
if err != nil {
panic(err)
}
},
),
)
gr.Add(server.ListenAndServe, func(_ error) {
_ = server.Shutdown(ctx)
cancel()
})
}
return gr.Run()
},
}
}

View File

@@ -0,0 +1,26 @@
package command
import (
"fmt"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
"github.com/urfave/cli/v2"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "version",
Usage: "print the version of this binary and the running service instances",
Category: "info",
Action: func(c *cli.Context) error {
fmt.Println("Version: " + version.GetString())
fmt.Printf("Compiled: %s\n", version.Compiled())
fmt.Println("")
return nil
},
}
}

View File

@@ -0,0 +1,72 @@
package config
import (
"context"
)
// Config combines all available configuration parts.
type Config struct {
File string
Log *Log
Debug Debug `mask:"struct" yaml:"debug"`
Service Service `yaml:"-"`
InfectedFileHandling string `yaml:"infected-file-handling" env:"ANTIVIRUS_INFECTED_FILE_HANDLING" desc:"Defines the behaviour when a virus has been found. Options are: 'delete', 'continue' and 'abort '. Delete will delete the file. Continue will mark the file as infected but continues further processing. Abort will keep the file in the upload folder for further admin inspection and will not move it to its target space."`
Events Events
Scanner Scanner
MaxScanSize string `yaml:"max-scan-size" env:"ANTIVIRUS_MAX_SCAN_SIZE" desc:"The maximum scan size the virusscanner can handle. Only that much bytes of a file will be scanned. 0 means unlimited and is the default. Usable common abbreviations: [KB, KiB, GB, GiB, TB, TiB, PB, PiB, EB, EiB], example: 2GB."`
Context context.Context `yaml:"-" json:"-"`
}
// Service defines the available service configuration.
type Service struct {
Name string `yaml:"-"`
}
// Log defines the available log configuration.
type Log struct {
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;POLICIES_LOG_LEVEL" desc:"The log level. Valid values are: \"panic\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"."`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;POLICIES_LOG_PRETTY" desc:"Activates pretty log output."`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;POLICIES_LOG_COLOR" desc:"Activates colorized log output."`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;POLICIES_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set."`
}
// Debug defines the available debug configuration.
type Debug struct {
Addr string `yaml:"addr" env:"POLICIES_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
Token string `yaml:"token" env:"POLICIES_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint."`
Pprof bool `yaml:"pprof" env:"POLICIES_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling."`
Zpages bool `yaml:"zpages" env:"POLICIES_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces."`
}
// Events combines the configuration options for the event bus.
type Events struct {
Endpoint string `yaml:"endpoint" env:"USERLOG_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture."`
Cluster string `yaml:"cluster" env:"USERLOG_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."`
TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;USERLOG_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"USERLOG_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."`
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;USERLOG_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
}
// Scanner provides configuration options for the antivirusscanner
type Scanner struct {
Type string `yaml:"type" env:"ANTIVIRUS_SCANNER_TYPE" desc:"The scanner to use. Must be one of: clamav, icap"`
ClamAV ClamAV // only if Type == clamav
ICAP ICAP // only if Type == icap
}
// ClamAV provides configuration option for clamav
type ClamAV struct {
Socket string `yaml:"socket" env:"ANTIVIRUS_CLAMAV_SOCKET" desc:"The socket clamav is running on. Note the default value is an example which needs adaption according your OS."`
}
// ICAP provides configuration option for ICAP
type ICAP struct {
Timeout int64 `yaml:"timeout" env:"ANTIVIRUS_ICAP_TIMEOUT" desc:"Timeout for the ICAP client."`
URL string `yaml:"url" env:"ANTIVIRUS_ICAP_URL" desc:"URL of the ICAP server."`
Service string `yaml:"service" env:"ANTIVIRUS_ICAP_SERVICE" desc:"Name of the ICAP server."`
}

View File

@@ -0,0 +1,54 @@
package defaults
import (
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
)
// FullDefaultConfig returns a fully initialized default configuration which is needed for doc generation.
func FullDefaultConfig() *config.Config {
cfg := DefaultConfig()
EnsureDefaults(cfg)
Sanitize(cfg)
return cfg
}
// DefaultConfig returns the services default config
func DefaultConfig() *config.Config {
return &config.Config{
Debug: config.Debug{
Addr: "127.0.0.1:9277",
Token: "",
},
Service: config.Service{
Name: "antivirus",
},
Events: config.Events{
Endpoint: "127.0.0.1:9233",
Cluster: "ocis-cluster",
},
InfectedFileHandling: "delete",
Scanner: config.Scanner{
Type: "clamav",
ClamAV: config.ClamAV{
Socket: "/run/clamav/clamd.ctl",
},
ICAP: config.ICAP{
URL: "icap://127.0.0.1:1344",
Service: "avscan",
Timeout: 300,
},
},
}
}
// EnsureDefaults adds default values to the configuration if they are not set yet
func EnsureDefaults(cfg *config.Config) {
if cfg.Log == nil {
cfg.Log = &config.Log{}
}
}
// Sanitize sanitizes the configuration
func Sanitize(cfg *config.Config) {
}

View File

@@ -0,0 +1,38 @@
package parser
import (
"errors"
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config/defaults"
"github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode"
)
// ParseConfig loads configuration from known paths.
func ParseConfig(cfg *config.Config) error {
_, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
if err != nil {
return err
}
defaults.EnsureDefaults(cfg)
// load all env variables relevant to the config in the current context.
if err := envdecode.Decode(cfg); err != nil {
// no environment variable set for this config is an expected "error"
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
return err
}
}
defaults.Sanitize(cfg)
return Validate(cfg)
}
// Validate validates our little config
func Validate(cfg *config.Config) error {
return nil
}

View File

@@ -0,0 +1,35 @@
package scanners
import (
"io"
"time"
"github.com/dutchcoders/go-clamd"
)
// NewClamAV returns an Scanner talking to clamAV via socket
func NewClamAV(socket string) *ClamAV {
return &ClamAV{
clamd: clamd.NewClamd(socket),
}
}
// ClamAV is a Scanner based on clamav
type ClamAV struct {
clamd *clamd.Clamd
}
// Scan to fulfill Scanner interface
func (s ClamAV) Scan(file io.Reader) (ScanResult, error) {
ch, err := s.clamd.ScanStream(file, make(chan bool))
if err != nil {
return ScanResult{}, err
}
r := <-ch
return ScanResult{
Infected: r.Status == clamd.RES_FOUND,
Description: r.Description,
Scantime: time.Now(),
}, nil
}

View File

@@ -0,0 +1,68 @@
package scanners
import (
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"time"
ic "github.com/egirna/icap-client"
)
// NewICAP returns a Scanner talking to an ICAP server
func NewICAP(icapURL string, icapService string, timeout time.Duration) (ICAP, error) {
endpoint, err := url.Parse(icapURL)
if err != nil {
return ICAP{}, err
}
endpoint.Scheme = "icap"
endpoint.Path = icapService
return ICAP{
client: &ic.Client{
Timeout: timeout,
},
endpoint: endpoint.String(),
}, nil
}
// ICAP is a Scanner talking to an ICAP server
type ICAP struct {
client *ic.Client
endpoint string
}
// Scan to fulfill Scanner interface
func (s ICAP) Scan(file io.Reader) (ScanResult, error) {
sr := ScanResult{}
httpReq, err := http.NewRequest(http.MethodGet, "http://localhost", file)
if err != nil {
return sr, err
}
req, err := ic.NewRequest(ic.MethodREQMOD, s.endpoint, httpReq, nil)
if err != nil {
return sr, err
}
resp, err := s.client.Do(req)
if err != nil {
return sr, err
}
if data, infected := resp.Header["X-Infection-Found"]; infected {
sr.Infected = infected
re := regexp.MustCompile(`Threat=(.*);`)
match := re.FindStringSubmatch(fmt.Sprint(data))
if len(match) > 1 {
sr.Description = match[1]
}
}
return sr, nil
}

View File

@@ -0,0 +1,34 @@
package scanners
import (
"fmt"
"io"
"time"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
)
// ScanResult is the common scan result to all scanners
type ScanResult struct {
Infected bool
Scantime time.Time
Description string
}
// Scanner is an abstraction for the actual virus scan
type Scanner interface {
Scan(file io.Reader) (ScanResult, error)
}
// New returns a new scanner from config
func New(c config.Scanner) (Scanner, error) {
switch c.Type {
default:
return nil, fmt.Errorf("unknown av scanner: '%s'", c.Type)
case "clamav":
return NewClamAV(c.ClamAV.Socket), nil
case "icap":
return NewICAP(c.ICAP.URL, c.ICAP.Service, time.Duration(c.ICAP.Timeout)*time.Second)
}
}

View File

@@ -0,0 +1,205 @@
package service
import (
"bytes"
"context"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/cs3org/reva/v2/pkg/bytesize"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/stream"
"github.com/cs3org/reva/v2/pkg/rhttp"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/config"
"github.com/owncloud/ocis/v2/services/antivirus/pkg/scanners"
)
// Scanner is an abstraction for the actual virus scan
type Scanner interface {
Scan(file io.Reader) (scanners.ScanResult, error)
}
// NewAntivirus returns a service implementation for Service.
func NewAntivirus(c *config.Config, l log.Logger) (Antivirus, error) {
av := Antivirus{c: c, l: l, client: rhttp.GetHTTPClient(rhttp.Insecure(true))}
var err error
av.s, err = scanners.New(c.Scanner)
if err != nil {
return av, err
}
switch o := events.PostprocessingOutcome(c.InfectedFileHandling); o {
case events.PPOutcomeContinue, events.PPOutcomeAbort, events.PPOutcomeDelete:
av.o = o
default:
return av, fmt.Errorf("unknown infected file handling '%s'", o)
}
if c.MaxScanSize != "" {
b, err := bytesize.Parse(c.MaxScanSize)
if err != nil {
return av, err
}
av.m = b.Bytes()
}
return av, nil
}
// Antivirus defines implements the business logic for Service.
type Antivirus struct {
c *config.Config
l log.Logger
s Scanner
o events.PostprocessingOutcome
m uint64
client *http.Client
}
// Run runs the service
func (av Antivirus) Run() error {
evtsCfg := av.c.Events
var rootCAPool *x509.CertPool
if evtsCfg.TLSRootCACertificate != "" {
rootCrtFile, err := os.Open(evtsCfg.TLSRootCACertificate)
if err != nil {
return err
}
var certBytes bytes.Buffer
if _, err := io.Copy(&certBytes, rootCrtFile); err != nil {
return err
}
rootCAPool = x509.NewCertPool()
rootCAPool.AppendCertsFromPEM(certBytes.Bytes())
evtsCfg.TLSInsecure = false
}
stream, err := stream.NatsFromConfig(stream.NatsConfig(av.c.Events))
if err != nil {
return err
}
ch, err := events.Consume(stream, "antivirus", events.StartPostprocessingStep{})
if err != nil {
return err
}
for e := range ch {
ev := e.Event.(events.StartPostprocessingStep)
if ev.StepToStart != events.PPStepAntivirus {
continue
}
var errmsg string
res, err := av.process(ev)
if err != nil {
errmsg = err.Error()
}
outcome := events.PPOutcomeContinue
if res.Infected {
outcome = av.o
}
av.l.Info().Str("uploadid", ev.UploadID).Interface("resourceID", ev.ResourceID).Str("virus", res.Description).Str("outcome", string(outcome)).Str("filename", ev.Filename).Str("user", ev.ExecutingUser.GetId().GetOpaqueId()).Bool("infected", res.Infected).Msg("File scanned")
if err := events.Publish(stream, events.PostprocessingStepFinished{
Outcome: outcome,
UploadID: ev.UploadID,
ExecutingUser: ev.ExecutingUser,
Filename: ev.Filename,
Result: events.VirusscanResult{
Infected: res.Infected,
Description: res.Description,
Scandate: time.Now(),
ResourceID: ev.ResourceID,
ErrorMsg: errmsg,
},
}); err != nil {
av.l.Fatal().Err(err).Str("uploadid", ev.UploadID).Interface("resourceID", ev.ResourceID).Msg("cannot publish events - exiting")
return err
}
}
return nil
}
// process the scan
func (av Antivirus) process(ev events.StartPostprocessingStep) (scanners.ScanResult, error) {
if ev.Filesize == 0 || (0 < av.m && av.m < ev.Filesize) {
return scanners.ScanResult{
Scantime: time.Now(),
}, nil
}
var err error
var rrc io.ReadCloser
switch ev.UploadID {
default:
rrc, err = av.downloadViaToken(ev.URL)
case "":
rrc, err = av.downloadViaReva(ev.URL, ev.Token, ev.RevaToken)
}
if err != nil {
av.l.Error().Err(err).Str("uploadid", ev.UploadID).Msg("error downloading file")
return scanners.ScanResult{}, err
}
defer rrc.Close()
res, err := av.s.Scan(rrc)
if err != nil {
av.l.Error().Err(err).Str("uploadid", ev.UploadID).Msg("error scanning file")
}
return res, err
}
// download will download the file
func (av Antivirus) downloadViaToken(url string) (io.ReadCloser, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return av.doDownload(req)
}
// download will download the file
func (av Antivirus) downloadViaReva(url string, dltoken string, revatoken string) (io.ReadCloser, error) {
ctx := ctxpkg.ContextSetToken(context.Background(), revatoken)
req, err := rhttp.NewRequest(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("X-Reva-Transfer", dltoken)
return av.doDownload(req)
}
func (av Antivirus) doDownload(req *http.Request) (io.ReadCloser, error) {
res, err := av.client.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
res.Body.Close()
return nil, fmt.Errorf("unexpected status code from Download %v", res.StatusCode)
}
return res.Body, nil
}

View File

@@ -2,6 +2,7 @@ package eventSVC
import (
"context"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/policies/pkg/engine"
@@ -37,7 +38,7 @@ func (s Service) Run() error {
for e := range ch {
switch ev := e.Event.(type) {
case events.StartPostprocessingStep:
if ev.StepToStart != "policies" {
if ev.StepToStart != events.PPStepPolicies {
continue
}

View File

@@ -21,7 +21,6 @@ func NewPostprocessingService(stream events.Stream, logger log.Logger, c config.
evs, err := events.Consume(stream, "postprocessing",
events.BytesReceived{},
events.StartPostprocessingStep{},
events.VirusscanFinished{},
events.UploadReady{},
events.PostprocessingStepFinished{},
)