diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 218226f..d05f631 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,8 +19,8 @@ env: KIND_IMAGE: "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6" jobs: - build: - name: Build + lint: + name: Lint runs-on: ubuntu-18.04 steps: - name: Setup Go @@ -31,12 +31,32 @@ jobs: uses: actions/checkout@v2 - name: yaml-lint uses: ibiqlik/action-yamllint@v3 + unit: + name: Unit tests + runs-on: ubuntu-18.04 + steps: + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - name: Checkout code + uses: actions/checkout@v2 - name: Run unit tests run: make tests - name: Upload code coverage uses: codecov/codecov-action@v2 with: file: ./coverage.txt + e2e: + name: E2e tests + runs-on: ubuntu-18.04 + steps: + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - name: Checkout code + uses: actions/checkout@v2 - name: Setup Kubernetes cluster (KIND) uses: engineerd/setup-kind@v0.5.0 with: @@ -56,6 +76,17 @@ jobs: first_file_path: ./test.data second_file_path: integration/testdata/Expected_output.data expected_result: PASSED + release: + name: Release snapshot + runs-on: ubuntu-18.04 + needs: [e2e, unit] + steps: + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - name: Checkout code + uses: actions/checkout@v2 - name: Dry-run release snapshot uses: goreleaser/goreleaser-action@v2 with: diff --git a/cfg/rh-0.7/master.yaml b/cfg/rh-0.7/master.yaml index 8c069a5..9db5393 100644 --- a/cfg/rh-0.7/master.yaml +++ b/cfg/rh-0.7/master.yaml @@ -591,11 +591,16 @@ groups: audit_config: "grep -A1 experimental-encryption-provider-config /etc/origin/master/master-config.yaml | sed -n '2p' | awk '{ print $2 }' | xargs cat" tests: + bin_op: and test_items: - - path: "{.providers.aescbc.experimental-encryption-provider-config}" + - path: "{.resources[*].providers[*].aescbc.keys[*]}}" compare: op: has - value: "aescbc" + value: "secret" + - path: "{.resources[*].providers[*].aescbc.keys[*]}}" + compare: + op: has + value: "name" remediation: | Edit the Openshift master config file /etc/origin/master/master-config.yaml and set aescbc as the first provider in encryption provider config. See https://docs.openshift.com/container-platform/3.10/admin_guide/encrypting_data.html. diff --git a/check/test_test.go b/check/test_test.go index b918bec..8cbad72 100644 --- a/check/test_test.go +++ b/check/test_test.go @@ -427,7 +427,7 @@ func TestExecuteJSONPath(t *testing.T) { }{ { "JSONPath parse works, results don't match", - "{.Kind}", + "{.resourcesproviders.aescbc}", kubeletConfig{ Kind: "KubeletConfiguration", ApiVersion: "kubelet.config.k8s.io/v1beta1", @@ -1134,3 +1134,129 @@ func TestToNumeric(t *testing.T) { }) } } + +func TestExecuteJSONPathOnEncryptionConfig(t *testing.T) { + + type Resources struct { + Resources []string `json:"resources"` + Providers []map[string]interface{} `json:"providers"` + } + + type EncryptionConfig struct { + Kind string `json:"kind"` + ApiVersion string `json:"apiVersion"` + Resources []Resources `json:"resources"` + } + + type Key struct { + Secret string `json:"secret"` + Name string `json:"name"` + } + + type Aescbc struct { + Keys []Key `json:"keys"` + } + + type SecretBox struct { + Keys []Key `json:"keys"` + } + + type Aesgcm struct { + Keys []Key `json:"keys"` + } + + // identity disable encryption when set as the first parameter + type Identity struct {} + + cases := []struct { + name string + jsonPath string + jsonInterface EncryptionConfig + expectedResult string + expectedToFail bool + }{ + { + "JSONPath parse works, results match", + "{.resources[*].providers[*].aescbc.keys[*].secret}", + EncryptionConfig{ + Kind: "EncryptionConfig", + ApiVersion: "v1", + Resources: []Resources{{Resources: []string{"secrets"}, Providers: []map[string]interface{}{ + {"aescbc": Aescbc{Keys: []Key{Key{Secret: "secret1", Name: "name1"}}}}, + }}}}, + "secret1", + false, + }, + { + "JSONPath parse works, results match", + "{.resources[*].providers[*].aescbc.keys[*].name}", + EncryptionConfig{ + Kind: "EncryptionConfig", + ApiVersion: "v1", + Resources: []Resources{{Resources: []string{"secrets"}, Providers: []map[string]interface{}{ + {"aescbc": Aescbc{Keys: []Key{Key{Secret: "secret1", Name: "name1"}}}}, + }}}}, + "name1", + false, + }, + { + "JSONPath parse works, results don't match", + "{.resources[*].providers[*].aescbc.keys[*].secret}", + EncryptionConfig{ + Kind: "EncryptionConfig", + ApiVersion: "v1", + Resources: []Resources{{Resources: []string{"secrets"}, Providers: []map[string]interface{}{ + {"aesgcm": Aesgcm{Keys: []Key{Key{Secret: "secret1", Name: "name1"}}}}, + }}}}, + "secret1", + true, + }, + { + "JSONPath parse works, results match", + "{.resources[*].providers[*].aesgcm.keys[*].secret}", + EncryptionConfig{ + Kind: "EncryptionConfig", + ApiVersion: "v1", + Resources: []Resources{{Resources: []string{"secrets"}, Providers: []map[string]interface{}{ + {"aesgcm": Aesgcm{Keys: []Key{Key{Secret: "secret1", Name: "name1"}}}}, + }}}}, + "secret1", + false, + }, + { + "JSONPath parse works, results match", + "{.resources[*].providers[*].secretbox.keys[*].secret}", + EncryptionConfig{ + Kind: "EncryptionConfig", + ApiVersion: "v1", + Resources: []Resources{{Resources: []string{"secrets"}, Providers: []map[string]interface{}{ + {"secretbox": SecretBox{Keys: []Key{Key{Secret: "secret1", Name: "name1"}}}}, + }}}}, + "secret1", + false, + }, + { + "JSONPath parse works, results match", + "{.resources[*].providers[*].aescbc.keys[*].secret}", + EncryptionConfig{ + Kind: "EncryptionConfig", + ApiVersion: "v1", + Resources: []Resources{{Resources: []string{"secrets"}, Providers: []map[string]interface{}{ + {"aescbc": Aescbc{Keys: []Key{Key{Secret: "secret1", Name: "name1"}, Key{Secret: "secret2", Name: "name2"}}}}, + }}}}, + "secret1 secret2", + false, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result, err := executeJSONPath(c.jsonPath, c.jsonInterface) + if err != nil && !c.expectedToFail { + t.Fatalf("jsonPath:%q, expectedResult:%q got:%v", c.jsonPath, c.expectedResult, err) + } + if c.expectedResult != result && !c.expectedToFail { + t.Errorf("jsonPath:%q, expectedResult:%q got:%q", c.jsonPath, c.expectedResult, result) + } + }) + } +} diff --git a/cmd/database.go b/cmd/database.go index 0c04082..6732ede 100644 --- a/cmd/database.go +++ b/cmd/database.go @@ -11,36 +11,98 @@ import ( "gorm.io/gorm" ) -func savePgsql(jsonInfo string) { - envVars := map[string]string{ - "PGSQL_HOST": viper.GetString("PGSQL_HOST"), - "PGSQL_USER": viper.GetString("PGSQL_USER"), - "PGSQL_DBNAME": viper.GetString("PGSQL_DBNAME"), - "PGSQL_SSLMODE": viper.GetString("PGSQL_SSLMODE"), - "PGSQL_PASSWORD": viper.GetString("PGSQL_PASSWORD"), +type PsqlConnInfo struct { + Host string + User string + DbName string + SslMode string + Password string +} + +func getPsqlConnInfo() (PsqlConnInfo, error) { + var host string + if value := viper.GetString("PGSQL_HOST"); value != "" { + host = value + } else { + return PsqlConnInfo{}, fmt.Errorf("%s_PGSQL_HOST env var is required", envVarsPrefix) } - for k, v := range envVars { - if v == "" { - exitWithError(fmt.Errorf("environment variable %s is missing", envVarsPrefix+"_"+k)) - } + var user string + if value := viper.GetString("PGSQL_USER"); value != "" { + user = value + } else { + return PsqlConnInfo{}, fmt.Errorf("%s_PGSQL_USER env var is required", envVarsPrefix) } - connInfo := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s password=%s", - envVars["PGSQL_HOST"], - envVars["PGSQL_USER"], - envVars["PGSQL_DBNAME"], - envVars["PGSQL_SSLMODE"], - envVars["PGSQL_PASSWORD"], + var dbName string + if value := viper.GetString("PGSQL_DBNAME"); value != "" { + dbName = value + } else { + return PsqlConnInfo{}, fmt.Errorf("%s_PGSQL_USER env var is required", envVarsPrefix) + } + + var sslMode string + if value := viper.GetString("PGSQL_SSLMODE"); value != "" { + sslMode = value + } else { + return PsqlConnInfo{}, fmt.Errorf("%s_PGSQL_SSLMODE env var is required", envVarsPrefix) + } + + var password string + if value := viper.GetString("PGSQL_PASSWORD"); value != "" { + password = value + } else { + return PsqlConnInfo{}, fmt.Errorf("%s_PGSQL_PASSWORD env var is required", envVarsPrefix) + } + + return PsqlConnInfo{ + Host: host, + User: user, + DbName: dbName, + SslMode: sslMode, + Password: password, + }, nil +} + +func (c *PsqlConnInfo) toString() string { + return fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s password=%s", + c.Host, + c.User, + c.DbName, + c.SslMode, + c.Password, ) +} - hostname, err := os.Hostname() +func savePgsql(jsonInfo string) { + var hostname string + if value := viper.GetString("K8S_HOST"); value != "" { + // Adhere to the ScanHost column definition below + if len(value) > 63 { + exitWithError(fmt.Errorf("%s_K8S_HOST value's length must be less than 63 chars", envVarsPrefix)) + } + + hostname = value + } else { + host, err := os.Hostname() + if err != nil { + exitWithError(fmt.Errorf("received error looking up hostname: %s", err)) + } + + hostname = host + } + + PsqlConnInfo, err := getPsqlConnInfo() if err != nil { - exitWithError(fmt.Errorf("received error looking up hostname: %s", err)) + exitWithError(err) + } + + db, err := gorm.Open(postgres.Open(PsqlConnInfo.toString()), &gorm.Config{}) + if err != nil { + exitWithError(fmt.Errorf("received error connecting to database: %s", err)) } timestamp := time.Now() - type ScanResult struct { gorm.Model ScanHost string `gorm:"type:varchar(63) not null"` // https://www.ietf.org/rfc/rfc1035.txt @@ -48,12 +110,7 @@ func savePgsql(jsonInfo string) { ScanInfo string `gorm:"type:jsonb not null"` } - db, err := gorm.Open(postgres.Open(connInfo), &gorm.Config{}) - if err != nil { - exitWithError(fmt.Errorf("received error connecting to database: %s", err)) - } - db.Debug().AutoMigrate(&ScanResult{}) db.Save(&ScanResult{ScanHost: hostname, ScanTime: timestamp, ScanInfo: jsonInfo}) - glog.V(2).Info(fmt.Sprintf("successfully stored result to: %s", envVars["PGSQL_HOST"])) + glog.V(2).Info(fmt.Sprintf("successfully stored result to: %s", PsqlConnInfo.Host)) } diff --git a/go.mod b/go.mod index bb5ed24..0143242 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/aquasecurity/kube-bench go 1.16 require ( - github.com/aws/aws-sdk-go v1.41.0 + github.com/aws/aws-sdk-go v1.41.11 github.com/fatih/color v1.13.0 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/magiconair/properties v1.8.5 @@ -13,7 +13,7 @@ require ( github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v2 v2.4.0 - gorm.io/driver/postgres v1.1.2 + gorm.io/driver/postgres v1.2.0 gorm.io/gorm v1.21.16 k8s.io/client-go v0.22.2 ) diff --git a/go.sum b/go.sum index eb089ad..3a3b684 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.41.0 h1:XUzHLFWQVhmFtmKTodnAo5QdooPQfpVfilCxIV3aLoE= -github.com/aws/aws-sdk-go v1.41.0/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.41.11 h1:QLouWsiYQ8i22kD8k58Dpdhio1A0MpT7bg9ZNXqEjuI= +github.com/aws/aws-sdk-go v1.41.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -899,9 +899,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.1.2 h1:Amy3hCvLqM+/ICzjCnQr8wKFLVJTeOTdlMT7kCP+J1Q= -gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI= -gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/driver/postgres v1.2.0 h1:2k0EYyqii7sfWVM7yomw6a82Jt5wjuQUpWmD6fI9fGI= +gorm.io/driver/postgres v1.2.0/go.mod h1:c/8rVZUl30/ZyaQtAobsLRbBTubskhCrkWZDwZe1KfI= gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs= gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=