cscli setup: accept stdin; fix proftpd detection test and service unmask (#2496)

This commit is contained in:
mmetc 2023-09-29 12:58:35 +02:00 committed by GitHub
parent 0d1c4c6070
commit 95ed308207
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 74 additions and 60 deletions

View file

@ -112,6 +112,20 @@ func runSetupDetect(cmd *cobra.Command, args []string) error {
return err return err
} }
var detectReader *os.File
switch detectConfigFile {
case "-":
log.Tracef("Reading detection rules from stdin")
detectReader = os.Stdin
default:
log.Tracef("Reading detection rules: %s", detectConfigFile)
detectReader, err = os.Open(detectConfigFile)
if err != nil {
return err
}
}
listSupportedServices, err := flags.GetBool("list-supported-services") listSupportedServices, err := flags.GetBool("list-supported-services")
if err != nil { if err != nil {
return err return err
@ -171,7 +185,7 @@ func runSetupDetect(cmd *cobra.Command, args []string) error {
} }
if listSupportedServices { if listSupportedServices {
supported, err := setup.ListSupported(detectConfigFile) supported, err := setup.ListSupported(detectReader)
if err != nil { if err != nil {
return err return err
} }
@ -195,7 +209,7 @@ func runSetupDetect(cmd *cobra.Command, args []string) error {
SnubSystemd: snubSystemd, SnubSystemd: snubSystemd,
} }
hubSetup, err := setup.Detect(detectConfigFile, opts) hubSetup, err := setup.Detect(detectReader, opts)
if err != nil { if err != nil {
return fmt.Errorf("detecting services: %w", err) return fmt.Errorf("detecting services: %w", err)
} }

View file

@ -3,6 +3,7 @@ package setup
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"sort" "sort"
@ -86,19 +87,19 @@ func validateDataSource(opaqueDS DataSourceItem) error {
return nil return nil
} }
func readDetectConfig(file string) (DetectConfig, error) { func readDetectConfig(fin io.Reader) (DetectConfig, error) {
var dc DetectConfig var dc DetectConfig
yamlBytes, err := os.ReadFile(file) yamlBytes, err := io.ReadAll(fin)
if err != nil { if err != nil {
return DetectConfig{}, fmt.Errorf("while reading file: %w", err) return DetectConfig{}, err
} }
dec := yaml.NewDecoder(bytes.NewBuffer(yamlBytes)) dec := yaml.NewDecoder(bytes.NewBuffer(yamlBytes))
dec.KnownFields(true) dec.KnownFields(true)
if err = dec.Decode(&dc); err != nil { if err = dec.Decode(&dc); err != nil {
return DetectConfig{}, fmt.Errorf("while parsing %s: %w", file, err) return DetectConfig{}, err
} }
switch dc.Version { switch dc.Version {
@ -107,7 +108,7 @@ func readDetectConfig(file string) (DetectConfig, error) {
case "1.0": case "1.0":
// all is well // all is well
default: default:
return DetectConfig{}, fmt.Errorf("unsupported version tag '%s' (must be 1.0)", dc.Version) return DetectConfig{}, fmt.Errorf("invalid version tag '%s' (must be 1.0)", dc.Version)
} }
for name, svc := range dc.Detect { for name, svc := range dc.Detect {
@ -457,15 +458,13 @@ type DetectOptions struct {
// Detect performs the service detection from a given configuration. // Detect performs the service detection from a given configuration.
// It outputs a setup file that can be used as input to "cscli setup install-hub" // It outputs a setup file that can be used as input to "cscli setup install-hub"
// or "cscli setup datasources". // or "cscli setup datasources".
func Detect(serviceDetectionFile string, opts DetectOptions) (Setup, error) { func Detect(detectReader io.Reader, opts DetectOptions) (Setup, error) {
ret := Setup{} ret := Setup{}
// explicitly initialize to avoid json mashaling an empty slice as "null" // explicitly initialize to avoid json mashaling an empty slice as "null"
ret.Setup = make([]ServiceSetup, 0) ret.Setup = make([]ServiceSetup, 0)
log.Tracef("Reading detection rules: %s", serviceDetectionFile) sc, err := readDetectConfig(detectReader)
sc, err := readDetectConfig(serviceDetectionFile)
if err != nil { if err != nil {
return ret, err return ret, err
} }
@ -559,8 +558,8 @@ func Detect(serviceDetectionFile string, opts DetectOptions) (Setup, error) {
} }
// ListSupported parses the configuration file and outputs a list of the supported services. // ListSupported parses the configuration file and outputs a list of the supported services.
func ListSupported(serviceDetectionFile string) ([]string, error) { func ListSupported(detectConfig io.Reader) ([]string, error) {
dc, err := readDetectConfig(serviceDetectionFile) dc, err := readDetectConfig(detectConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -10,7 +10,6 @@ import (
"github.com/lithammer/dedent" "github.com/lithammer/dedent"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/crowdsecurity/go-cs-lib/csstring"
"github.com/crowdsecurity/go-cs-lib/cstest" "github.com/crowdsecurity/go-cs-lib/cstest"
"github.com/crowdsecurity/crowdsec/pkg/setup" "github.com/crowdsecurity/crowdsec/pkg/setup"
@ -58,7 +57,7 @@ func TestSetupHelperProcess(t *testing.T) {
os.Exit(0) os.Exit(0)
} }
func tempYAML(t *testing.T, content string) string { func tempYAML(t *testing.T, content string) os.File {
t.Helper() t.Helper()
require := require.New(t) require := require.New(t)
file, err := os.CreateTemp("", "") file, err := os.CreateTemp("", "")
@ -70,7 +69,10 @@ func tempYAML(t *testing.T, content string) string {
err = file.Close() err = file.Close()
require.NoError(err) require.NoError(err)
return file.Name() file, err = os.Open(file.Name())
require.NoError(err)
return *file
} }
func TestPathExists(t *testing.T) { func TestPathExists(t *testing.T) {
@ -239,7 +241,7 @@ func TestListSupported(t *testing.T) {
"invalid yaml: bad version", "invalid yaml: bad version",
"version: 2.0", "version: 2.0",
nil, nil,
"unsupported version tag '2.0' (must be 1.0)", "invalid version tag '2.0' (must be 1.0)",
}, },
} }
@ -248,8 +250,8 @@ func TestListSupported(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
f := tempYAML(t, tc.yml) f := tempYAML(t, tc.yml)
defer os.Remove(f) defer os.Remove(f.Name())
supported, err := setup.ListSupported(f) supported, err := setup.ListSupported(&f)
cstest.RequireErrorContains(t, err, tc.expectedErr) cstest.RequireErrorContains(t, err, tc.expectedErr)
require.ElementsMatch(t, tc.expected, supported) require.ElementsMatch(t, tc.expected, supported)
}) })
@ -373,9 +375,9 @@ func TestDetectSimpleRule(t *testing.T) {
- false - false
ugly: ugly:
`) `)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{}) detected, err := setup.Detect(&f, setup.DetectOptions{})
require.NoError(err) require.NoError(err)
expected := []setup.ServiceSetup{ expected := []setup.ServiceSetup{
@ -420,9 +422,9 @@ detect:
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
f := tempYAML(t, tc.config) f := tempYAML(t, tc.config)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{}) detected, err := setup.Detect(&f, setup.DetectOptions{})
cstest.RequireErrorContains(t, err, tc.expectedErr) cstest.RequireErrorContains(t, err, tc.expectedErr)
require.Equal(tc.expected, detected) require.Equal(tc.expected, detected)
}) })
@ -514,9 +516,9 @@ detect:
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
f := tempYAML(t, tc.config) f := tempYAML(t, tc.config)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{}) detected, err := setup.Detect(&f, setup.DetectOptions{})
cstest.RequireErrorContains(t, err, tc.expectedErr) cstest.RequireErrorContains(t, err, tc.expectedErr)
require.Equal(tc.expected, detected) require.Equal(tc.expected, detected)
}) })
@ -542,9 +544,9 @@ func TestDetectForcedUnit(t *testing.T) {
journalctl_filter: journalctl_filter:
- _SYSTEMD_UNIT=crowdsec-setup-forced.service - _SYSTEMD_UNIT=crowdsec-setup-forced.service
`) `)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{ForcedUnits: []string{"crowdsec-setup-forced.service"}}) detected, err := setup.Detect(&f, setup.DetectOptions{ForcedUnits: []string{"crowdsec-setup-forced.service"}})
require.NoError(err) require.NoError(err)
expected := setup.Setup{ expected := setup.Setup{
@ -580,9 +582,9 @@ func TestDetectForcedProcess(t *testing.T) {
when: when:
- ProcessRunning("foobar") - ProcessRunning("foobar")
`) `)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}}) detected, err := setup.Detect(&f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}})
require.NoError(err) require.NoError(err)
expected := setup.Setup{ expected := setup.Setup{
@ -610,9 +612,9 @@ func TestDetectSkipService(t *testing.T) {
when: when:
- ProcessRunning("foobar") - ProcessRunning("foobar")
`) `)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}, SkipServices: []string{"wizard"}}) detected, err := setup.Detect(&f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}, SkipServices: []string{"wizard"}})
require.NoError(err) require.NoError(err)
expected := setup.Setup{[]setup.ServiceSetup{}} expected := setup.Setup{[]setup.ServiceSetup{}}
@ -826,9 +828,9 @@ func TestDetectForcedOS(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
f := tempYAML(t, tc.config) f := tempYAML(t, tc.config)
defer os.Remove(f) defer os.Remove(f.Name())
detected, err := setup.Detect(f, setup.DetectOptions{ForcedOS: tc.forced}) detected, err := setup.Detect(&f, setup.DetectOptions{ForcedOS: tc.forced})
cstest.RequireErrorContains(t, err, tc.expectedErr) cstest.RequireErrorContains(t, err, tc.expectedErr)
require.Equal(tc.expected, detected) require.Equal(tc.expected, detected)
}) })
@ -882,7 +884,7 @@ func TestDetectDatasourceValidation(t *testing.T) {
datasource: datasource:
source: file`, source: file`,
expected: setup.Setup{Setup: []setup.ServiceSetup{}}, expected: setup.Setup{Setup: []setup.ServiceSetup{}},
expectedErr: "while parsing {{.DetectYaml}}: yaml: unmarshal errors:\n line 6: field source not found in type setup.Service", expectedErr: "yaml: unmarshal errors:\n line 6: field source not found in type setup.Service",
}, { }, {
name: "source is mismatched", name: "source is mismatched",
config: ` config: `
@ -1001,18 +1003,10 @@ func TestDetectDatasourceValidation(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
detectYaml := tempYAML(t, tc.config) f := tempYAML(t, tc.config)
defer os.Remove(detectYaml) defer os.Remove(f.Name())
detected, err := setup.Detect(&f, setup.DetectOptions{})
data := map[string]string{ cstest.RequireErrorContains(t, err, tc.expectedErr)
"DetectYaml": detectYaml,
}
expectedErr, err := csstring.Interpolate(tc.expectedErr, data)
require.NoError(err)
detected, err := setup.Detect(detectYaml, setup.DetectOptions{})
cstest.RequireErrorContains(t, err, expectedErr)
require.Equal(tc.expected, detected) require.Equal(tc.expected, detected)
}) })
} }

View file

@ -14,5 +14,6 @@
- zsh-autosuggestions - zsh-autosuggestions
- zsh-syntax-highlighting - zsh-syntax-highlighting
- zsh-theme-powerlevel9k - zsh-theme-powerlevel9k
- silversearcher-ag
when: when:
- ansible_facts.os_family == "Debian" - ansible_facts.os_family == "Debian"

View file

@ -14,14 +14,14 @@ end
Vagrant.configure('2') do |config| Vagrant.configure('2') do |config|
config.vm.define 'crowdsec' config.vm.define 'crowdsec'
if ARGV.any? { |arg| arg == 'up' || arg == 'provision' } if ARGV.any? { |arg| arg == 'up' || arg == 'provision' } && !ARGV.include?('--no-provision')
unless ENV['DB_BACKEND'] unless ENV['DB_BACKEND']
$stderr.puts "\e[31mThe DB_BACKEND environment variable is not defined. Please set up the environment and try again.\e[0m" $stderr.puts "\e[31mThe DB_BACKEND environment variable is not defined. Please set up the environment and try again.\e[0m"
exit 1 exit 1
end end
end end
config.vm.provision 'shell', path: 'bootstrap' if File.exists?('bootstrap') config.vm.provision 'shell', path: 'bootstrap' if File.exist?('bootstrap')
config.vm.synced_folder '.', '/vagrant', disabled: true config.vm.synced_folder '.', '/vagrant', disabled: true
config.vm.provider :libvirt do |libvirt| config.vm.provider :libvirt do |libvirt|

View file

@ -10,4 +10,4 @@ Vagrant.configure('2') do |config|
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -21,7 +21,7 @@ Vagrant.configure('2') do |config|
end end
end end
config.vm.provision 'shell', path: 'bootstrap' if File.exists?('bootstrap') config.vm.provision 'shell', path: 'bootstrap' if File.exist?('bootstrap')
config.vm.synced_folder '.', '/vagrant', disabled: true config.vm.synced_folder '.', '/vagrant', disabled: true
config.vm.provider :libvirt do |libvirt| config.vm.provider :libvirt do |libvirt|

View file

@ -9,4 +9,4 @@ Vagrant.configure('2') do |config|
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -9,4 +9,4 @@ Vagrant.configure('2') do |config|
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -9,4 +9,4 @@ Vagrant.configure('2') do |config|
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -8,4 +8,4 @@ Vagrant.configure('2') do |config|
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -3,9 +3,9 @@
Vagrant.configure('2') do |config| Vagrant.configure('2') do |config|
config.vm.box = 'generic/ubuntu2204' config.vm.box = 'generic/ubuntu2204'
config.vm.provision "shell", inline: <<-SHELL config.vm.provision "shell", inline: <<-SHELL
sudo apt install -y aptitude kitty-terminfo sudo env DEBIAN_FRONTEND=noninteractive apt install -y aptitude kitty-terminfo
SHELL SHELL
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -8,4 +8,4 @@ Vagrant.configure('2') do |config|
end end
common = '../common' common = '../common'
load common if File.exists?(common) load common if File.exist?(common)

View file

@ -10,7 +10,8 @@ setup_file() {
teardown_file() { teardown_file() {
load "../lib/teardown_file.sh" load "../lib/teardown_file.sh"
deb-remove proftpd systemctl stop proftpd.service || :
deb-remove proftpd proftpd-core
} }
setup() { setup() {
@ -32,6 +33,7 @@ setup() {
@test "proftpd: install" { @test "proftpd: install" {
run -0 deb-install proftpd run -0 deb-install proftpd
run -0 sudo systemctl unmask proftpd.service
run -0 sudo systemctl enable proftpd.service run -0 sudo systemctl enable proftpd.service
} }

View file

@ -70,7 +70,11 @@ teardown() {
assert_line --partial "--skip-service strings ignore a service, don't recommend hub/datasources (can be repeated)" assert_line --partial "--skip-service strings ignore a service, don't recommend hub/datasources (can be repeated)"
rune -1 cscli setup detect --detect-config /path/does/not/exist rune -1 cscli setup detect --detect-config /path/does/not/exist
assert_stderr --partial "detecting services: while reading file: open /path/does/not/exist: no such file or directory" assert_stderr --partial "open /path/does/not/exist: no such file or directory"
# - is stdin
rune -1 cscli setup detect --detect-config - <<< "{}"
assert_stderr --partial "detecting services: missing version tag (must be 1.0)"
# rm -f "${HUB_DIR}/detect.yaml" # rm -f "${HUB_DIR}/detect.yaml"
} }
@ -144,7 +148,7 @@ teardown() {
EOT EOT
rune -1 cscli setup detect --list-supported-services --detect-config "$tempfile" rune -1 cscli setup detect --list-supported-services --detect-config "$tempfile"
assert_stderr --partial "while parsing ${tempfile}: yaml: unmarshal errors:" assert_stderr --partial "yaml: unmarshal errors:"
rm -f "$tempfile" rm -f "$tempfile"
} }