Skip to content

Commit

Permalink
Merge pull request #6595 from Checkmarx/kics-970
Browse files Browse the repository at this point in the history
feat(parser): ansible configuration support
  • Loading branch information
asofsilva authored Aug 11, 2023
2 parents 74dffe7 + faa85fa commit 45deb5d
Show file tree
Hide file tree
Showing 12 changed files with 443 additions and 8 deletions.
3 changes: 3 additions & 0 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ KICS supports scanning Ansible files with `.yaml` extension.

KICS can decrypt Ansible Vault files on the fly. For that, you need to define the environment variable `ANSIBLE_VAULT_PASSWORD_FILE`.

## Ansible Config
KICS supports scanning Ansible Configuration files with `.cfg` or `.conf` extension.

## Ansible Inventory
KICS supports scanning Ansible Inventory files with `.ini`, `.json` or `.yaml` extension.

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/alexmullins/zip v0.0.0-20180717182244-4affb64b04d0
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12
github.com/aws/aws-sdk-go v1.44.295
github.com/bigkevmcd/go-configparser v0.0.0-20230427073640-c6b631f70126
github.com/cheggaaa/pb/v3 v3.1.2
github.com/emicklei/proto v1.11.2
github.com/getsentry/sentry-go v0.20.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bigkevmcd/go-configparser v0.0.0-20230427073640-c6b631f70126 h1:uru++pUKoS/yYU3Ohq9VItZdK/cT7FFJH/UUjOlxc+s=
github.com/bigkevmcd/go-configparser v0.0.0-20230427073640-c6b631f70126/go.mod h1:zqqfbfnDeSdRs1WihmMjSbhb2Ptw8Jbus831xoqiIec=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
Expand Down
5 changes: 3 additions & 2 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ var (
"tfvars": true,
".proto": true,
".sh": true,
".cfg": true,
".conf": true,
".ini": true,
}
supportedRegexes = map[string][]string{
Expand Down Expand Up @@ -378,8 +380,7 @@ func (a *analyzerInfo) worker(results, unwanted chan<- string, locCount chan<- i
results <- grpc
locCount <- linesCount
}
// Ansible Inventory Files
case ".ini":
case ".cfg", ".conf", ".ini":
if a.isAvailableType(ansible) {
results <- ansible
locCount <- linesCount
Expand Down
32 changes: 28 additions & 4 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestAnalyzer_Analyze(t *testing.T) {
filepath.FromSlash("../../test/fixtures/analyzer_test/dead_symlink")},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
wantLOC: 602,
wantLOC: 793,
wantErr: false,
gitIgnoreFileName: "",
excludeGitIgnore: false,
Expand Down Expand Up @@ -219,7 +219,7 @@ func TestAnalyzer_Analyze(t *testing.T) {
filepath.FromSlash("../../test/fixtures/analyzer_test/dead_symlink")},
typesFromFlag: []string{"ansible", "pulumi"},
excludeTypesFromFlag: []string{""},
wantLOC: 300,
wantLOC: 491,
wantErr: false,
gitIgnoreFileName: "",
excludeGitIgnore: false,
Expand Down Expand Up @@ -251,7 +251,7 @@ func TestAnalyzer_Analyze(t *testing.T) {
filepath.FromSlash("../../test/fixtures/analyzer_test/dead_symlink")},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
wantLOC: 602,
wantLOC: 793,
wantErr: false,
gitIgnoreFileName: "",
excludeGitIgnore: false,
Expand All @@ -267,7 +267,7 @@ func TestAnalyzer_Analyze(t *testing.T) {
},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
wantLOC: 602,
wantLOC: 793,
wantErr: false,
gitIgnoreFileName: "",
excludeGitIgnore: false,
Expand All @@ -284,6 +284,30 @@ func TestAnalyzer_Analyze(t *testing.T) {
gitIgnoreFileName: "",
excludeGitIgnore: false,
},
{
name: "analyze_test_ansible_cfg",
paths: []string{filepath.FromSlash("../../test/fixtures/analyzer_test/ansible.cfg")},
wantTypes: []string{"ansible"},
wantExclude: []string{},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
wantLOC: 173,
wantErr: false,
gitIgnoreFileName: "",
excludeGitIgnore: false,
},
{
name: "analyze_test_ansible_conf",
paths: []string{filepath.FromSlash("../../test/fixtures/analyzer_test/ansible.conf")},
wantTypes: []string{"ansible"},
wantExclude: []string{},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
wantLOC: 18,
wantErr: false,
gitIgnoreFileName: "",
excludeGitIgnore: false,
},
}

for _, tt := range tests {
Expand Down
1 change: 1 addition & 0 deletions pkg/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
KindCOMMON FileKind = "*"
KindHELM FileKind = "HELM"
KindBUILDAH FileKind = "SH"
KindCFG FileKind = "CFG"
KindINI FileKind = "INI"
)

Expand Down
118 changes: 118 additions & 0 deletions pkg/parser/ansible/ini/config/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package ansibleconfig

import (
"regexp"
"strconv"
"strings"

"github.com/Checkmarx/kics/pkg/model"
"github.com/Checkmarx/kics/pkg/parser/ansible/ini/comments"
"github.com/bigkevmcd/go-configparser"
)

// Parser defines a parser type
type Parser struct {
}

func (p *Parser) Resolve(fileContent []byte, filename string) ([]byte, error) {
return fileContent, nil
}

// Parse parses .cfg/.conf file and returns it as a Document
func (p *Parser) Parse(filePath string, fileContent []byte) ([]model.Document, []int, error) {
model.NewIgnore.Reset()

reader := strings.NewReader(string(fileContent))
configparser.Delimiters("=")
inline := configparser.InlineCommentPrefixes([]string{";"})

config, err := configparser.ParseReaderWithOptions(reader, inline)
if err != nil {
return nil, nil, err
}

doc := make(map[string]interface{})
doc["groups"] = refactorConfig(config)

ignoreLines := comments.GetIgnoreLines(strings.Split(string(fileContent), "\n"))

return []model.Document{doc}, ignoreLines, nil
}

// refactorConfig removes all extra information and tries to convert
func refactorConfig(config *configparser.ConfigParser) (doc *model.Document) {
doc = emptyDocument()
for _, section := range config.Sections() {
dict, err := config.Items(section)
if err != nil {
continue
}
dictRefact := make(map[string]interface{})
for key, value := range dict {
if boolValue, err := strconv.ParseBool(value); err == nil {
dictRefact[key] = boolValue
} else if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
dictRefact[key] = floatValue
} else if strings.Contains(value, ",") {
re := regexp.MustCompile(`\w+`)
matches := re.FindAllString(value, -1)
if len(matches) > 0 {
dictRefact[key] = matches
} else {
dictRefact[key] = []string{}
}
} else if strings.Contains(value, ":") {
re := regexp.MustCompile(`\w+`)
matches := re.FindAllString(value, -1)
if len(matches) > 0 {
dictRefact[key] = matches
} else {
dictRefact[key] = []string{}
}
} else if value == "[]" {
dictRefact[key] = []string{}
} else {
dictRefact[key] = value
}
}
(*doc)[section] = dictRefact
}

return doc
}

// SupportedExtensions returns extensions supported by this parser, which are only ini extension
func (p *Parser) SupportedExtensions() []string {
return []string{".cfg", ".conf"}
}

// SupportedTypes returns types supported by this parser, which is ansible
func (p *Parser) SupportedTypes() map[string]bool {
return map[string]bool{
"ansible": true,
}
}

// GetKind returns CFG constant kind
func (p *Parser) GetKind() model.FileKind {
return model.KindCFG
}

// GetCommentToken return the comment token of CFG/CONF - #
func (p *Parser) GetCommentToken() string {
return "#"
}

// GetResolvedFiles returns resolved files
func (p *Parser) GetResolvedFiles() map[string]model.ResolvedFile {
return make(map[string]model.ResolvedFile)
}

// StringifyContent converts original content into string formatted version
func (p *Parser) StringifyContent(content []byte) (string, error) {
return string(content), nil
}

func emptyDocument() *model.Document {
return &model.Document{}
}
92 changes: 92 additions & 0 deletions pkg/parser/ansible/ini/config/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package ansibleconfig

import (
"encoding/json"
"testing"

"github.com/Checkmarx/kics/pkg/model"
"github.com/stretchr/testify/require"
)

// TestParser_GetKind tests the functions [GetKind()] and all the methods called by them
func TestParser_GetKind(t *testing.T) {
p := &Parser{}
require.Equal(t, model.KindCFG, p.GetKind())
}

// TestParser_SupportedExtensions tests the functions [SupportedExtensions()] and all the methods called by them
func TestParser_SupportedExtensions(t *testing.T) {
p := &Parser{}
require.Equal(t, []string{".cfg", ".conf"}, p.SupportedExtensions())
}

// TestParser_SupportedExtensions tests the functions [SupportedTypes()] and all the methods called by them
func TestParser_SupportedTypes(t *testing.T) {
p := &Parser{}
require.Equal(t, map[string]bool{
"ansible": true,
}, p.SupportedTypes())
}

// TestParser_Parse tests the functions [Parse()] and all the methods called by them
func TestParser_Parse(t *testing.T) {
type args struct {
content []byte
}
tests := []struct {
name string
p *Parser
args args
want string
wantErr bool
}{
{
name: "CFG to model.Document",
p: &Parser{},
args: args{
content: []byte(`
[defaults]
inventory = /etc/ansible/hosts
library = /usr/share/ansible/
module_utils = /usr/share/ansible/plugins/modules/
inventory_plugins = /usr/share/ansible/plugins/inventory/
roles_path = /etc/ansible/roles
stdout_callback = yaml
forks = 10
strategy = free
httpapi_plugins=~/.ansible/plugins/httpapi:/usr/share/ansible/plugins/httpapi
internal_poll_interval=0.001
inventory_plugins=~/.ansible/plugins/inventory:/usr/share/ansible/plugins/inventory
jinja2_extensions=[]
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=30m -o ControlPath=/tmp/ansible-ssh-%h-%p-%r
[callback_plugins]
profile_tasks = yes
`),
},
want: "",
wantErr: false,
},
}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Parser{}
switch i {
case 0:
got, _, err := p.Parse("", tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("Parser() error = %v, wantErr %v", err, tt.wantErr)
return
}
_, err = json.Marshal(got)
if err != nil {
t.Errorf("json.Marshal() error = %v, wantErr %v", err, tt.wantErr)
return
}
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/scan/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/Checkmarx/kics/pkg/kics"
"github.com/Checkmarx/kics/pkg/model"
"github.com/Checkmarx/kics/pkg/parser"
ansibleConfigParser "github.com/Checkmarx/kics/pkg/parser/ansible/ini/config"
ansibleHostsParser "github.com/Checkmarx/kics/pkg/parser/ansible/ini/hosts"
buildahParser "github.com/Checkmarx/kics/pkg/parser/buildah"
dockerParser "github.com/Checkmarx/kics/pkg/parser/docker"
Expand Down Expand Up @@ -227,6 +228,7 @@ func (c *Client) createService(
Add(&dockerParser.Parser{}).
Add(&protoParser.Parser{}).
Add(&buildahParser.Parser{}).
Add(&ansibleConfigParser.Parser{}).
Add(&ansibleHostsParser.Parser{}).
Build(querySource.Types, querySource.CloudProviders)
if err != nil {
Expand Down
Loading

0 comments on commit 45deb5d

Please sign in to comment.