diff --git a/.license_scout.yml b/.license_scout.yml index 0290808f1b6..790957ac7a5 100644 --- a/.license_scout.yml +++ b/.license_scout.yml @@ -99,6 +99,7 @@ allowed_licenses: - w32-Authors - WTFPL - Zlib + - GPL-2.0 fallbacks: golang: diff --git a/components/compliance-service/api/jobs/server/server.go b/components/compliance-service/api/jobs/server/server.go index f15e87a891c..2ccdd9979f7 100644 --- a/components/compliance-service/api/jobs/server/server.go +++ b/components/compliance-service/api/jobs/server/server.go @@ -40,13 +40,13 @@ var empty = pb.Empty{} // New creates a new jobs server func New(db *pgdb.DB, connFactory *secureconn.Factory, eventsClient automate_event.EventServiceClient, - managerEndpoint string, cerealManager *cereal.Manager) *Server { + managerEndpoint string, cerealManager *cereal.Manager, fireJailExecProfilePath string) *Server { conf := &Server{ db: db, connFactory: connFactory, eventsClient: eventsClient, } - conf.getComplianceAndSecretsConnection(connFactory, db, managerEndpoint, cerealManager) + conf.getComplianceAndSecretsConnection(connFactory, db, managerEndpoint, cerealManager, fireJailExecProfilePath) return conf } @@ -54,7 +54,7 @@ func New(db *pgdb.DB, connFactory *secureconn.Factory, eventsClient automate_eve // the scheduler server is used to call the inspec-agent func (srv *Server) getComplianceAndSecretsConnection( connectionFactory *secureconn.Factory, db *pgdb.DB, - managerEndpoint string, cerealManager *cereal.Manager) { + managerEndpoint string, cerealManager *cereal.Manager, fireJailExecProfilePath string) { if managerEndpoint == "" { logrus.Errorf("complianceEndpoint and managerEndpoint cannot be empty or Dial will get stuck") return @@ -78,7 +78,7 @@ func (srv *Server) getComplianceAndSecretsConnection( return } - scanner := scanner.New(mgrClient, nodesClient, db) + scanner := scanner.New(mgrClient, nodesClient, db, fireJailExecProfilePath) srv.schedulerServer = scheduler.New(scanner, cerealManager) } diff --git a/components/compliance-service/api/profiles/server/pgserver.go b/components/compliance-service/api/profiles/server/pgserver.go index 01dbe2173ef..c2f30d995fb 100644 --- a/components/compliance-service/api/profiles/server/pgserver.go +++ b/components/compliance-service/api/profiles/server/pgserver.go @@ -29,11 +29,13 @@ import ( // PGProfileServer implements the profile store GRPC interface type PGProfileServer struct { - es *relaxting.ES2Backend - esClient *ingestic.ESClient - profiles *config.Profiles - store *dbstore.Store - eventsClient automate_event.EventServiceClient + es *relaxting.ES2Backend + esClient *ingestic.ESClient + profiles *config.Profiles + store *dbstore.Store + eventsClient automate_event.EventServiceClient + firejailProfilePath string + fireJailExecProfilePath string } func (srv *PGProfileServer) convertProfileToTgz(reader io.ReadCloser, contentType string) (string, error) { @@ -69,7 +71,7 @@ func (srv *PGProfileServer) convertProfileToTgz(reader io.ReadCloser, contentTyp return "", err } - err = util.ConvertZipToTarGz(tmpZipUpload, tmpWithSuffix) + err = util.ConvertZipToTarGz(tmpZipUpload, tmpWithSuffix, srv.firejailProfilePath) if err != nil { return "", err } @@ -80,7 +82,7 @@ func (srv *PGProfileServer) convertProfileToTgz(reader io.ReadCloser, contentTyp func (srv *PGProfileServer) storeProfile(owner string, cacheFile string) (inspec.CheckResult, error) { var inspecCheckResult inspec.CheckResult // Run InSpec check - inspecCheckResult, err := market.CheckProfile(cacheFile) + inspecCheckResult, err := market.CheckProfile(cacheFile, srv.firejailProfilePath) if err != nil { logrus.Errorf("Create CheckProfile error: %s", err.Error()) inspecCheckResult.Summary.Valid = false @@ -88,7 +90,7 @@ func (srv *PGProfileServer) storeProfile(owner string, cacheFile string) (inspec return inspecCheckResult, status.Error(codes.InvalidArgument, err.Error()) } - sha256, tar, info, err := srv.store.GetProfileInfo(cacheFile) + sha256, tar, info, err := srv.store.GetProfileInfo(cacheFile, srv.firejailProfilePath) if err != nil { logrus.Errorf("Create GetProfileInfo error: %s", err.Error()) inspecCheckResult.Summary.Valid = false diff --git a/components/compliance-service/api/profiles/server/profiles.go b/components/compliance-service/api/profiles/server/profiles.go index 8aa0baba131..c00fd3bdb61 100644 --- a/components/compliance-service/api/profiles/server/profiles.go +++ b/components/compliance-service/api/profiles/server/profiles.go @@ -14,21 +14,23 @@ import ( // New creates a new server func New(db *pgdb.DB, esBackend *relaxting.ES2Backend, esClient *ingestic.ESClient, profiles *config.Profiles, - eventsClient automate_event.EventServiceClient, statusSrv *statusserver.Server) *PGProfileServer { + eventsClient automate_event.EventServiceClient, statusSrv *statusserver.Server, firejailProfilePath string, fireJailExecProfilePath string) *PGProfileServer { srv := &PGProfileServer{ - profiles: profiles, - es: esBackend, - esClient: esClient, - store: &dbstore.Store{DB: db}, - eventsClient: eventsClient, + profiles: profiles, + es: esBackend, + esClient: esClient, + store: &dbstore.Store{DB: db}, + eventsClient: eventsClient, + firejailProfilePath: firejailProfilePath, + fireJailExecProfilePath: fireJailExecProfilePath, } // TODO: unbundle object creation from service bootup sanity check statusserver.AddMigrationUpdate(statusSrv, statusserver.MigrationLabelPRO, "Ensuring Market profiles are up-to-date...") // ensure all market profiles are up to date - err := srv.store.LoadMarketProfiles(profiles.MarketPath) + err := srv.store.LoadMarketProfiles(profiles.MarketPath, firejailProfilePath) if err != nil { logrus.Errorf("could not ensure all market profiles are up to date: %v", err) } diff --git a/components/compliance-service/cmd/compliance-service/cmd/run.go b/components/compliance-service/cmd/compliance-service/cmd/run.go index f3d7433e779..c965fa3ce29 100644 --- a/components/compliance-service/cmd/compliance-service/cmd/run.go +++ b/components/compliance-service/cmd/compliance-service/cmd/run.go @@ -111,7 +111,8 @@ func init() { runCmd.Flags().IntVar(&conf.Service.LcrOpenSearchRequests, "lcr-open-search-requests", conf.Service.LcrOpenSearchRequests, "number of concurrent requests to communicate with open search for large compliance reporting") runCmd.Flags().BoolVar(&conf.Service.EnableEnhancedReporting, "enable-enhanced-reporting", false, "upgrade to support enhanced compliance reporting") runCmd.Flags().IntVar(&conf.Service.ControlsPopulatorsCount, "control-populators-count", 1, "Number of workers for control workers") - + runCmd.Flags().StringVar(&conf.Service.FirejailProfilePath, "firejail-profile-path", conf.Service.FirejailProfilePath, "Firejail profile path") + runCmd.Flags().StringVar(&conf.Service.FireJailExecProfilePath, "firejail-exec-profile-path", conf.Service.FireJailExecProfilePath, "Firejail profile path for exec") // Postgres Config Flags runCmd.Flags().StringVar(&conf.Postgres.ConnectionString, "postgres-uri", conf.Postgres.ConnectionString, "PostgreSQL connection string to use") runCmd.Flags().StringVar(&conf.Postgres.Database, "postgres-database", "", "PostgreSQL database to use. Will override postgres-uri") diff --git a/components/compliance-service/compliance.go b/components/compliance-service/compliance.go index 759bee4fea1..6a427ab9650 100644 --- a/components/compliance-service/compliance.go +++ b/components/compliance-service/compliance.go @@ -231,11 +231,11 @@ func serveGrpc(ctx context.Context, db *pgdb.DB, connFactory *secureconn.Factory conf.Service.MessageBufferSize, conf.Service.EnableLargeReporting, cerealManager)) jobs.RegisterJobsServiceServer(s, jobsserver.New(db, connFactory, eventClient, - conf.Manager.Endpoint, cerealManager)) + conf.Manager.Endpoint, cerealManager, conf.Service.FireJailExecProfilePath)) reporting.RegisterReportingServiceServer(s, reportingserver.New(&esr, reportmanagerClient, conf.Service.LcrOpenSearchRequests, db, conf.Service.EnableEnhancedReporting)) - ps := profilesserver.New(db, &esr, ingesticESClient, &conf.Profiles, eventClient, statusSrv) + ps := profilesserver.New(db, &esr, ingesticESClient, &conf.Profiles, eventClient, statusSrv, conf.Service.FirejailProfilePath, conf.Service.FireJailExecProfilePath) profiles.RegisterProfilesServiceServer(s, ps) profiles.RegisterProfilesAdminServiceServer(s, ps) @@ -602,8 +602,8 @@ func setup(ctx context.Context, connFactory *secureconn.Factory, conf config.Com // set up the scanner, scheduler, and runner servers with needed clients // these are all inspec-agent packages - scanner := scanner.New(mgrClient, nodesClient, db) - resolver := resolver.New(mgrClient, nodesClient, db, secretsClient) + scanner := scanner.New(mgrClient, nodesClient, db, conf.FireJailExecProfilePath) + resolver := resolver.New(mgrClient, nodesClient, db, secretsClient, conf.FireJailExecProfilePath) err = runner.InitCerealManager(cerealManager, conf.InspecAgent.JobWorkers, ingestClient, scanner, resolver, conf.RemoteInspecVersion) if err != nil { @@ -703,7 +703,7 @@ type ServiceInfo struct { connFactory *secureconn.Factory } -//TODO(jaym) If these don't get exposed in the gateway, we need to provide the http server certs +// TODO(jaym) If these don't get exposed in the gateway, we need to provide the http server certs // this custom route is used by the inspec-agent scanner to retrieve profile tars for scan execution func (conf *ServiceInfo) serveCustomRoutes() error { conf.ServerBind = fmt.Sprintf("%s:%d", conf.HostBind, conf.Port) diff --git a/components/compliance-service/config/types.go b/components/compliance-service/config/types.go index b876c30f3ab..529ea1fecec 100644 --- a/components/compliance-service/config/types.go +++ b/components/compliance-service/config/types.go @@ -19,6 +19,8 @@ type Service struct { LcrOpenSearchRequests int EnableEnhancedReporting bool ControlsPopulatorsCount int + FirejailProfilePath string + FireJailExecProfilePath string } // Compliance service specific config options diff --git a/components/compliance-service/firejail/secureexecprofile.profile b/components/compliance-service/firejail/secureexecprofile.profile new file mode 100644 index 00000000000..2847434f04e --- /dev/null +++ b/components/compliance-service/firejail/secureexecprofile.profile @@ -0,0 +1,90 @@ +include disable-common.inc # dangerous directories like ~/.ssh and ~/.gnupg +#include disable-devel.inc # development tools such as gcc and gdb +#include disable-exec.inc # non-executable directories such as /var, /tmp, and /home +#include disable-interpreters.inc # perl, python, lua etc. +include disable-programs.inc # user configuration for programs such as firefox, vlc etc. +#include disable-shell.inc # sh, bash, zsh etc. +#include disable-xdg.inc # standard user directories: Documents, Pictures, Videos, Music + +### Home Directory Whitelisting ### +### If something goes wrong, this section is the first one to comment out. +### Instead, you'll have to relay on the basic blacklisting above. +#private +blacklist /hab/cache +blacklist /hab/etc +blacklist /hab/svc +blacklist /hab/launcher +blacklist /hab/user +blacklist /hab/studios +blacklist /hab/sup +blacklist /hab/pkgs/chef/applications-service +blacklist /hab/pkgs/chef/automate-dex +blacklist /hab/pkgs/chef/automate-opensearch +blacklist /hab/pkgs/chef/backup-gateway +blacklist /hab/pkgs/chef/deployment-service +blacklist /hab/pkgs/chef/infra-proxy-service +blacklist /hab/pkgs/chef/local-user-service +blacklist /hab/pkgs/chef/report-manager-service +blacklist /hab/pkgs/chef/authn-service +blacklist /hab/pkgs/chef/automate-es-gateway +blacklist /hab/pkgs/chef/automate-pg-gateway +blacklist /hab/pkgs/chef/cereal-service +blacklist /hab/pkgs/chef/es-sidecar-service +blacklist /hab/pkgs/chef/ingest-service +blacklist /hab/pkgs/chef/mlsa +blacklist /hab/pkgs/chef/secrets-service +blacklist /hab/pkgs/chef/authz-service +blacklist /hab/pkgs/chef/automate-gateway +blacklist /hab/pkgs/chef/automate-platform-tools +blacklist /hab/pkgs/chef/compliance-service +blacklist /hab/pkgs/chef/event-feed-service +blacklist /hab/pkgs/chef/nodemanager-service +blacklist /hab/pkgs/chef/session-service +blacklist /hab/pkgs/chef/automate-cli +blacklist /hab/pkgs/chef/automate-load-balancer +blacklist /hab/pkgs/chef/automate-postgresql +blacklist /hab/pkgs/chef/config-mgmt-service +blacklist /hab/pkgs/chef/event-gateway +blacklist /hab/pkgs/chef/license-audit +blacklist /hab/pkgs/chef/notifications-service +blacklist /hab/pkgs/chef/teams-service +blacklist /hab/pkgs/chef/automate-compliance-profiles +blacklist /hab/pkgs/chef/automate-openjdk +blacklist /hab/pkgs/chef/automate-ui +blacklist /hab/pkgs/chef/data-feed-service +blacklist /hab/pkgs/chef/event-service +blacklist /hab/pkgs/chef/license-control-service +blacklist /hab/pkgs/chef/pg-sidecar-service +blacklist /hab/pkgs/chef/user-settings-service +read-only /hab/pkgs/chef/inspec + +### Filesystem Whitelisting ### +include whitelist-run-common.inc +include whitelist-runuser-common.inc +include whitelist-usr-share-common.inc +include whitelist-var-common.inc + +#apparmor # if you have AppArmor running, try this one! +caps.drop all +ipc-namespace +#netfilter +#no3d # disable 3D acceleration +#nodvd # disable DVD and CD devices +#nogroups # disable supplementary user groups +#noinput # disable input devices +nonewprivs +noroot +#notv # disable DVB TV devices +#nou2f # disable U2F devices +#novideo # disable video capture devices +##net none +#ip 127.0.0.1 +protocol unix,inet,inet6,netlink +#seccomp !chroot # allowing chroot, just in case this is an Electron app +#shell none +#tracelog # send blacklist violations to syslog + +#disable-mnt # no access to /mnt, /media, /run/mount and /run/media +#private-bin dash, hab +#private-cache # run with an +#read-only /hab \ No newline at end of file diff --git a/components/compliance-service/firejail/secureprofile.profile b/components/compliance-service/firejail/secureprofile.profile new file mode 100644 index 00000000000..7986b30fdee --- /dev/null +++ b/components/compliance-service/firejail/secureprofile.profile @@ -0,0 +1,88 @@ +include disable-common.inc # dangerous directories like ~/.ssh and ~/.gnupg +#include disable-devel.inc # development tools such as gcc and gdb +#include disable-exec.inc # non-executable directories such as /var, /tmp, and /home +#include disable-interpreters.inc # perl, python, lua etc. +include disable-programs.inc # user configuration for programs such as firefox, vlc etc. +#include disable-shell.inc # sh, bash, zsh etc. +#include disable-xdg.inc # standard user directories: Documents, Pictures, Videos, Music + +### Home Directory Whitelisting ### +### If something goes wrong, this section is the first one to comment out. +### Instead, you'll have to relay on the basic blacklisting above. +#private +blacklist /hab/cache +blacklist /hab/etc +blacklist /hab/svc +blacklist /hab/launcher +blacklist /hab/user +blacklist /hab/studios +blacklist /hab/sup +blacklist /hab/pkgs/chef/applications-service +blacklist /hab/pkgs/chef/automate-dex +blacklist /hab/pkgs/chef/automate-opensearch +blacklist /hab/pkgs/chef/backup-gateway +blacklist /hab/pkgs/chef/deployment-service +blacklist /hab/pkgs/chef/infra-proxy-service +blacklist /hab/pkgs/chef/local-user-service +blacklist /hab/pkgs/chef/report-manager-service +blacklist /hab/pkgs/chef/authn-service +blacklist /hab/pkgs/chef/automate-es-gateway +blacklist /hab/pkgs/chef/automate-pg-gateway +blacklist /hab/pkgs/chef/cereal-service +blacklist /hab/pkgs/chef/es-sidecar-service +blacklist /hab/pkgs/chef/ingest-service +blacklist /hab/pkgs/chef/mlsa +blacklist /hab/pkgs/chef/secrets-service +blacklist /hab/pkgs/chef/authz-service +blacklist /hab/pkgs/chef/automate-gateway +blacklist /hab/pkgs/chef/automate-platform-tools +blacklist /hab/pkgs/chef/compliance-service +blacklist /hab/pkgs/chef/event-feed-service +blacklist /hab/pkgs/chef/nodemanager-service +blacklist /hab/pkgs/chef/session-service +blacklist /hab/pkgs/chef/automate-cli +blacklist /hab/pkgs/chef/automate-load-balancer +blacklist /hab/pkgs/chef/automate-postgresql +blacklist /hab/pkgs/chef/config-mgmt-service +blacklist /hab/pkgs/chef/event-gateway +blacklist /hab/pkgs/chef/license-audit +blacklist /hab/pkgs/chef/notifications-service +blacklist /hab/pkgs/chef/teams-service +blacklist /hab/pkgs/chef/automate-compliance-profiles +blacklist /hab/pkgs/chef/automate-openjdk +blacklist /hab/pkgs/chef/automate-ui +blacklist /hab/pkgs/chef/data-feed-service +blacklist /hab/pkgs/chef/event-service +blacklist /hab/pkgs/chef/license-control-service +blacklist /hab/pkgs/chef/pg-sidecar-service +blacklist /hab/pkgs/chef/user-settings-service +read-only /hab/pkgs/chef/inspec + +### Filesystem Whitelisting ### +include whitelist-run-common.inc +include whitelist-runuser-common.inc +include whitelist-usr-share-common.inc +include whitelist-var-common.inc + +#apparmor # if you have AppArmor running, try this one! +caps.drop all +ipc-namespace +netfilter +#no3d # disable 3D acceleration +#nodvd # disable DVD and CD devices +#nogroups # disable supplementary user groups +#noinput # disable input devices +nonewprivs +noroot +#notv # disable DVB TV devices +#nou2f # disable U2F devices +#novideo # disable video capture devices +net none +#seccomp !chroot # allowing chroot, just in case this is an Electron app +#shell none +#tracelog # send blacklist violations to syslog + +#disable-mnt # no access to /mnt, /media, /run/mount and /run/media +#private-bin dash, hab +#private-cache # run with an +#read-only /hab \ No newline at end of file diff --git a/components/compliance-service/habitat/default.toml b/components/compliance-service/habitat/default.toml index 6bf221db0f3..d126a0b3e91 100644 --- a/components/compliance-service/habitat/default.toml +++ b/components/compliance-service/habitat/default.toml @@ -13,6 +13,8 @@ enable_large_reporting = false lcr_open_search_requests = 50 enable_enhanced_compliance_reporting = false control_data_populators_count = 1 +firejail_profile_path="secureprofile.profile" +firejail_exec_profile_path="secureexecprofile.profile" [storage] database = "chef_compliance_service" diff --git a/components/compliance-service/habitat/hooks/run b/components/compliance-service/habitat/hooks/run index 3b8ef10b249..8de1f2ac86c 100644 --- a/components/compliance-service/habitat/hooks/run +++ b/components/compliance-service/habitat/hooks/run @@ -25,6 +25,8 @@ pg-helper migrate-tables-v2 delivery "$DBNAME" \ agents node_managers results profiles tags jobs jobs_nodes jobs_profiles \ jobs_tags nodes nodes_agents nodes_secrets nodes_tags + + pg-helper ensure-service-database "$DBNAME" pg-helper create-extension "$DBNAME" pgcrypto @@ -57,6 +59,8 @@ CONFIG="$CONFIG --enable-large-reporting={{cfg.service.enable_large_reporting}}" CONFIG="$CONFIG --lcr-open-search-requests {{cfg.service.lcr_open_search_requests}}" CONFIG="$CONFIG --enable-enhanced-reporting={{cfg.service.enable_enhanced_compliance_reporting}}" CONFIG="$CONFIG --control-populators-count {{cfg.service.control_data_populators_count}}" +CONFIG="$CONFIG --firejail-profile-path {{pkg.path}}/data/firejail/{{cfg.service.firejail_profile_path}}" +CONFIG="$CONFIG --firejail-exec-profile-path {{pkg.path}}/data/firejail/{{cfg.service.firejail_exec_profile_path}}" # Interval in minutes to poll for node status. CONFIG="$CONFIG --manager-awsec2-poll {{cfg.nodemanager.awsec2_polling_interval}}" @@ -167,6 +171,10 @@ export HOME="{{pkg.svc_data_path}}" CONFIG="$CONFIG --inspec-tmp-dir {{pkg.svc_var_path}}/tmp" + +export FIREJAIL="{{pkgPathFor "core/firejail"}}/bin/firejail" + + # Start our service # shellcheck disable=SC2086 exec compliance-service run ${CONFIG} ${ES_BACKEND} ${PG_BACKEND} diff --git a/components/compliance-service/habitat/plan.sh b/components/compliance-service/habitat/plan.sh index 64dff70a275..81b5115510d 100644 --- a/components/compliance-service/habitat/plan.sh +++ b/components/compliance-service/habitat/plan.sh @@ -30,6 +30,7 @@ pkg_binds_optional=( [authn-service]="port" [notifications-service]="port" ) + inspec_release="chef/inspec/4.56.22/20220517052126" pkg_deps=( core/coreutils @@ -39,6 +40,7 @@ pkg_deps=( core/grpcurl # Used in habitat/hooks/health_check core/jq-static # Used in habitat/hooks/health_check core/bash + core/firejail ) if [[ -n "$AUTOMATE_OSS_BUILD" ]]; then @@ -62,14 +64,17 @@ scaffolding_go_binary_list=( do_prepare() { do_default_prepare - + GO_LDFLAGS="${GO_LDFLAGS} -X main.EXECUTABLE_PATH=$(pkg_path_for chef/inspec)/bin/inspec" - export GO_LDFLAGS + export GO_LDFLAGS + } do_install() { do_default_install + echo $HOME + inspec_sem_version=$(awk -F '/' '{print $3}' <<< ${inspec_release}) build_line "Setting InSpec version ${inspec_sem_version}" sed -i "s/REPLACE-FROM-PLAN.SH/${inspec_sem_version}/" habitat/default.toml @@ -81,6 +86,14 @@ do_install() { build_line "Setting perms on inspec_runner" chown root: "${pkg_prefix}/bin/inspec_runner" chmod u+s "${pkg_prefix}/bin/inspec_runner" + + + mkdir -p "${pkg_prefix}/data/firejail" + + cp -r firejail/* "${pkg_prefix}/data/firejail" + + + } do_strip() { diff --git a/components/compliance-service/inspec-agent/resolver/resolver.go b/components/compliance-service/inspec-agent/resolver/resolver.go index 537e6c16a65..73e90241bb8 100644 --- a/components/compliance-service/inspec-agent/resolver/resolver.go +++ b/components/compliance-service/inspec-agent/resolver/resolver.go @@ -34,8 +34,8 @@ type Resolver struct { secretsClient secrets.SecretsServiceClient } -func New(managerClient manager.NodeManagerServiceClient, nodesClient nodes.NodesServiceClient, db *pgdb.DB, secretsClient secrets.SecretsServiceClient) *Resolver { - scannerServer := scanner.New(managerClient, nodesClient, db) +func New(managerClient manager.NodeManagerServiceClient, nodesClient nodes.NodesServiceClient, db *pgdb.DB, secretsClient secrets.SecretsServiceClient, fireJailProfilePath string) *Resolver { + scannerServer := scanner.New(managerClient, nodesClient, db, fireJailProfilePath) return &Resolver{managerClient, nodesClient, db, scannerServer, secretsClient} } diff --git a/components/compliance-service/inspec-agent/runner/runner.go b/components/compliance-service/inspec-agent/runner/runner.go index d46dcf4806d..ffbe1bf10c4 100644 --- a/components/compliance-service/inspec-agent/runner/runner.go +++ b/components/compliance-service/inspec-agent/runner/runner.go @@ -420,6 +420,7 @@ func (t *InspecJobTask) Run(ctx context.Context, task cereal.Task) (interface{}, job.NodeStatus = types.StatusCompleted case types.JobTypeExec: // call out to do the ssm job + job.FireJailExecProfilePath = t.scannerServer.FireJailExecProfilePath jobInfo.InspecErr = remote.RunSSMJob(ctx, &job) } } else if nodeHasSecrets(&job.TargetConfig) { @@ -427,6 +428,7 @@ func (t *InspecJobTask) Run(ctx context.Context, task cereal.Task) (interface{}, case types.JobTypeDetect: jobInfo.DetectInfo, jobInfo.InspecErr = doDetect(&job) case types.JobTypeExec: + job.FireJailExecProfilePath = t.scannerServer.FireJailExecProfilePath jobInfo.ExecInfo, jobInfo.InspecErr = doExec(&job) } } else { @@ -806,7 +808,7 @@ func doExec(job *types.InspecJob) (jsonBytes []byte, err *inspec.Error) { } for i, tc := range potentialTargetConfigs(job) { - jsonBytes, _, err = inspec.Scan(job.InternalProfiles, &tc, timeout, env, inputs) + jsonBytes, _, err = inspec.Scan(job.InternalProfiles, &tc, timeout, env, inputs, job.FireJailExecProfilePath) if err == nil { break } diff --git a/components/compliance-service/inspec-agent/types/types.go b/components/compliance-service/inspec-agent/types/types.go index dd9b9963ba7..a4ea4385ba5 100644 --- a/components/compliance-service/inspec-agent/types/types.go +++ b/components/compliance-service/inspec-agent/types/types.go @@ -53,26 +53,27 @@ type NodeStatus struct { // Timeout is the number of seconds until a timeout is issued and the job is aborted. type InspecJob struct { InspecBaseJob - Profiles []string `json:"profiles,omitempty"` - Timeout int32 `json:"timeout,omitempty"` - Retries int32 `json:"retries"` - RetriesLeft int32 `json:"retries_left"` - StartTime *time.Time `json:"start_time,omitempty"` - EndTime *time.Time `json:"end_time,omitempty"` - TargetConfig inspec.TargetConfig `json:"target_config,omitempty"` - NodeStatus string `json:"node_status,omitempty"` - SourceID string `json:"source_id,omitempty"` - SourceAccountID string `json:"account_id,omitempty"` - SSM bool `json:"ssm,omitempty"` - RemoteInspecVersion string `json:"remote_inspec_version,omitempty"` - Reporter inspec.Reporter `json:"reporter,omitempty"` - ProfilesOwner string `json:"profiles_owner,omitempty"` - InternalProfiles []string `json:"internal_profiles,omitempty"` - MachineIdentifier string `json:"machine_identifier,omitempty"` - Tags []*common.Kv `json:"kv,omitempty"` - ParentJobID string `json:"parent_job_id,omitempty"` - ManagerID string `json:"manager_id,omitempty"` - ManagerType string `json:"manager_type,omitempty"` + Profiles []string `json:"profiles,omitempty"` + Timeout int32 `json:"timeout,omitempty"` + Retries int32 `json:"retries"` + RetriesLeft int32 `json:"retries_left"` + StartTime *time.Time `json:"start_time,omitempty"` + EndTime *time.Time `json:"end_time,omitempty"` + TargetConfig inspec.TargetConfig `json:"target_config,omitempty"` + NodeStatus string `json:"node_status,omitempty"` + SourceID string `json:"source_id,omitempty"` + SourceAccountID string `json:"account_id,omitempty"` + SSM bool `json:"ssm,omitempty"` + RemoteInspecVersion string `json:"remote_inspec_version,omitempty"` + Reporter inspec.Reporter `json:"reporter,omitempty"` + ProfilesOwner string `json:"profiles_owner,omitempty"` + InternalProfiles []string `json:"internal_profiles,omitempty"` + MachineIdentifier string `json:"machine_identifier,omitempty"` + Tags []*common.Kv `json:"kv,omitempty"` + ParentJobID string `json:"parent_job_id,omitempty"` + ManagerID string `json:"manager_id,omitempty"` + ManagerType string `json:"manager_type,omitempty"` + FireJailExecProfilePath string } // WorkerStats describe the state of all inspec job workers to be used for analysis purposes diff --git a/components/compliance-service/inspec/cli.go b/components/compliance-service/inspec/cli.go index 5f2753b6a90..2e5c52964f1 100644 --- a/components/compliance-service/inspec/cli.go +++ b/components/compliance-service/inspec/cli.go @@ -8,9 +8,12 @@ import ( "io/ioutil" "os" "os/exec" + "path" + "path/filepath" "strings" "time" + "github.com/chef/automate/lib/io/fileutils" "github.com/pkg/errors" "github.com/sirupsen/logrus" yaml "gopkg.in/yaml.v2" @@ -25,11 +28,17 @@ var ResultMessageLimit int // TmpDir is used for setting the location of the /tmp dir to be used by inspec for caching var TmpDir string +var FIREJAIL string + // The timeout used for tasks that just evaluate a profile but do not execute it. const defaultTimeout = 2 * time.Minute const binName = "inspec" const shimBinName = "inspec_runner" +const json_command = "json" +const check_command = "check" +const archive_command = "archive" +const exec_command = "exec" // Set to `true` to emit inspec configuration and environment // variables via debug logs. Leave to false for release. @@ -43,6 +52,13 @@ func inspecShimEnv() map[string]string { } } +func errorStringValues() []string { + return []string{ + "Permission denied", + "Could not resolve host", + } +} + func isTimeoutSane(timeout time.Duration, max time.Duration) error { if timeout < 1*time.Second { return errors.New("Timeout for InSpec CLI should never be less than 1 second. It is probably misconfigured.") @@ -66,7 +82,7 @@ func writeInputsToYmlFile(inputs map[string]string, filename string) error { } // Scan a target node with all specified profiles -func Scan(paths []string, target *TargetConfig, timeout time.Duration, env map[string]string, inputs map[string]string) ([]byte, []byte, *Error) { +func Scan(paths []string, target *TargetConfig, timeout time.Duration, env map[string]string, inputs map[string]string, fireJailExecProfilePath string) ([]byte, []byte, *Error) { if err := isTimeoutSane(timeout, 12*time.Hour); err != nil { return nil, nil, NewInspecError(INVALID_PARAM, err.Error()) } @@ -78,22 +94,41 @@ func Scan(paths []string, target *TargetConfig, timeout time.Duration, env map[s target.ResultIncludeBacktrace = false target.ResultMessageLimit = ResultMessageLimit - env["HOME"] = os.Getenv("HOME") + //env["HOME"] = os.Getenv("HOME") - args := append([]string{binName, "exec"}, paths...) + //args := append([]string{binName, "exec"}, paths...) // write the inputs to a file to be passed to inspec during command execution + tmpDirPath := fmt.Sprintf("/tmp/inspec-upload-%v", makeTimestamp()) + args, err := getFirejailArgsaAndOutputFileForExec(fireJailExecProfilePath, paths, tmpDirPath) + if err != nil { + return nil, nil, NewInspecError(INVALID_PARAM, err.Error()) + } + + tmpKeys := copyKeyFilesIntoTmpDirectory(tmpDirPath, target.KeyFiles) + newTarget := target + newTarget.KeyFiles = tmpKeys + stdoutFile, erroutFile, shellFile := shellscriptAndResponse(exec_command, tmpDirPath) + commandArgs := args + commandArgs = append(commandArgs, []string{"/bin/sh", shellFile}...) + commandArgs = append(commandArgs, paths...) if len(inputs) > 0 { - filename := "/tmp/inputs.yml" + filename := path.Join(tmpDirPath, "inputs.yml") err := writeInputsToYmlFile(inputs, filename) if err != nil { errString := fmt.Sprintf("unable to write inputs to file for scan job %s : %s", target.Hostname, err.Error()) - return nil, nil, NewInspecError(INVALID_PARAM, errString) + return nil, nil, NewInspecError(UNKNOWN_ERROR, errString) } args = append(args, fmt.Sprintf("--input-file %s", filename)) } + //Changing the home directory to tmp directory created + env["HOME"] = tmpDirPath - stdOut, stdErr, err := run(args, target, timeout, env) + logrus.Debugf("Run: inspec %v", args) + _, _, err = run(commandArgs, newTarget, timeout, env) + stdOut := readFile(stdoutFile) + stdErr := readFile(erroutFile) + os.RemoveAll(tmpDirPath) stdOutErr := "" if len(stdOut) == 0 { stdOutErr = "Empty STDOUT, we have a problem..." @@ -238,22 +273,43 @@ func run(args []string, conf *TargetConfig, timeout time.Duration, env map[strin return stdout.Bytes(), stderr.Bytes(), err } -func Check(profilePath string) (CheckResult, error) { +func Check(profilePath string, firejailprofilePath string) (CheckResult, error) { var res CheckResult + tmpDirPath := fmt.Sprintf("/tmp/inspec-upload-%v", makeTimestamp()) + tmpDirFile, args, err := getFirejailArgsaAndOutputFile(false, firejailprofilePath, profilePath, tmpDirPath) + if err != nil { + return res, err + } - args := []string{shimBinName, "check", profilePath, "--format", "json"} - logrus.Debugf("Run: inspec %v", args) - stdout, stderr, err := run(args, nil, defaultTimeout, inspecShimEnv()) + stdoutFile, erroutFile, shellFile := shellscriptAndResponse(check_command, tmpDirPath) + + args = append(args, []string{"/bin/sh", shellFile, tmpDirFile, stdoutFile, erroutFile}...) + + logrus.Infof("Run: inspec %v", args) + env := map[string]string{ + "HOME": tmpDirPath, + "TMPDIR": tmpDirPath, + "CHEF_LICENSE": "accept-no-persist", + } + _, _, err = run(args, nil, defaultTimeout, env) + + errorContent := readFile(erroutFile) + + successContent := readFile(stdoutFile) + os.RemoveAll(tmpDirPath) if err != nil { - e := fmt.Sprintf("%s\n%s", err.Error(), stderr) - return res, errors.New("Check InSpec check failed for " + profilePath + " with message: " + e) + return res, errors.New("Check InSpec check failed for " + profilePath + " with message: " + err.Error() + string(errorContent)) } - jsonContent := findJsonLine(stdout) + if checkForError(errorContent, successContent) { + return res, errors.New("InSpec check failed for " + profilePath + " with message: " + string(errorContent)) + } + + jsonContent := findJsonLine([]byte(successContent)) err = json.Unmarshal(jsonContent, &res) if err != nil { - return res, fmt.Errorf("Failed to unmarshal json:\n%s\nWith message: %s\nstdout: %s\nstderr: %s", jsonContent, err.Error(), stdout, stderr) + return res, fmt.Errorf("Failed to unmarshal json:\n%s\nWith message: %s\nstdout: %s\nstderr: %s", jsonContent, err.Error(), successContent, errorContent) } if len(res.Errors) > 0 { @@ -264,33 +320,94 @@ func Check(profilePath string) (CheckResult, error) { return res, errors.New(strings.Join(errs, "\n")) } - logrus.Debugf("Successfully checked inspec profile in %s", profilePath) + logrus.Infof("Successfully checked inspec profile in %s", profilePath) return res, nil } -func Json(profilePath string) ([]byte, error) { - args := []string{shimBinName, "json", profilePath} - logrus.Debugf("Run: inspec %v", args) - stdout, stderr, err := run(args, nil, defaultTimeout, inspecShimEnv()) - logrus.Debugf("Run: %s %s %v", stdout, stderr, err) +func Json(profilePath string, firejailprofilePath string) ([]byte, error) { + + tmpDirPath := fmt.Sprintf("/tmp/inspec-upload-%v", makeTimestamp()) + + tmpDirFile, args, err := getFirejailArgsaAndOutputFile(false, firejailprofilePath, profilePath, tmpDirPath) + if err != nil { + return nil, err + } + + stdoutFile, erroutFile, shellFile := shellscriptAndResponse(json_command, tmpDirPath) + args = append(args, []string{"/bin/sh", shellFile, tmpDirFile, stdoutFile, erroutFile}...) + //Changing the home directory to tmp directory created + env := map[string]string{ + "HOME": tmpDirPath, + "TMPDIR": tmpDirPath, + "CHEF_LICENSE": "accept-no-persist", + } + _, _, err = run(args, nil, defaultTimeout, env) + errorContent := readFile(erroutFile) + + successContent := readFile(stdoutFile) + + os.RemoveAll(tmpDirPath) if err != nil { - e := fmt.Sprintf("%s\n%s", err.Error(), stderr) + e := fmt.Sprintf("%s\n%s", err.Error(), errorContent) return nil, errors.New("Could not gather profile json for " + profilePath + " caused by: " + e) } - return stdout, nil + + if checkForError(errorContent, successContent) { + return nil, errors.New("InSpec json failed for " + profilePath + " with message: " + string(errorContent) + "/n" + string(successContent)) + } + + return []byte(successContent), nil } // Archives a directory to a TAR.GZ -func Archive(profilePath string, outputPath string) error { - args := []string{shimBinName, "archive", profilePath, "-o", outputPath, "--overwrite"} +// 1. Creates a tmp directory +// 2. Copy the uploaded file tmp directory +// 3. Create firejail command sand box env +// 4. Create script to add archive command +func Archive(profilePath string, outputPath string, firejailprofilePath string) error { + //Creating a tmp directory for intermittent profile + tmpDirPath := fmt.Sprintf("/tmp/inspec-upload-%v", makeTimestamp()) + + tmpDirProfilePath, args, err := getFirejailArgsaAndOutputFile(true, firejailprofilePath, profilePath, tmpDirPath) + if err != nil { + return err + } + + _, outputFileName := filepath.Split(outputPath) + outputFilePath := tmpDirPath + "/" + outputFileName + + stdoutFile, erroutFile, shellFile := shellscriptAndResponse(archive_command, tmpDirPath) + + //args = append(args, []string{binName, "archive", tmpDirProfilePath, "-o", outputFilePath, "--overwrite"}...) + args = append(args, []string{"/bin/sh", shellFile, tmpDirProfilePath, outputFilePath, stdoutFile, erroutFile}...) + + env := map[string]string{ + "HOME": tmpDirPath, + "TMPDIR": tmpDirPath, + "CHEF_LICENSE": "accept-no-persist", + } + logrus.Debugf("Run: inspec %v", args) - _, stderr, err := run(args, nil, defaultTimeout, inspecShimEnv()) + _, _, err = run(args, nil, defaultTimeout, env) + + errorContent := readFile(erroutFile) + successContent := readFile(stdoutFile) if err != nil { - e := fmt.Sprintf("%s\n%s", err.Error(), stderr) - return errors.New("InSpec archive failed for " + profilePath + " with message: " + e) + e := fmt.Sprintf("%s\n%s", err.Error(), errorContent) + os.RemoveAll(tmpDirPath) + return errors.New("InSpec archive failed for " + tmpDirProfilePath + " with message: " + e) + } + if checkForError(errorContent, successContent) { + os.RemoveAll(tmpDirPath) + return errors.New("InSpec archive failed for " + tmpDirProfilePath + " with message: " + string(errorContent) + "/n" + string(successContent)) } + err = fileutils.CopyFile(outputFilePath, outputPath) + if err != nil { + return errors.Wrapf(err, "Unable to copy archived file for output file %s", outputFileName) + } + os.RemoveAll(tmpDirPath) logrus.Debugf("Successfully archived %s to %s", profilePath, outputPath) return nil } @@ -381,3 +498,191 @@ func findJsonLine(in []byte) []byte { } return []byte(rawJson) } + +func getFirejailArgsaAndOutputFileForExec(firejailprofilePath string, profilePaths []string, tmpDirPath string) ([]string, error) { + + err := os.MkdirAll(tmpDirPath, 0777) + if err != nil { + return nil, errors.Wrapf(err, "Unable to make tmp directory") + } + firjailBin := os.Getenv("FIREJAIL") + firejailFlag := "--quiet" + firejailProfile := fmt.Sprintf("--profile=%s", firejailprofilePath) + + firejailArgs := []string{firjailBin, firejailProfile, firejailFlag} + + return firejailArgs, nil +} + +func prerequisiteForExec(tmpDir string, profilePath string) error { + err := os.MkdirAll(tmpDir, 0777) + if err != nil { + return errors.Wrapf(err, "Unable to make tmp directory") + } + + err = fileutils.CopyDir(profilePath, tmpDir, fileutils.Overwrite()) + if err != nil { + return errors.Wrapf(err, "Unable to copy files in tmp directory") + } + + return nil +} + +func getFirejailArgsaAndOutputFile(isArchive bool, firejailprofilePath string, profilePath string, tmpDirPath string) (string, []string, error) { + + firjailBin := os.Getenv("FIREJAIL") + firejailFlag := "--quiet" + firejailProfile := fmt.Sprintf("--profile=%s", firejailprofilePath) + + firejailArgs := []string{firjailBin, firejailProfile, firejailFlag} + + fileName := filepath.Base(profilePath) + fileCreated := path.Join(tmpDirPath, fileName) + + if isArchive { + tempDirProfile := tmpDirPath + "/" + fileName + err := prerequisiteForArchive(tempDirProfile, profilePath) + if err != nil { + logrus.Errorf("Unable to move files %v", err) + return "", nil, err + } + return tempDirProfile, firejailArgs, nil + } + + err := prerequisiteForCommands(tmpDirPath, profilePath, fileName) + if err != nil { + logrus.Errorf("Unable to move files %v", err) + return "", nil, nil + } + return fileCreated, firejailArgs, nil +} + +func prerequisiteForArchive(tmpDir string, file string) error { + err := os.MkdirAll(tmpDir, 0777) + if err != nil { + return errors.Wrapf(err, "Unable to make tmp directory") + } + + err = fileutils.CopyDir(file, tmpDir, fileutils.Overwrite()) + if err != nil { + return errors.Wrapf(err, "Unable to copy files in tmp directory") + } + return nil + +} + +func prerequisiteForCommands(tmpDir string, filepath string, fileName string) error { + + err := os.MkdirAll(tmpDir, 0777) + if err != nil { + return errors.Wrapf(err, "Unable to make tmp directory") + } + tmpDir = tmpDir + "/" + fileName + err = fileutils.CopyFile(filepath, tmpDir, fileutils.Overwrite()) + if err != nil { + return err + } + + return nil +} + +func makeTimestamp() int64 { + return time.Now().UnixNano() +} + +func shellscriptAndResponse(command string, tmpDirPath string) (string, string, string) { + + stdoutFile := tmpDirPath + "/success_json" + erroutFile := tmpDirPath + "/error_json" + shellFile := fmt.Sprintf("%s/%s_script.sh", tmpDirPath, command) + contentForShellFile := createShellFileContent(command, stdoutFile, erroutFile) + err := createFileAndAddContent(shellFile, contentForShellFile) + if err != nil { + logrus.Errorf("Unable to create shell script for path %s with error %v", shellFile, err) + } + + return stdoutFile, erroutFile, shellFile + +} + +func createShellFileContent(command string, stdout string, stderr string) string { + if command == json_command { + return fmt.Sprintf(`inspec %s $1 >$2 2>$3`, command) + } + if command == check_command { + return `inspec check $1 --format json>$2 2>$3` + } + if command == archive_command { + return `inspec archive $1 -o $2 --overwrite >$3 2>$4` + } + if command == exec_command { + return fmt.Sprintf(`inspec exec $@ > %s 2>%s`, stdout, stderr) + } + + return "" +} + +func createFileAndAddContent(fileName string, content string) error { + f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + if _, err := f.Write([]byte(content)); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + + return nil +} + +func readFile(fileName string) []byte { + dat, err := os.ReadFile(fileName) + if err != nil { + logrus.Errorf("Unable to read the contents of the file %v", err) + return nil + } + + return dat + +} + +func copyKeyFilesIntoTmpDirectory(tmpDirPath string, keyfiles []string) []string { + var outputkeys []string + for _, keyFile := range keyfiles { + key := filepath.Base(keyFile) + outputKey := fmt.Sprintf("%s/%s", tmpDirPath, key) + fileutils.CopyFile(keyFile, outputKey, fileutils.Overwrite()) + outputkeys = append(outputkeys, outputKey) + + } + + return outputkeys +} + +func checkForError(stdErr []byte, stdOut []byte) bool { + + if stdErr != nil && isErrorInOutput(stdErr, errorStringValues()) { + return true + } + + if stdOut != nil && isErrorInOutput(stdOut, errorStringValues()) { + return true + } + + return false + +} + +func isErrorInOutput(fileContent []byte, value []string) bool { + + for _, val := range value { + if strings.Contains(string(fileContent), val) { + return true + } + } + + return false + +} diff --git a/components/compliance-service/profiles/db/store.go b/components/compliance-service/profiles/db/store.go index 58bf3a91060..93afc25d004 100644 --- a/components/compliance-service/profiles/db/store.go +++ b/components/compliance-service/profiles/db/store.go @@ -26,7 +26,7 @@ type Store struct { DB *pgdb.DB } -func (s *Store) GetProfileInfo(filename string) (string, []byte, []byte, error) { +func (s *Store) GetProfileInfo(filename string, firejailProfilePath string) (string, []byte, []byte, error) { // load profile into memory tarContent, err := ioutil.ReadFile(filename) if err != nil { @@ -52,7 +52,7 @@ func (s *Store) GetProfileInfo(filename string) (string, []byte, []byte, error) // JSON file was not pregenerated or could not be read, so delegate to inspec. if len(inspecJSON) == 0 { - inspecJSON, err = inspec.Json(filename) + inspecJSON, err = inspec.Json(filename, firejailProfilePath) if err != nil { return "", nil, nil, err } @@ -66,7 +66,7 @@ func (s *Store) GetProfileInfo(filename string) (string, []byte, []byte, error) return inspecProfile.Sha256, tarContent, inspecJSON, nil } -func (s *Store) LoadMarketProfiles(path string) error { +func (s *Store) LoadMarketProfiles(path string, firejailProfilePath string) error { logrus.Infof("Verify that latest market profiles (%s) are stored in database", path) // determine all profiles in directory @@ -83,7 +83,7 @@ func (s *Store) LoadMarketProfiles(path string) error { logrus.Debugf("Upload profile %s", diskProfile) // gather information that we need to store in postgres - sha256, tar, info, err := s.GetProfileInfo(diskProfile) + sha256, tar, info, err := s.GetProfileInfo(diskProfile, firejailProfilePath) if err != nil { // log error, and ignore profile logrus.Error(err) diff --git a/components/compliance-service/profiles/market/market.go b/components/compliance-service/profiles/market/market.go index 3019e37c4e9..d570887d891 100644 --- a/components/compliance-service/profiles/market/market.go +++ b/components/compliance-service/profiles/market/market.go @@ -85,8 +85,8 @@ func TempUpload(body io.ReadCloser, suffix string) (string, error) { return tmpWithSuffix, nil } -func CheckProfile(tmpWithSuffix string) (inspec.CheckResult, error) { +func CheckProfile(tmpWithSuffix string, firejailProfilePath string) (inspec.CheckResult, error) { defer util.TimeTrack(time.Now(), "CheckProfile") - return inspec.Check(tmpWithSuffix) + return inspec.Check(tmpWithSuffix, firejailProfilePath) } diff --git a/components/compliance-service/reporting/util/zip.go b/components/compliance-service/reporting/util/zip.go index e7a9c1a7746..bfae5ea92a6 100644 --- a/components/compliance-service/reporting/util/zip.go +++ b/components/compliance-service/reporting/util/zip.go @@ -80,7 +80,7 @@ func Zip2Path(zipPath string, extractPath string) error { } // ConvertZipToTarGz extracts the profile to a tmp dir and archives the file as a tar.gz. -func ConvertZipToTarGz(zipPath string, tarPath string) error { +func ConvertZipToTarGz(zipPath string, tarPath string, firejailPath string) error { // should we make this user specific tmpPath, err := ioutil.TempDir("", "inspec-upload") if err != nil { @@ -122,7 +122,7 @@ func ConvertZipToTarGz(zipPath string, tarPath string) error { return err } - err = inspec.Archive(tmpPath, tarPath) + err = inspec.Archive(tmpPath, tarPath, firejailPath) if err != nil { return err } diff --git a/components/compliance-service/scanner/db.go b/components/compliance-service/scanner/db.go index faaf053d69e..221319b867a 100644 --- a/components/compliance-service/scanner/db.go +++ b/components/compliance-service/scanner/db.go @@ -28,13 +28,14 @@ import ( ) type Scanner struct { - managerClient manager.NodeManagerServiceClient - nodesClient nodes.NodesServiceClient - DB *pgdb.DB + managerClient manager.NodeManagerServiceClient + nodesClient nodes.NodesServiceClient + DB *pgdb.DB + FireJailExecProfilePath string } -func New(managerClient manager.NodeManagerServiceClient, nodesClient nodes.NodesServiceClient, db *pgdb.DB) *Scanner { - return &Scanner{managerClient, nodesClient, db} +func New(managerClient manager.NodeManagerServiceClient, nodesClient nodes.NodesServiceClient, db *pgdb.DB, fireJailExecProfilePath string) *Scanner { + return &Scanner{managerClient, nodesClient, db, fireJailExecProfilePath} } const sqlUpdateJobStatus = `