diff --git a/.gitignore b/.gitignore index cde1160..fadf50a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ dist ### Go Patch ### /vendor/ /Godeps/ +/linux/ config.toml /ciscollector # End of https://www.toptal.com/developers/gitignore/api/go diff --git a/cmd/ciscollector/main.go b/cmd/ciscollector/main.go index cbe4d58..9a57445 100644 --- a/cmd/ciscollector/main.go +++ b/cmd/ciscollector/main.go @@ -42,7 +42,7 @@ func main() { ctx := context.Background() if cnf.App.VerbosePostgres { runPostgresByControl(ctx, cnf) - + return } if cnf.App.RunMySql { runMySql(ctx, cnf) @@ -166,14 +166,7 @@ func runPostgres(ctx context.Context, cnf *config.Config) []*model.Result { fmt.Println("**********listOfResults*************\n", data) } fmt.Println("postgressecreport.html file generated") - // data = htmlreport.GenerateMarkdown(listOfResults) - // htmldata = []byte(data) - // err = os.WriteFile("postgressecreport.md", htmldata, 0600) - // if err != nil { - // log.Error().Err(err).Msg("Unable to generate postgressecreport.md file: " + err.Error()) - // fmt.Println("**********listOfResults*************\n", data) - // } - // fmt.Println("postgressecreport.md file generated") + return listOfResults } @@ -183,21 +176,29 @@ func runRDS(ctx context.Context, cnf *config.Config) { rds.Validate() listOfResults := rds.PerformAllChecks(ctx) - jsonData, err := json.MarshalIndent(listOfResults, "", " ") - if err != nil { - fmt.Println("error marshaling list of results", err) - return - } + tableData := rds.ConvertToMainTable(listOfResults) + output := strings.ReplaceAll(string(tableData), `\n`, "\n") + + fmt.Println("\n\n\nfor detailed information check the generated output file rdssecreport.json\n") + fmt.Println(output) - output := strings.ReplaceAll(string(jsonData), `\n`, "\n") + tableData = rds.ConvertToTable(listOfResults) + + // jsonData, err := json.MarshalIndent(listOfResults, "", " ") + // if err != nil { + // fmt.Println("error marshaling list of results", err) + // return + // } + + output = strings.ReplaceAll(string(tableData), `\n`, "\n") // write output data to file - err = os.WriteFile("rdssecreport.json", []byte(output), 0600) + err := os.WriteFile("rdssecreport.json", []byte(output), 0600) if err != nil { log.Error().Err(err).Msg("Unable to generate rdssecreport.json file: " + err.Error()) - fmt.Println("**********listOfResults*************\n", string(jsonData)) + fmt.Println("**********listOfResults*************\n", string(tableData)) } - fmt.Println("rdssecreport.json file generated") + fmt.Println("rdssecreport.json file generated\n\n") } func runHBAScanner(ctx context.Context, cnf *config.Config) []*model.HBAScannerResult { postgresDatabase := cnf.Postgres diff --git a/go.mod b/go.mod index 9224d7e..cd3e85e 100644 --- a/go.mod +++ b/go.mod @@ -4,28 +4,27 @@ go 1.18 require ( github.com/go-sql-driver/mysql v1.7.0 + github.com/hashicorp/go-version v1.6.0 + github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/rs/zerolog v1.29.0 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.1 + go.uber.org/mock v0.2.0 golang.org/x/net v0.8.0 ) require ( - github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/errors v0.20.2 // indirect - github.com/go-openapi/strfmt v0.21.3 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/go-openapi/errors v0.20.3 // indirect + github.com/go-openapi/strfmt v0.21.7 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - go.mongodb.org/mongo-driver v1.10.0 // indirect + go.mongodb.org/mongo-driver v1.11.3 // indirect ) require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/jedib0t/go-pretty/v6 v6.4.6 github.com/lib/pq v1.10.7 github.com/magiconair/properties v1.8.7 // indirect diff --git a/go.sum b/go.sum index 3f16e0a..33aa102 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -65,10 +65,10 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= -github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= -github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= +github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k= +github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -124,12 +124,13 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -161,7 +162,6 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= @@ -205,8 +205,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= @@ -219,14 +219,16 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg= -go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= +go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= +go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -525,7 +527,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/linux/ciscollector b/linux/ciscollector deleted file mode 100755 index a418abd..0000000 Binary files a/linux/ciscollector and /dev/null differ diff --git a/model/result.go b/model/result.go index f65f6fa..28975bd 100644 --- a/model/result.go +++ b/model/result.go @@ -1,16 +1,34 @@ package model -import "database/sql" +import ( + "database/sql" + "fmt" +) + +type CaseResult struct { + Name string + Reason string + Status string +} + +func NewCaseResult(name string) *CaseResult { + return &CaseResult{ + Name: name, + Status: "Fail", + Reason: fmt.Sprintf("no subscription found for %s", name), + } +} type Result struct { - FailReason interface{} `json:"FailReason,omitempty"` - Status string - Description string `json:"-"` - Control string - Title string - Rationale string `json:"-"` - References string `json:"-"` - Procedure string `json:"-"` + FailReason interface{} `json:"FailReason,omitempty"` + Status string + Description string `json:"-"` + Control string + Title string + Rationale string `json:"-"` + References string `json:"-"` + Procedure string `json:"-"` + CaseFailReason map[string]*CaseResult } type Config struct { store *sql.DB diff --git a/rds/2.3.1.go b/rds/2.3.1.go index 73106d0..ff898ca 100644 --- a/rds/2.3.1.go +++ b/rds/2.3.1.go @@ -13,6 +13,7 @@ import ( const Pass = "Pass" const Fail = "Fail" +const Manual = "Manual" func ExecutePerDB(ctx context.Context, args ...interface{}) error { @@ -25,7 +26,7 @@ func ExecutePerDB(ctx context.Context, args ...interface{}) error { return nil } - dbFunc, ok := args[0].(func(context.Context, string, *tablePrinter) *model.Result) + dbFunc, ok := args[0].(func(context.Context, string, *rdsInstancePrinter) *model.Result) if !ok { log.Println("first argument cant be parsed to dbStatus func") return nil @@ -36,7 +37,7 @@ func ExecutePerDB(ctx context.Context, args ...interface{}) error { log.Println("second argument cant be parsed to string") return nil } - printer, ok := args[2].(*tablePrinter) + printer, ok := args[2].(*rdsInstancePrinter) if !ok { log.Println("third argument cant be parsed to table printer") return nil @@ -69,7 +70,8 @@ func Execute231(ctx context.Context) (result *model.Result) { result = &model.Result{} } result.Control = "2.3.1" - result.Description = "Ensure that encryption is enabled for RDS instances" + result.Title = "Ensure that encryption is enabled for RDS instances" + result = fixFailReason(result) }() @@ -77,7 +79,7 @@ func Execute231(ctx context.Context) (result *model.Result) { if err != nil { return result } - printer := NewTablePrinter() + printer := NewRDSInstancePrinter() mutex := &sync.Mutex{} gp := NewGoPool(ctx) @@ -110,7 +112,7 @@ func GetEncryptionStatusOfDB2(ctx context.Context) *model.Result { return nil } -func GetEncryptionStatusOfDB(ctx context.Context, dbName string, printer *tablePrinter) *model.Result { +func GetEncryptionStatusOfDB(ctx context.Context, dbName string, printer *rdsInstancePrinter) *model.Result { result, cmdOutput, err := ExecRdsCommand(ctx, fmt.Sprintf(`aws rds describe-db-instances --db-instance-identifier "%s" --query 'DBInstances[*].StorageEncrypted'`, dbName)) if err != nil { result.Status = Fail diff --git a/rds/2.3.2.go b/rds/2.3.2.go index 5200e9a..9caf05b 100644 --- a/rds/2.3.2.go +++ b/rds/2.3.2.go @@ -18,7 +18,7 @@ func Execute232(ctx context.Context) (result *model.Result) { result = &model.Result{} } result.Control = "2.3.2" - result.Description = "Ensure that auto minor version upgrade is enabled for RDS instances" + result.Title = "Ensure that auto minor version upgrade is enabled for RDS instances" result = fixFailReason(result) }() @@ -26,7 +26,7 @@ func Execute232(ctx context.Context) (result *model.Result) { if err != nil { return result } - printer := NewTablePrinter() + printer := NewRDSInstancePrinter() mutex := &sync.Mutex{} gp := NewGoPool(ctx) @@ -51,7 +51,7 @@ func Execute232(ctx context.Context) (result *model.Result) { } -func GetAutoMinorVersionOfDB(ctx context.Context, dbName string, printer *tablePrinter) *model.Result { +func GetAutoMinorVersionOfDB(ctx context.Context, dbName string, printer *rdsInstancePrinter) *model.Result { result, cmdOutput, err := ExecRdsCommand(ctx, fmt.Sprintf(`aws rds describe-db-instances --db-instance-identifier "%s" --query 'DBInstances[*].AutoMinorVersionUpgrade'`, dbName)) if err != nil { result.Status = Fail diff --git a/rds/2.3.3.go b/rds/2.3.3.go index ee00fd6..1d848b4 100644 --- a/rds/2.3.3.go +++ b/rds/2.3.3.go @@ -18,7 +18,7 @@ func Execute233(ctx context.Context) (result *model.Result) { result = &model.Result{} } result.Control = "2.3.3" - result.Description = "Ensure that public address is not given to RDS instance" + result.Title = "Ensure that public address is not given to RDS instance" result = fixFailReason(result) }() @@ -26,7 +26,7 @@ func Execute233(ctx context.Context) (result *model.Result) { if err != nil { return result } - printer := NewTablePrinter() + printer := NewRDSInstancePrinter() mutex := &sync.Mutex{} gp := NewGoPool(ctx) @@ -51,7 +51,7 @@ func Execute233(ctx context.Context) (result *model.Result) { } -func GetPublicAccessStatusOfDB(ctx context.Context, dbName string, printer *tablePrinter) *model.Result { +func GetPublicAccessStatusOfDB(ctx context.Context, dbName string, printer *rdsInstancePrinter) *model.Result { result, cmdOutput, err := ExecRdsCommand(ctx, fmt.Sprintf(`aws rds describe-db-instances --db-instance-identifier "%s" --query 'DBInstances[*].PubliclyAccessible'`, dbName)) if err != nil { result.Status = Fail diff --git a/rds/3.5.0.go b/rds/3.5.0.go index de021f4..b765c83 100644 --- a/rds/3.5.0.go +++ b/rds/3.5.0.go @@ -20,7 +20,7 @@ func Execute350(ctx context.Context) (result *model.Result) { result = &model.Result{} } result.Control = "3.5.0" - result.Description = "Multi-AZ check" + result.Title = "Multi-AZ check" result = fixFailReason(result) }() @@ -38,7 +38,7 @@ func Execute350(ctx context.Context) (result *model.Result) { result.FailReason = fmt.Errorf("error un marshalling %s", err) return } - printer := NewTablePrinter() + printer := NewRDSInstancePrinter() for _, multiAZDB := range arrayOfDataBases { diff --git a/rds/3.8.0.go b/rds/3.8.0.go index 2ff2b98..a5bb7a9 100644 --- a/rds/3.8.0.go +++ b/rds/3.8.0.go @@ -20,7 +20,7 @@ func Execute380(ctx context.Context) (result *model.Result) { result = &model.Result{} } result.Control = "3.8.0" - result.Description = "Ensure Relational Database Service backup retention policy is set" + result.Title = "Ensure Relational Database Service backup retention policy is set" result = fixFailReason(result) }() @@ -38,7 +38,7 @@ func Execute380(ctx context.Context) (result *model.Result) { result.FailReason = fmt.Errorf("error un marshalling cmdOutput.StdOut: %s, error :%s", cmdOutput.StdOut, err) return } - printer := NewTablePrinter() + printer := NewRDSInstancePrinter() for _, multiAZDB := range backupRetentionValues { if multiAZDB.BackupRetentionPeriod < 7 { diff --git a/rds/4.2.0.go b/rds/4.2.0.go index 1707968..e477d3d 100644 --- a/rds/4.2.0.go +++ b/rds/4.2.0.go @@ -10,37 +10,93 @@ import ( "github.com/klouddb/klouddbshield/model" ) -type SnsRdsEvents struct { - EventSubscriptionsList []struct { - EventCategoriesList []string `json:"EventCategoriesList"` - Enabled bool `json:"Enabled"` - EventSubscriptionArn string `json:"EventSubscriptionArn"` - Status string `json:"Status"` - SourceType string `json:"SourceType"` - CustomerAwsID string `json:"CustomerAwsId"` - SubscriptionCreationTime string `json:"SubscriptionCreationTime"` - CustSubscriptionID string `json:"CustSubscriptionId"` - SnsTopicArn string `json:"SnsTopicArn"` - SourceIdsList []string `json:"SourceIdsList"` - } `json:"EventSubscriptionsList"` +type RdsSubscriptions struct { + EventSubscriptionsList []EventSubscription `json:"EventSubscriptionsList"` } + +type EventSubscription struct { + EventCategoriesList any `json:"EventCategoriesList"` + SourceIdsList any `json:"SourceIdsList"` + Enabled bool `json:"Enabled"` + EventSubscriptionArn string `json:"EventSubscriptionArn"` + Status string `json:"Status"` + SourceType string `json:"SourceType"` + CustomerAwsID string `json:"CustomerAwsId"` + SubscriptionCreationTime string `json:"SubscriptionCreationTime"` + CustSubscriptionID string `json:"CustSubscriptionId"` + SnsTopicArn string `json:"SnsTopicArn"` +} + type SNSSubscriptions struct { - Subscriptions []struct { - SubscriptionArn string `json:"SubscriptionArn"` - Owner string `json:"Owner"` - Protocol string `json:"Protocol"` - Endpoint string `json:"Endpoint"` - TopicArn string `json:"TopicArn"` - } `json:"Subscriptions"` + Subscriptions []SNSSubscription `json:"Subscriptions"` +} + +type SNSSubscription struct { + SubscriptionArn string `json:"SubscriptionArn"` + Owner string `json:"Owner"` + Protocol string `json:"Protocol"` + Endpoint string `json:"Endpoint"` + TopicArn string `json:"TopicArn"` } var dbMap map[string]bool var timeToRunAWSCommand time.Duration +var ( + ErrEmptySNSSubscriptions = fmt.Errorf("no sns subscriptions present") +) + func init() { dbMap = make(map[string]bool) } +var RDSSubGetter RDSSubscriptionGetter + +type RDSSubscriptionGetter interface { + GetEventSubscription(ctx context.Context) (*RdsSubscriptions, error) + GetSNSSubscriptions(ctx context.Context, subToCheck *EventSubscription) (*SNSSubscriptions, error) +} + +type SnsChecks struct { +} + +func (s *SnsChecks) GetEventSubscription(ctx context.Context) (*RdsSubscriptions, error) { + _, cmdOutput, err := ExecRdsCommand(ctx, "aws rds describe-event-subscriptions") + if err != nil { + return nil, fmt.Errorf("error executing command %s", err) + } + + var arrayOfDescribeEventSubs RdsSubscriptions + err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfDescribeEventSubs) + if err != nil { + return nil, err + } + + if len(arrayOfDescribeEventSubs.EventSubscriptionsList) == 0 { + return nil, ErrEmptySubscriptions + } + return &arrayOfDescribeEventSubs, nil +} + +func (s *SnsChecks) GetSNSSubscriptions(ctx context.Context, subToCheck *EventSubscription) (*SNSSubscriptions, error) { + + _, cmdOutput, err := ExecRdsCommand(ctx, fmt.Sprintf(`aws sns list-subscriptions-by-topic --topic-arn %s`, subToCheck.SnsTopicArn)) + if err != nil { + return nil, fmt.Errorf("error executing command %s", err) + } + + var arrayOfSNSSubscriptions SNSSubscriptions + err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfSNSSubscriptions) + if err != nil { + return nil, err + } + + if len(arrayOfSNSSubscriptions.Subscriptions) == 0 { + return nil, ErrEmptySNSSubscriptions + } + return &arrayOfSNSSubscriptions, nil +} + func GetDBMap(ctx context.Context) (*model.Result, map[string]bool, error) { if len(dbMap) > 0 { return &model.Result{Status: Pass}, dbMap, nil @@ -73,114 +129,112 @@ func GetDBMap(ctx context.Context) (*model.Result, map[string]bool, error) { } -// Execute420 +func GetName(sub *EventSubscription) string { + name := sub.SnsTopicArn + if name == "" { + name = sub.EventSubscriptionArn + } + if name == "" { + name = sub.CustomerAwsID + } + if name == "" { + name = sub.CustSubscriptionID + } + if name == "" { + name = "SNS Topic" + } + return name +} + +// Execute420 ... func Execute420(ctx context.Context) (result *model.Result) { + if result == nil { + result = &model.Result{} + } + casResultMap := make(map[string]*model.CaseResult) + result.CaseFailReason = casResultMap + + printer := NewRDSInstancePrinter() defer func() { if result == nil { result = &model.Result{} } result.Control = "4.2.0" - result.Description = "Ensure SNS topic is created for RDS events" + result.Title = "Ensure SNS topic is created for RDS events" result = fixFailReason(result) + isOneResultFail := false + for snsName, caseResult := range casResultMap { + if caseResult.Reason == Fail { + isOneResultFail = true + } + printer.AddInstance(snsName, caseResult.Status, caseResult.Reason) + } + if len(casResultMap) > 0 { + result.FailReason = printer.Print() + } + if result.Status != Fail && !isOneResultFail { + result.Status = Pass + } }() - result, dbSubMap, err := GetDBMap(ctx) - if err != nil { - return result + if RDSSubGetter == nil { + RDSSubGetter = &SnsChecks{} } - - result, cmdOutput, err := ExecRdsCommand(ctx, "aws rds describe-event-subscriptions") - if err != nil { + rdsSubscriptions, err := RDSSubGetter.GetEventSubscription(ctx) + if err != nil || rdsSubscriptions == nil || len(rdsSubscriptions.EventSubscriptionsList) == 0 { result.Status = Fail - result.FailReason = fmt.Errorf("error executing command %s", err) - return result - } - - var arrayOfSnsRdsEvents SnsRdsEvents - err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfSnsRdsEvents) - if err != nil { - result.Status = Fail - result.FailReason = fmt.Errorf("error un marshalling cmdOutput.StdOut: %s, error :%s", cmdOutput.StdOut, err) - return - } - - if len(arrayOfSnsRdsEvents.EventSubscriptionsList) == 0 { - result.Status = Fail - result.FailReason = fmt.Errorf("no event subscription list present for databases") + result.FailReason = fmt.Errorf("error getting subscriptions %s", err) return } - for _, sub := range arrayOfSnsRdsEvents.EventSubscriptionsList { - for _, dbName := range sub.SourceIdsList { - _, ok := dbSubMap[dbName] - if ok { - dbSubMap[dbName] = true - } - } - } - - // check if we got subscription for all databases or not - for dbName, isSubscribed := range dbSubMap { - if !isSubscribed { - result.Status = Fail - result.FailReason = fmt.Errorf("no subscription found for %s", dbName) - return - } - } - - for _, sub := range arrayOfSnsRdsEvents.EventSubscriptionsList { - - // this step2 is not required - // result, cmdOutput, err = ExecRdsCommand(ctx, fmt.Sprintf(`aws sns get-topic-attributes --topic-arn %s`, sub.SnsTopicArn)) - // if err != nil { - // result.Status = Fail - // result.FailReason = fmt.Errorf("error getting sns topic attributes %s", err) - // return result - // } - - // var arrayOfRecords []interface{} - // err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfRecords) - // if err != nil { - // result.Status = Fail - // result.FailReason = fmt.Errorf("error un marshalling %s", err) - // return - // } - // if len(arrayOfRecords) == 0 { - // result.Status = Fail - // result.FailReason = fmt.Errorf("the len of the databases storage encrypted to verify is not correct") - // return - // } - - result, cmdOutput, err = ExecRdsCommand(ctx, fmt.Sprintf(`aws sns list-subscriptions-by-topic --topic-arn %s`, sub.SnsTopicArn)) - if err != nil { + // we iterate all subscriptions to make sure snstopicArn is present for all. If not it is a failure + for _, sub := range rdsSubscriptions.EventSubscriptionsList { + name := GetName(&sub) + casResultMap[name] = model.NewCaseResult(name) + if sub.SnsTopicArn == "" { result.Status = Fail - result.FailReason = fmt.Errorf("error executing command %s", err) - return result + result.FailReason = fmt.Errorf("SnsTopicArn is empty") + casResultMap[name].Status = Fail + casResultMap[name].Reason = "SnsTopicArn is empty" + continue } - var arrayOfSNSSubscriptions SNSSubscriptions - err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfSNSSubscriptions) + arrayOfSNSSubscriptions, err := RDSSubGetter.GetSNSSubscriptions(ctx, &sub) if err != nil { result.Status = Fail - result.FailReason = fmt.Errorf("error un marshalling %s", err) - return + reason := fmt.Sprintf("error getting sns subscriptions %s", err) + result.FailReason = reason + casResultMap[name].Status = Fail + casResultMap[name].Reason = reason + continue } - if len(arrayOfSNSSubscriptions.Subscriptions) == 0 { + if arrayOfSNSSubscriptions == nil || len(arrayOfSNSSubscriptions.Subscriptions) == 0 { result.Status = Fail - result.FailReason = fmt.Errorf("the len of the databases storage encrypted to verify is not correct") - return + reason := "sns subscriptions empty" + result.FailReason = reason + casResultMap[name].Status = Fail + casResultMap[name].Reason = reason + continue } + hasOnePending := false for _, snsSub := range arrayOfSNSSubscriptions.Subscriptions { if snsSub.SubscriptionArn == "PendingConfirmation" { result.Status = Fail - result.FailReason = fmt.Errorf("the subscription for the arn %s is pending", sub.SnsTopicArn) - return + reason := fmt.Sprintf("subscription for the arn %s has pending confirmation for %s", name, snsSub.Endpoint) + result.FailReason = reason + casResultMap[name].Status = Fail + casResultMap[name].Reason = reason + hasOnePending = true + continue } } - + if !hasOnePending { + reason := fmt.Sprintf("subscription for the arn %s is present", name) + casResultMap[name].Status = Pass + casResultMap[name].Reason = reason + } } - result.Status = Pass return result } diff --git a/rds/4.2.0_test.go b/rds/4.2.0_test.go new file mode 100644 index 0000000..5e1e694 --- /dev/null +++ b/rds/4.2.0_test.go @@ -0,0 +1,307 @@ +package rds_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/klouddb/klouddbshield/model" + "github.com/klouddb/klouddbshield/rds" + "github.com/klouddb/klouddbshield/rds/mock" + "go.uber.org/mock/gomock" +) + +func TestExecute420(t *testing.T) { + + dbMap := make(map[string]bool) + dbMap["database1"] = true + dbMap["database2"] = true + var msg *mock.MockRDSSubscriptionGetter + ctrl := gomock.NewController(t) + db := mock.NewMockDBGetter(ctrl) + rds.DataBaseGetter = db + setSubGetter := func() { + msg = mock.NewMockRDSSubscriptionGetter(ctrl) + rds.RDSSubGetter = msg + } + + t.Run("no rds event subscriptions at all", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.RdsSubscriptions, error) { + return nil, nil + }).AnyTimes() + + msg.EXPECT().GetSNSSubscriptions(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.SNSSubscriptions, error) { + return nil, nil + }).AnyTimes() + + result := rds.Execute420(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions has sns subscriptions but snsTopicARN is empty then it is Fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.RdsSubscriptions, error) { + rdsSubscriptions := &rds.RdsSubscriptions{} + + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + rdsSubscriptions.EventSubscriptionsList = append(rdsSubscriptions.EventSubscriptionsList, subs...) + return rdsSubscriptions, nil + }).AnyTimes() + + msg.EXPECT().GetSNSSubscriptions(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, subToCheck *rds.EventSubscription) (*rds.SNSSubscriptions, error) { + snsSubscriptions := &rds.SNSSubscriptions{} + snsSubscriptions.Subscriptions = append(snsSubscriptions.Subscriptions, rds.SNSSubscription{ + SubscriptionArn: "arn:aws:sns:us-west-1:932267803712:testinst:cb178db0-12f0-4a32-881a-b560dc3891cc", + Owner: "932267803712", + Protocol: "email", + Endpoint: "testdb@gmail.com", + TopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + }) + + return snsSubscriptions, nil + }).AnyTimes() + + result := rds.Execute420(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "SnsTopicArn is empty") { + fmt.Println(result.FailReason) + t.Error("SnsTopicArn should be empty should come in error") + } + }) + + t.Run("rds event subscriptions has sns subscriptions but snsTopicARN is not empty and we have confirmation then it is a pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.RdsSubscriptions, error) { + rdsSubscriptions := &rds.RdsSubscriptions{} + + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + SnsTopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + rdsSubscriptions.EventSubscriptionsList = append(rdsSubscriptions.EventSubscriptionsList, subs...) + return rdsSubscriptions, nil + }).AnyTimes() + + msg.EXPECT().GetSNSSubscriptions(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, subToCheck *rds.EventSubscription) (*rds.SNSSubscriptions, error) { + snsSubscriptions := &rds.SNSSubscriptions{} + snsSubscriptions.Subscriptions = append(snsSubscriptions.Subscriptions, rds.SNSSubscription{ + SubscriptionArn: "arn:aws:sns:us-west-1:932267803712:testinst:cb178db0-12f0-4a32-881a-b560dc3891cc", + Owner: "932267803712", + Protocol: "email", + Endpoint: "testdb@gmail.com", + TopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + }) + + return snsSubscriptions, nil + }).AnyTimes() + + result := rds.Execute420(context.Background()) + if result.Status != rds.Pass { + t.Error(result.FailReason) + } + }) + + t.Run("rds has multiple event subscriptions has for each it returns sns subscriptions but snsTopicARN is not empty and we have confirmation once and no confirmation for others then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.RdsSubscriptions, error) { + rdsSubscriptions := &rds.RdsSubscriptions{} + + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + SnsTopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + SnsTopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + rdsSubscriptions.EventSubscriptionsList = append(rdsSubscriptions.EventSubscriptionsList, subs...) + return rdsSubscriptions, nil + }).AnyTimes() + + count := 0 + msg.EXPECT().GetSNSSubscriptions(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, subToCheck *rds.EventSubscription) (*rds.SNSSubscriptions, error) { + defer func() { + count++ + }() + snsSubscriptions := &rds.SNSSubscriptions{} + if count == 0 { + snsSubscriptions.Subscriptions = append(snsSubscriptions.Subscriptions, rds.SNSSubscription{ + SubscriptionArn: "arn:aws:sns:us-west-1:932267803712:testinst:cb178db0-12f0-4a32-881a-b560dc3891cc", + Owner: "932267803712", + Protocol: "email", + Endpoint: "testdb@gmail.com", + TopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + }) + } else { + snsSubscriptions.Subscriptions = append(snsSubscriptions.Subscriptions, rds.SNSSubscription{ + SubscriptionArn: "PendingConfirmation", + Owner: "932267803712", + Protocol: "email", + Endpoint: "testdb@gmail.com", + TopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + }) + } + + return snsSubscriptions, nil + }).AnyTimes() + + result := rds.Execute420(context.Background()) + if result.Status != rds.Fail { + t.Error(result.FailReason) + } + if !strings.Contains(result.FailReason.(string), "has pending confirmation for testdb@gmail.com") { + fmt.Println(result.FailReason) + t.Error("SnsTopicArn should show pending confirmation") + } + }) + + t.Run("rds has multiple event subscriptions has for each it returns sns subscriptions but snsTopicARN is not empty and we have confirmation once and no confirmation for others then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.RdsSubscriptions, error) { + rdsSubscriptions := &rds.RdsSubscriptions{} + + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + SnsTopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + SnsTopicArn: "arn:aws:sns:us-west-1:932267803712:testinst2", + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + rdsSubscriptions.EventSubscriptionsList = append(rdsSubscriptions.EventSubscriptionsList, subs...) + return rdsSubscriptions, nil + }).AnyTimes() + + msg.EXPECT().GetSNSSubscriptions(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, subToCheck *rds.EventSubscription) (*rds.SNSSubscriptions, error) { + + snsSubscriptions := &rds.SNSSubscriptions{} + + snsSubscriptions.Subscriptions = append(snsSubscriptions.Subscriptions, rds.SNSSubscription{ + SubscriptionArn: "arn:aws:sns:us-west-1:932267803712:testinst:cb178db0-12f0-4a32-881a-b560dc3891cc", + Owner: "932267803712", + Protocol: "email", + Endpoint: "testdb@gmail.com", + TopicArn: "arn:aws:sns:us-west-1:932267803712:testinst"}) + + return snsSubscriptions, nil + }).AnyTimes() + + result := rds.Execute420(context.Background()) + if result.Status != rds.Pass { + t.Error(result.FailReason) + } + if !strings.Contains(result.FailReason.(string), "is present") { + fmt.Println(result.FailReason) + t.Error("SnsTopicArn should show pending confirmation") + } + }) + +} diff --git a/rds/4.3.0.go b/rds/4.3.0.go index 174e9d9..fef881f 100644 --- a/rds/4.3.0.go +++ b/rds/4.3.0.go @@ -4,175 +4,380 @@ import ( "context" "encoding/json" "fmt" - "reflect" + "log" + "sort" + "strings" "github.com/klouddb/klouddbshield/model" ) +var ( + ErrEmptySubscriptions = fmt.Errorf("no event subscriptions present") +) + +const ( + DBInstanceType = "db-instance" + DBSecurityGroup = "db-security-group" + ManualCheckNeeded = "manual check needed" +) + +var SubGetter SubscriptionGetter +var DataBaseGetter DBGetter + +type DBGetter interface { + GetDBMap(ctx context.Context) (*model.Result, map[string]bool, error) +} + +type AWSDB struct { +} + +func (a *AWSDB) GetDBMap(ctx context.Context) (*model.Result, map[string]bool, error) { + return GetDBMap(ctx) +} + +type SubscriptionGetter interface { + GetEventSubscription(ctx context.Context) ([]EventSubscription, error) +} + +type AWSSubGetter struct { +} + +func (s *AWSSubGetter) GetEventSubscription(ctx context.Context) ([]EventSubscription, error) { + _, cmdOutput, err := ExecRdsCommand(ctx, "aws rds describe-event-subscriptions --query 'EventSubscriptionsList[*].{SourceType:SourceType, SourceIdsList:SourceIdsList, EventCategoriesList:EventCategoriesList}'") + if err != nil { + return nil, fmt.Errorf("error executing command %s", err) + } + + var arrayOfDescribeEventSubs []EventSubscription + err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfDescribeEventSubs) + if err != nil { + return nil, err + } + + if len(arrayOfDescribeEventSubs) == 0 { + return nil, ErrEmptySubscriptions + } + return arrayOfDescribeEventSubs, nil +} + type DescribeEventSubscription struct { SourceType string `json:"SourceType"` SourceIdsList any `json:"SourceIdsList"` EventCategoriesList any `json:"EventCategoriesList"` + Status string `json:"Status"` } -func CheckEventCategoryList(sub *DescribeEventSubscription) *model.Result { - result := &model.Result{} - // type case sourceIDList to string array first - eventCategoriesList, ok := sub.EventCategoriesList.([]interface{}) - if !ok { - result.Status = Fail - result.FailReason = fmt.Errorf("event category list can't be parsed %s", sub.EventCategoriesList) - return result +func CheckForAllSourceTypesAllEventCategoriesDBInstances(ctx context.Context, sub *EventSubscription) bool { + if sub == nil { + return false } - eventCateGoryMap := make(map[string]bool) - eventCateGoryMap["deletion"] = false - eventCateGoryMap["failure"] = false - eventCateGoryMap["failover"] = false - eventCateGoryMap["low storage"] = false - eventCateGoryMap["maintenance"] = false - eventCateGoryMap["notification"] = false - for _, eventCategory := range eventCategoriesList { + // for cases where we get output like this because of aws rds describe-event-subscriptions --query 'EventSubscriptionsList[*].{SourceType:SourceType, SourceIdsList:SourceIdsList, EventCategoriesList:EventCategoriesList}' + // we shouldn't be checking status as active + // { + // "SourceType": "db-instance", + // "SourceIdsList": null, + // "EventCategoriesList": null + // } - evtCategory, ok := eventCategory.(string) - if !ok { - result.Status = Fail - result.FailReason = fmt.Errorf("eventCategory can't be parsed %v, type of is %s", evtCategory, reflect.TypeOf(evtCategory)) - return result - } + // if sub.Status != "active" { + // return false + // } - _, ok = eventCateGoryMap[evtCategory] - if ok { - eventCateGoryMap[evtCategory] = true + sourceIDList, sourceIDlistNil := GetSourceIDList(sub) + if len(sourceIDList) > 0 { + return false + } + + eventCategoryList, eventCategoryListNil := GetEventCategoryList(sub) + if len(eventCategoryList) > 0 { + return false + } + + if sub.SourceType == "" { + if sourceIDlistNil && eventCategoryListNil { + return true + } else if !sourceIDlistNil { + return false + } else if !eventCategoryListNil { + return false } } - for k, v := range eventCateGoryMap { - if !v { - result.Status = Fail - result.FailReason = fmt.Errorf("event category subscription %s is %t", k, v) - return result + if sub.SourceType == DBInstanceType { + if sourceIDlistNil && eventCategoryListNil { + // log.Println("source type list is nil") + return true + } else if !sourceIDlistNil { + // log.Println("source type list is not nil", sourceIDlistNil) + return false + } else if !eventCategoryListNil { + // log.Println("event category list not nil", eventCategoryListNil) + return false } } - result.Status = Pass - return result + return false } -// Execute430 +// Execute430 ... func Execute430(ctx context.Context) (result *model.Result) { - defer func() { - if result == nil { - result = &model.Result{} - } - result.Control = "4.3.0" - result.Description = "Ensure RDS event subscriptions are enabled for Instance level events" - result = fixFailReason(result) - }() + if result == nil { + result = &model.Result{} + } + dbResultMap := make(map[string]*model.CaseResult) + result.CaseFailReason = dbResultMap + if DataBaseGetter == nil { + DataBaseGetter = &AWSDB{} + } - result, cmdOutput, err := ExecRdsCommand(ctx, "aws rds describe-event-subscriptions --query 'EventSubscriptionsList[*].{SourceType:SourceType, SourceIdsList:SourceIdsList, EventCategoriesList:EventCategoriesList}'") + _, dbSubMap, err := DataBaseGetter.GetDBMap(ctx) if err != nil { + // log.Println("error getting database", err) result.Status = Fail - result.FailReason = fmt.Errorf("error executing command %s", err) + result.FailReason = fmt.Errorf("error getting databases %s", err) return result } - var arrayOfDescribeEventSubs []DescribeEventSubscription - err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfDescribeEventSubs) - if err != nil { - result.Status = Fail - result.FailReason = fmt.Errorf("error un marshalling %s", err) - return + // assign fail reason to empty + for dbName, _ := range dbSubMap { + dbResultMap[dbName] = model.NewCaseResult(dbName) } - if len(arrayOfDescribeEventSubs) == 0 { + printer := NewRDSInstancePrinter() + defer func() { + result.Control = "4.3.0" + result.Title = "Ensure RDS event subscriptions are enabled for Instance level events" + result = fixFailReason(result) + + for dbName, _ := range dbSubMap { + _, ok := dbResultMap[dbName] + if !ok { + dbResultMap[dbName] = model.NewCaseResult(dbName) + } + if result.Status == Pass { + dbResultMap[dbName].Reason = fmt.Sprintf("subscription found for %s", dbName) + dbResultMap[dbName].Status = Pass + } + if result.Status == Manual { + dbResultMap[dbName].Reason = fmt.Sprintf(ManualCheckNeeded+" %s", dbName) + dbResultMap[dbName].Status = Manual + } + printer.AddInstance(dbName, dbResultMap[dbName].Status, dbResultMap[dbName].Reason) + } + result.FailReason = printer.Print() + }() + + if SubGetter == nil { + SubGetter = &AWSSubGetter{} + } + arrayOfDescribeEventSubs, err := SubGetter.GetEventSubscription(ctx) + if err != nil || arrayOfDescribeEventSubs == nil { + // log.Println("error getting database", err, arrayOfDescribeEventSubs) result.Status = Fail - result.FailReason = "no describe event subscriptions exist" + result.FailReason = fmt.Errorf("error getting subscriptions %s", err) return } + log.Println("the event subscription we got is", arrayOfDescribeEventSubs) + + // var finalSourceIDList []string + // first we make sure we have subscription for all. if we are this far means either we got sourceIDlist is nil and event category is not empty + // or sourceIDlist is not nil and we got some records. for _, sub := range arrayOfDescribeEventSubs { - if sub.SourceType == "db-instance" { - // all the instances type are covered && all event category list are covered for all instances - if sub.SourceIdsList == nil { - if sub.EventCategoriesList == nil { - result.Status = Pass - return - } else { - newSub := sub - result = CheckEventCategoryList(&newSub) - return result - } + // test case2 and test case 4 are covered here + isEnabledForAll := CheckForAllSourceTypesAllEventCategoriesDBInstances(ctx, &sub) + if isEnabledForAll { + result.Status = Pass + return + } - } + _, sourceIDListNil := GetSourceIDList(&sub) + if sourceIDListNil { + continue } + // log.Println("adding sourceID list", arrayOfSourceIDList) + // finalSourceIDList = append(finalSourceIDList, arrayOfSourceIDList...) } - result, dbSubMap, err := GetDBMap(ctx) - if err != nil { - return result + // if the number of subscriptions it is showing is greater than 1 but we know it hasn't passed before we need manual check + if len(arrayOfDescribeEventSubs) > 1 { + printer.AddInstance("Multi Subscription found", Manual, "Manual check needed") + result.Status = Manual + return } - printer := NewTablePrinter() - var results []*model.Result - for _, sub := range arrayOfDescribeEventSubs { - result = CheckDescribeEventSubscription(sub, dbSubMap, printer) - if result.Status != Pass { - // result.FailReason = fmt.Errorf("event category list is missing for database %v", sub.SourceIdsList) - // printer.AddInstance(sub.SourceIdsList, result.Status, result.FailReason.(string)) - continue - // return result + // because by here we confirmed only one remains + subToCheck := arrayOfDescribeEventSubs[0] + + if subToCheck.SourceType != DBInstanceType { + printer.AddInstance("Source type is not Instance type", Fail, subToCheck.SourceType) + result.Status = Fail + return + } + + arrayOfSourceIDList, sourceIDListNil := GetSourceIDList(&subToCheck) + + // if sourceIDlist is nil we have subscription for all + if sourceIDListNil { + for srcID, _ := range dbSubMap { + arrayOfSourceIDList = append(arrayOfSourceIDList, srcID) } - results = append(results, result) } - if len(results) != len(dbSubMap) { - result.FailReason = fmt.Errorf("no subscription found for some of the databases") + // here set for how many we have subscription. remaining set subscription is false + for _, srcID := range arrayOfSourceIDList { + dbResult := model.NewCaseResult(srcID) + dbResult.Status = Pass + dbResult.Reason = fmt.Sprintf("subscription found for %s", srcID) + dbResultMap[srcID] = dbResult + } + + if len(arrayOfSourceIDList) == len(dbSubMap) { + result.Status = Pass + } else { result.Status = Fail + } + + // if !sourceIDListNil && len(arrayOfSourceIDList) != len(dbSubMap) { + // // log.Println("source ID list not equal to submap") + // result.FailReason = fmt.Errorf("no subscription found for %d of the databases, sourceIDList: %s", len(dbSubMap)-len(arrayOfSourceIDList), arrayOfSourceIDList) + // result.Status = Fail + // return + // } + + eventCategoryList, eventCategoryListNil := GetEventCategoryList(&subToCheck) + if eventCategoryListNil { + if result.Status != Fail { + result.Status = Pass + } return } - // check if we got subscription for all databases or not - for dbName, isSubscribed := range dbSubMap { - if !isSubscribed { - result.Status = Fail - result.FailReason = fmt.Errorf("no subscription found for %s", dbName) + if len(eventCategoryList) > 0 { + eventCateGoryMap := make(map[string]bool) + eventCateGoryMap["deletion"] = false + eventCateGoryMap["failure"] = false + eventCateGoryMap["failover"] = false + eventCateGoryMap["low storage"] = false + eventCateGoryMap["maintenance"] = false + eventCateGoryMap["notification"] = false + + for _, eventCategory := range eventCategoryList { + _, ok := eventCateGoryMap[eventCategory] + if ok { + eventCateGoryMap[eventCategory] = true + } + } + + var listofEventCategoriesMissing []string + for k, v := range eventCateGoryMap { + if !v { + result.Status = Fail + listofEventCategoriesMissing = append(listofEventCategoriesMissing, k) + // printer.AddInstance(k, Fail, "only a subset of events under event category is present, others are missing for sns subscription") + // result.FailReason = fmt.Errorf("only a subset of events under event category is present, event category subscription is missing %s ", k) + // return result + } + } + + ConvertSliceToString := func(arr []string) string { + sort.Strings(arr) + return strings.Join(arr, ", ") + } + + if len(listofEventCategoriesMissing) > 0 { + commaSeperatedString := ConvertSliceToString(listofEventCategoriesMissing) + for _, srcID := range arrayOfSourceIDList { + dbResult, ok := dbResultMap[srcID] + if !ok { + dbResult = model.NewCaseResult(srcID) + } + dbResult.Status = Fail + dbResult.Reason = dbResult.Reason + ", only a subset of events under event category is present, others like " + commaSeperatedString + " are missing for sns subscriptions" + } return } + } - result.Status = Pass - return result + // this is the case where it has failed for the case where two databases exist , we have one source ID list and we got all category events. So we failed the check + // but sourceIDList for this passed so over all it is failure but for database1 it is pass + if result.Status != Fail { + result.Status = Pass + } + return } -func CheckDescribeEventSubscription(sub DescribeEventSubscription, dbSubMap map[string]bool, printer *tablePrinter) (result *model.Result) { - result = &model.Result{} - if sub.SourceType != "db-instance" { - return result +func GetSourceIDList(sub *EventSubscription) (sourceIDlist []string, sourceIDlistNil bool) { + // if sub.SourceType != DBInstanceType { + // sourceIDlistNil = true + // return + // } + // source ID list is nil that means we have subscription for all + if sub.SourceIdsList == nil { + sourceIDlistNil = true + return } + // all the instances type are covered && all event category list are covered for all instances if sub.SourceIdsList != nil { - // type case sourceIDList to string array first + var sourceIDStrs []string + // type case sourceIDList to array first sourceIDs, ok := sub.SourceIdsList.([]interface{}) if !ok { - result.Status = Fail - result.FailReason = fmt.Errorf("source ID List can't be parsed %s, type of is %s", sub.SourceIdsList, reflect.TypeOf(sub.SourceIdsList)).Error() - return + // []string array are not apparerntly []interface{} so we need to give support for []string for test cases + sourceIDStrs, ok = sub.SourceIdsList.([]string) + if !ok { + log.Println("sourceIDsList is not array", sub.SourceIdsList) + return + } } - for _, sourceID := range sourceIDs { - srcID, ok := sourceID.(string) + for _, srcID := range sourceIDs { + strSourceID, ok := srcID.(string) if !ok { - result.Status = Fail - // result.FailReason = fmt.Errorf("source ID can't be parsed %v, type of is %s", sourceID, reflect.TypeOf(sourceID)).Error() - printer.AddInstance(srcID, result.Status, fmt.Errorf("source ID can't be parsed %v, type of is %s", sourceID, reflect.TypeOf(sourceID)).Error()) - continue + log.Println("can not convert sourceID to string", srcID) + return } - dbSubMap[srcID] = true + sourceIDlist = append(sourceIDlist, strSourceID) } - } else { - result.Status = Pass + sourceIDlist = append(sourceIDlist, sourceIDStrs...) + } + return +} + +func GetEventCategoryList(sub *EventSubscription) (eventCategoryList []string, eventCategoryListNil bool) { + // if sub.SourceType != DBSecurityGroup { + // eventCategoryListNil = true + // return + // } + // source ID list is nil that means we have subscription for all + if sub.EventCategoriesList == nil { + eventCategoryListNil = true + return } - result = CheckEventCategoryList(&sub) - return result + // all the instances type are covered && all event category list are covered for all instances + if sub.EventCategoriesList != nil { + var eventIDStrs []string + eventCategories, ok := sub.EventCategoriesList.([]interface{}) + if !ok { + // []string array are not apparerntly []interface{} so we need to give support for []string for test cases + eventIDStrs, ok = sub.EventCategoriesList.([]string) + if !ok { + log.Println("eventCategoryList is not array", sub.SourceIdsList) + return + } + } + for _, eventID := range eventCategories { + strEventID, ok := eventID.(string) + if !ok { + log.Println("can not convert eventID to string", eventID) + return + } + eventCategoryList = append(eventCategoryList, strEventID) + } + eventCategoryList = append(eventCategoryList, eventIDStrs...) + } + return } diff --git a/rds/4.3.0_test.go b/rds/4.3.0_test.go new file mode 100644 index 0000000..474c13c --- /dev/null +++ b/rds/4.3.0_test.go @@ -0,0 +1,1034 @@ +package rds_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/klouddb/klouddbshield/model" + "github.com/klouddb/klouddbshield/rds" + "github.com/klouddb/klouddbshield/rds/mock" + "go.uber.org/mock/gomock" +) + +const ( + EmptySubscriptions = `┌───────────┬────────┬─────────────────────────────────────┐ + │ Instance │ Status │ Current Value │ + ├───────────┼────────┼─────────────────────────────────────┤ + │ database2 │ Fail │ no subscription found for database2 │ + ├───────────┼────────┼─────────────────────────────────────┤ + │ database1 │ Fail │ no subscription found for database1 │ + └───────────┴────────┴─────────────────────────────────────┘` + NoSubscriptionsFound = "no subscription found for database1" +) + +func TestExecute430(t *testing.T) { + + dbMap := make(map[string]bool) + dbMap["database1"] = true + dbMap["database2"] = true + var msg *mock.MockSubscriptionGetter + var db *mock.MockDBGetter + ctrl := gomock.NewController(t) + rds.DataBaseGetter = db + setSubGetter := func() { + msg = mock.NewMockSubscriptionGetter(ctrl) + db = mock.NewMockDBGetter(ctrl) + rds.SubGetter = msg + rds.DataBaseGetter = db + } + + t.Run("no rds event subscriptions at all", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + return nil, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + + if len(result.CaseFailReason) != len(dbMap) { + t.Error("not all databases got failure reasons") + } + + if result.CaseFailReason["database1"].Status != rds.Fail { + t.Error("database doesn't have correct status") + } + + if result.CaseFailReason["database1"].Reason != NoSubscriptionsFound { + t.Error("database doesn't have correct failure reason") + } + }) + + t.Run("rds event subscriptions returns one record sourceIDlist is empty and no source type is there no event category is there", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.FailReason) + } + }) + + t.Run("rds event subscriptions returns one record souceIDlist is emtpy and source type is db-instance type", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + // this case is not valid commenting it out + // t.Run("rds event subscriptions returns one record and source type is not db-instance", func(t *testing.T) { + // setSubGetter() + // db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + // return nil, dbMap, nil + // }).AnyTimes() + + // msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + // var subs []rds.EventSubscription + // subs = append(subs, rds.EventSubscription{ + // Status: "active", + // SourceType: "something", + // }) + // return subs, nil + // }).AnyTimes() + // result := rds.Execute430(context.Background()) + // if result.Status != rds.Fail { + // t.Error(result.Status) + // } + // }) + + t.Run("rds event subscriptions returns multiple records but one passes all the event source and category are enabled it should pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + }, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns multiple records but one passes which has only status active and no db instance type as well still all the event source and category are enabled it should pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + }, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns proper status and other has data with empty sourceIDlist and empty event category list we will return status as pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns empty record then it is a pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{}) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns multiple records but one no proper status and other has data with empty sourceIDlist and empty event category list we will return status as pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{}, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + "database3", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns data with empty sourceIDlist and empty event category list we will return status as pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + Status: "Active", + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns multiple records but one no proper status and other has data with empty sourceIDlist and empty event category but status active is false list we will return status as pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions returns multiple records but one has few records and other has few records we will return status as manual", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }}, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database-3", + "database-4", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Manual { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), rds.ManualCheckNeeded) { + fmt.Println(result.FailReason) + t.Error("expect manual check needed") + } + }) + + t.Run("rds event subscriptions which has event category but every thing else empty ", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + SourceIdsList: []string{ + "database1", + "database2", + }}, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Manual { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions which has event category ", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + SourceIdsList: []string{ + "database1", + "database2", + }}, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Manual { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions which has event category type but some ID list ", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + SourceIdsList: []string{ + "database1", + "database2", + }}) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + // not a valid test case + // t.Run("rds event subscriptions which has event category then it is a pass", func(t *testing.T) { + // setSubGetter() + // db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + // return nil, dbMap, nil + // }).AnyTimes() + + // msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + // var subs []rds.EventSubscription + // subs = append(subs, rds.EventSubscription{ + // Status: "active", + // SourceType: rds.DBSecurityGroup}) + // return subs, nil + // }).AnyTimes() + // result := rds.Execute430(context.Background()) + // if result.Status != rds.Fail { + // t.Error(result.Status) + // } + // }) + + t.Run("rds event subscriptions which has event category then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType}) + return subs, nil + }).AnyTimes() + result := rds.Execute440(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions which has event subscription correct for ond database then it should pass for it and others should be fail", func(t *testing.T) { + setSubGetter() + dbSubMap := make(map[string]bool) + dbSubMap["database1"] = true + dbSubMap["database2"] = true + dbSubMap["database3"] = true + dbSubMap["database4"] = true + + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbSubMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + }}) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "subscription found for database1") { + fmt.Println(result.FailReason) + t.Error("expect subscription found for database1") + } + }) + + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testunconfirmed", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testemail", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-23 04:25:33.938", + // "SourceType": "db-instance", + // "SourceIdsList": [ + // "database-1" + // ], + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testunconfirmed" + // } + + t.Run("rds event subscriptions which has event category then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType}) + return subs, nil + }).AnyTimes() + result := rds.Execute440(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions which has event category then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType}, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType}) + return subs, nil + }).AnyTimes() + result := rds.Execute440(context.Background()) + if result.Status != rds.Manual { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions which has event category list then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + EventCategoriesList: []string{ + "change", + "delete", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions has all databases and all event categories it is a pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions has one out of two databases and all event categories it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + }, + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions has two databases and one event categories is missing it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []string{ + "deletion", + "failure", + "low storage", + "maintenance", + "notification", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "only a subset of events under event category is present") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present") + } + }) + + // "EventSubscriptionsList": [ + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testsomeventsonallinstances", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testsomeventsonallinstances", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-23 23:48:30.473", + // "SourceType": "db-instance", + // "EventCategoriesList": [ + // "availability", + // "low storage" + // ], + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testsomeventsonallinstances" + // } + // ] + + t.Run("rds event subscriptions has two databases and one event categories is missing it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []interface{}{ + "availability", + "low storage", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "only a subset of events under event category is present") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present") + } + }) + + // "EventSubscriptionsList": [ + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testsns2", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testsns1", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-28 03:38:16.131", + // "SourceType": "db-security-group", + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testsns2" + // } + // ] + + t.Run("rds event subscriptions has two databases and it is of type db-security group then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBSecurityGroup, + CustomerAwsID: "320240993546", + CustSubscriptionID: "testsns2", + SnsTopicArn: "arn:aws:sns:us-west-1:320240993546:testsns1", + Enabled: true, + EventSubscriptionArn: "arn:aws:rds:us-west-1:320240993546:es:testsns2", + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + // if !strings.Contains(result.FailReason.(string), "only a subset of events under event category is present") { + // fmt.Println(result.FailReason) + // t.Error("expect only a subset of events under event category is present") + // } + }) + + // "EventSubscriptionsList": [ + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testsns42", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testsnstopic", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-24 00:45:20.95", + // "SourceType": "db-instance", + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testsns42" + // } + // ] + + t.Run("rds event subscriptions is only one with out mention of any thing then it should be pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SnsTopicArn: "arn:aws:sns:us-west-1:320240993546:testsnstopic", + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "subscription found for") { + fmt.Println(result.FailReason) + t.Error("expect subscription to be found") + } + }) + + // "EventSubscriptionsList": [ + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testeventcat", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testeventcat", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-24 14:03:51.767", + // "SourceType": "db-instance", + // "EventCategoriesList": [ + // "availability", + // "creation" + // ], + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testeventcat" + // } + // ] + + // [ + // { + // "SourceType": "db-instance", + // "SourceIdsList": null, + // "EventCategoriesList": [ + // "availability", + // "creation" + // ] + // } + // ] + + t.Run("rds event subscriptions is subset of subscriptions it should fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + EventCategoriesList: []interface{}{ + "availability", + "low storage", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "only a subset of events under event category is present") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + }) + + // { + // "SourceType": "db-instance", + // "SourceIdsList": [ + // "database-1", + // "database-2" + // ], + // "EventCategoriesList": [ + // "availability", + // "creation" + // ] + // } + + t.Run("rds event subscriptions is subset of subscriptions it should fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []interface{}{ + "availability", + "low storage", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "only a subset of events under event category is present") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + }) + + t.Run("rds event subscriptions which 4 databases but we have subscriptions for only 2 and has event category also as subset should fail", func(t *testing.T) { + setSubGetter() + dbSubMap := make(map[string]bool) + dbSubMap["database1"] = true + dbSubMap["database2"] = true + dbSubMap["database3"] = true + dbSubMap["database4"] = true + + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbSubMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []interface{}{ + "availability", + "low storage", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + if !strings.Contains(result.FailReason.(string), "subscription found for database1, only a subset of events under event category is present, others like deletion, failover, failure, maintenance, notification are missing for sns subscriptions") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + if !strings.Contains(result.FailReason.(string), "subscription found for database2, only a subset of events under event category is present, others like deletion, failover, failure, maintenance, notification are missing for sns subscriptions") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + if !strings.Contains(result.FailReason.(string), "no subscription found for database3") { + fmt.Println(result.FailReason) + t.Error("should print for which it is not having subscription") + } + if !strings.Contains(result.FailReason.(string), "no subscription found for database4") { + fmt.Println(result.FailReason) + t.Error("should print for which it is not having subscription") + } + + }) + + t.Run("rds event subscriptions which 4 databases but we have subscriptions for all and has event category also as subset should fail", func(t *testing.T) { + setSubGetter() + dbSubMap := make(map[string]bool) + dbSubMap["database1"] = true + dbSubMap["database2"] = true + dbSubMap["database3"] = true + dbSubMap["database4"] = true + + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbSubMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + EventCategoriesList: []interface{}{ + "availability", + "low storage", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + + if !strings.Contains(result.FailReason.(string), "subscription found for database1, only a subset of events under event category is present, others like deletion, failover, failure, maintenance, notification are missing for sns subscriptions") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + if !strings.Contains(result.FailReason.(string), "subscription found for database2, only a subset of events under event category is present, others like deletion, failover, failure, maintenance, notification are missing for sns subscriptions") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + if !strings.Contains(result.FailReason.(string), "subscription found for database3, only a subset of events under event category is present, others like deletion, failover, failure, maintenance, notification are missing for sns subscriptions") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + if !strings.Contains(result.FailReason.(string), "subscription found for database4, only a subset of events under event category is present, others like deletion, failover, failure, maintenance, notification are missing for sns subscriptions") { + fmt.Println(result.FailReason) + t.Error("expect only a subset of events under event category is present to be found") + } + + }) + + t.Run("rds event subscriptions which 4 databases but we have subscriptions for all and has event category for all then pass", func(t *testing.T) { + setSubGetter() + dbSubMap := make(map[string]bool) + dbSubMap["database1"] = true + dbSubMap["database2"] = true + dbSubMap["database3"] = true + dbSubMap["database4"] = true + + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbSubMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + "database3", + "database4", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions is subset of subscriptions but multiple it should manual", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []interface{}{ + "availability", + "low storage", + }, + }, rds.EventSubscription{ + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + EventCategoriesList: []interface{}{ + "mainteanance", + }, + }) + return subs, nil + }).AnyTimes() + result := rds.Execute430(context.Background()) + if result.Status != rds.Manual { + t.Error(result.Status) + } + }) + +} diff --git a/rds/4.4.0.go b/rds/4.4.0.go index 6c78a03..939a0cc 100644 --- a/rds/4.4.0.go +++ b/rds/4.4.0.go @@ -2,56 +2,121 @@ package rds import ( "context" - "encoding/json" "fmt" "github.com/klouddb/klouddbshield/model" ) +func CheckForAllSourceTypesAllEventCategoriesDBSecurityGroup(ctx context.Context, sub *EventSubscription) bool { + if sub == nil { + return false + } + // if sub.Status != "active" { + // return false + // } + + sourceIDList, sourceIDlistNil := GetSourceIDList(sub) + if len(sourceIDList) > 0 { + return false + } + + eventCategoryList, eventCategoryListNil := GetEventCategoryList(sub) + if len(eventCategoryList) > 0 { + return false + } + + if sub.SourceType == "" { + if sourceIDlistNil && eventCategoryListNil { + return true + } else if !sourceIDlistNil { + return false + } else if !eventCategoryListNil { + return false + } + } + + if sub.SourceType == DBSecurityGroup { + if sourceIDlistNil && eventCategoryListNil { + return true + } else if !sourceIDlistNil { + return false + } else if !eventCategoryListNil { + return false + } + } + + return false +} + // Execute440 func Execute440(ctx context.Context) (result *model.Result) { - result = &model.Result{} - defer func() { - if result == nil { - result = &model.Result{} - } - result.Control = "4.4.0" - result.Description = "Ensure RDS event subscriptions are enabled for DB security groups" - result = fixFailReason(result) - }() - result, cmdOutput, err := ExecRdsCommand(ctx, "aws rds describe-event-subscriptions --query 'EventSubscriptionsList[*].{SourceType:SourceType, SourceIdsList:SourceIdsList, EventCategoriesList:EventCategoriesList}'") - if err != nil { - result.Status = Fail - result.FailReason = fmt.Errorf("error executing command %s", err) - return result + if result == nil { + result = &model.Result{} + } + dbResultMap := make(map[string]*model.CaseResult) + result.CaseFailReason = dbResultMap + if DataBaseGetter == nil { + DataBaseGetter = &AWSDB{} } - var arrayOfDescribeEventSubs []DescribeEventSubscription - err = json.Unmarshal([]byte(cmdOutput.StdOut), &arrayOfDescribeEventSubs) + _, _, err := DataBaseGetter.GetDBMap(ctx) if err != nil { result.Status = Fail - result.FailReason = fmt.Errorf("error un marshalling %s", err) - return + result.FailReason = fmt.Errorf("error getting databases %s", err) + return result } - if len(arrayOfDescribeEventSubs) == 0 { + printer := NewRDSInstancePrinter() + result = &model.Result{} + defer func() { + result.Control = "4.4.0" + result.Title = "Ensure RDS event subscriptions are enabled for DB security groups" + result = fixFailReason(result) + result.FailReason = printer.Print() + if result.Status == Manual { + result.Status = Manual + } + }() + + if SubGetter == nil { + SubGetter = &AWSSubGetter{} + } + arrayOfDescribeEventSubs, err := SubGetter.GetEventSubscription(ctx) + if err != nil || arrayOfDescribeEventSubs == nil { result.Status = Fail - result.FailReason = "no describe event subscriptions exist" + result.FailReason = fmt.Errorf("error getting subscriptions %s", err) return } + var finalSourceIDList []string + // first we make sure we have subscription for all. if we are this far means either we got sourceIDlist is nil and event category is not empty + // or sourceIDlist is not nil and we got some records. for _, sub := range arrayOfDescribeEventSubs { - if sub.SourceType == "db-security-group" { - // all the instances type are covered && all event category list are covered for all instances - if sub.SourceIdsList == nil { - result.Status = Pass - return - } + // test case2 and test case 4 are covered here + isEnabledForAll := CheckForAllSourceTypesAllEventCategoriesDBSecurityGroup(ctx, &sub) + if isEnabledForAll { + result.Status = Pass + return + } + + arrayOfSourceIDList, sourceIDListNil := GetSourceIDList(&sub) + if sourceIDListNil { + continue } + finalSourceIDList = append(finalSourceIDList, arrayOfSourceIDList...) } + + // if the number of subscriptions it is showing is greater than 1 but we know it hasn't passed before we need manual check + if len(arrayOfDescribeEventSubs) > 1 { + printer.AddInstance("Multi Subscription found", Manual, "Manual check needed") + result.Status = Manual + return + } + result.Status = Fail result.FailReason = "no subscriptions for security group" + printer.AddInstance("No subscriptions found for security group", Fail, "Please add subscriptions for security group") return result } diff --git a/rds/4.4.0_test.go b/rds/4.4.0_test.go new file mode 100644 index 0000000..25c6b59 --- /dev/null +++ b/rds/4.4.0_test.go @@ -0,0 +1,212 @@ +package rds_test + +import ( + "context" + "testing" + + "github.com/klouddb/klouddbshield/model" + "github.com/klouddb/klouddbshield/rds" + "github.com/klouddb/klouddbshield/rds/mock" + "go.uber.org/mock/gomock" +) + +func TestExecute440(t *testing.T) { + + dbMap := make(map[string]bool) + dbMap["database1"] = true + dbMap["database2"] = true + var msg *mock.MockSubscriptionGetter + ctrl := gomock.NewController(t) + db := mock.NewMockDBGetter(ctrl) + rds.DataBaseGetter = db + setSubGetter := func() { + msg = mock.NewMockSubscriptionGetter(ctrl) + db = mock.NewMockDBGetter(ctrl) + rds.SubGetter = msg + rds.DataBaseGetter = db + } + + t.Run("no rds event subscriptions at all", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) (*rds.RdsSubscriptions, error) { + return nil, nil + }).AnyTimes() + + result := rds.Execute440(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + // "EventSubscriptionsList": [ + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testeventcat", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testeventcat", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-24 14:03:51.767", + // "SourceType": "db-instance", + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testeventcat" + // } + // ] + + t.Run("rds event subscriptions event subscriptions and it is doesn't have any type but and status is active it is still pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + CustomerAwsID: "320240993546", + SnsTopicArn: "arn:aws:sns:us-west-1:320240993546:testeventcat", + }) + return subs, nil + }).AnyTimes() + + result := rds.Execute440(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions event subscriptions and it is doesn't have any type but and status is something it is still pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "Something", + CustomerAwsID: "320240993546", + SnsTopicArn: "arn:aws:sns:us-west-1:320240993546:testeventcat", + }) + + return subs, nil + }).AnyTimes() + + result := rds.Execute440(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions event subscriptions it is of type db-instance then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "Something", + CustomerAwsID: "320240993546", + SnsTopicArn: "arn:aws:sns:us-west-1:320240993546:testeventcat", + SourceType: "db-instance", + }) + + return subs, nil + }).AnyTimes() + + result := rds.Execute440(context.Background()) + if result.Status != rds.Fail { + t.Error(result.Status) + } + }) + + t.Run("rds event subscriptions event subscriptions and it is of tupe security grou pthen it is a pass", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "Something", + CustomerAwsID: "320240993546", + SnsTopicArn: "arn:aws:sns:us-west-1:320240993546:testeventcat", + SourceType: "db-security-group", + }) + + return subs, nil + }).AnyTimes() + + result := rds.Execute440(context.Background()) + if result.Status != rds.Pass { + t.Error(result.Status) + } + }) + + // "EventSubscriptionsList": [ + // { + // "CustomerAwsId": "320240993546", + // "CustSubscriptionId": "testeventcat", + // "SnsTopicArn": "arn:aws:sns:us-west-1:320240993546:testeventcat", + // "Status": "active", + // "SubscriptionCreationTime": "2023-08-24 14:03:51.767", + // "SourceType": "db-instance", + // "EventCategoriesList": [ + // "availability", + // "creation" + // ], + // "Enabled": true, + // "EventSubscriptionArn": "arn:aws:rds:us-west-1:320240993546:es:testeventcat" + // } + // ] + + // [ + // { + // "SourceType": "db-instance", + // "SourceIdsList": null, + // "EventCategoriesList": [ + // "availability", + // "creation" + // ] + // } + // ] + t.Run("rds event subscriptions event subscriptions and it is of type db instance then it is a fail", func(t *testing.T) { + setSubGetter() + db.EXPECT().GetDBMap(gomock.Any()).DoAndReturn(func(ctx context.Context) (*model.Result, map[string]bool, error) { + return nil, dbMap, nil + }).AnyTimes() + + msg.EXPECT().GetEventSubscription(gomock.Any()).DoAndReturn(func(ctx context.Context) ([]rds.EventSubscription, error) { + var subs []rds.EventSubscription + subs = append(subs, rds.EventSubscription{ + Status: "active", + SourceType: rds.DBInstanceType, + SourceIdsList: []string{ + "database1", + "database2", + }, + SnsTopicArn: "arn:aws:sns:us-west-1:932267803712:testinst", + EventCategoriesList: []string{ + "deletion", + "failure", + "failover", + "low storage", + "maintenance", + "notification", + }, + }) + return subs, nil + }).AnyTimes() + + result := rds.Execute440(context.Background()) + if result.Status != rds.Fail { + t.Error(result.FailReason) + } + }) +} diff --git a/rds/check.go b/rds/check.go index 578f3b0..60ad0b9 100644 --- a/rds/check.go +++ b/rds/check.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "log" + "sort" "strings" "sync" "time" + "github.com/hashicorp/go-version" "github.com/klouddb/klouddbshield/model" ) @@ -82,7 +84,7 @@ func PerformAllChecks(ctx context.Context) []*model.Result { gp.WaitGroup().Wait() gp.ShutDown(true, time.Second) - CalculateScore(listOfResult) + // CalculateScore(listOfResult) return listOfResult } @@ -164,3 +166,50 @@ func PrintScore(score map[int]*model.Status) { ) } + +func ConvertToTable(listOfResults []*model.Result) string { + sort.Slice(listOfResults, func(i, j int) bool { + v1, err := version.NewVersion(listOfResults[i].Control) + if err != nil { + return false + } + v2, err := version.NewVersion(listOfResults[j].Control) + if err != nil { + return false + } + // Comparison example. There is also GreaterThan, Equal, and just + // a simple Compare that returns an int allowing easy >=, <=, etc. + return v1.LessThan(v2) + }) + + // sb := strings.Builder{} + // for _, result := range listOfResults { + // sb.WriteString("\n\n") + // sp := NewSectionPrinter(result) + // sb.WriteString(sp.Print()) + // sb.WriteString("\n\n") + // } + // return sb.String() + + rdsPrinter := NewRDSPrinter(listOfResults) + return rdsPrinter.Print() +} + +func ConvertToMainTable(listOfResults []*model.Result) string { + sort.Slice(listOfResults, func(i, j int) bool { + v1, err := version.NewVersion(listOfResults[i].Control) + if err != nil { + return false + } + v2, err := version.NewVersion(listOfResults[j].Control) + if err != nil { + return false + } + // Comparison example. There is also GreaterThan, Equal, and just + // a simple Compare that returns an int allowing easy >=, <=, etc. + return v1.LessThan(v2) + }) + + rdsPrinter := NewRDSPrinter(listOfResults) + return rdsPrinter.SectionPrint() +} diff --git a/rds/mock/4.2.0.go b/rds/mock/4.2.0.go new file mode 100644 index 0000000..e5352cb --- /dev/null +++ b/rds/mock/4.2.0.go @@ -0,0 +1,66 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /home/chandra/workspace/go/src/github.com/klouddb/chandraprivatetestrepo/rds/4.2.0.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + rds "github.com/klouddb/klouddbshield/rds" + gomock "go.uber.org/mock/gomock" +) + +// MockRDSSubscriptionGetter is a mock of RDSSubscriptionGetter interface. +type MockRDSSubscriptionGetter struct { + ctrl *gomock.Controller + recorder *MockRDSSubscriptionGetterMockRecorder +} + +// MockRDSSubscriptionGetterMockRecorder is the mock recorder for MockRDSSubscriptionGetter. +type MockRDSSubscriptionGetterMockRecorder struct { + mock *MockRDSSubscriptionGetter +} + +// NewMockRDSSubscriptionGetter creates a new mock instance. +func NewMockRDSSubscriptionGetter(ctrl *gomock.Controller) *MockRDSSubscriptionGetter { + mock := &MockRDSSubscriptionGetter{ctrl: ctrl} + mock.recorder = &MockRDSSubscriptionGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRDSSubscriptionGetter) EXPECT() *MockRDSSubscriptionGetterMockRecorder { + return m.recorder +} + +// GetEventSubscription mocks base method. +func (m *MockRDSSubscriptionGetter) GetEventSubscription(ctx context.Context) (*rds.RdsSubscriptions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEventSubscription", ctx) + ret0, _ := ret[0].(*rds.RdsSubscriptions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEventSubscription indicates an expected call of GetEventSubscription. +func (mr *MockRDSSubscriptionGetterMockRecorder) GetEventSubscription(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEventSubscription", reflect.TypeOf((*MockRDSSubscriptionGetter)(nil).GetEventSubscription), ctx) +} + +// GetSNSSubscriptions mocks base method. +func (m *MockRDSSubscriptionGetter) GetSNSSubscriptions(ctx context.Context, subToCheck *rds.EventSubscription) (*rds.SNSSubscriptions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSNSSubscriptions", ctx, subToCheck) + ret0, _ := ret[0].(*rds.SNSSubscriptions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSNSSubscriptions indicates an expected call of GetSNSSubscriptions. +func (mr *MockRDSSubscriptionGetterMockRecorder) GetSNSSubscriptions(ctx, subToCheck interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSNSSubscriptions", reflect.TypeOf((*MockRDSSubscriptionGetter)(nil).GetSNSSubscriptions), ctx, subToCheck) +} diff --git a/rds/mock/4.3.0.go b/rds/mock/4.3.0.go new file mode 100644 index 0000000..96d892f --- /dev/null +++ b/rds/mock/4.3.0.go @@ -0,0 +1,91 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /home/chandra/workspace/go/src/github.com/klouddb/chandraprivatetestrepo/rds/4.3.0.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + model "github.com/klouddb/klouddbshield/model" + rds "github.com/klouddb/klouddbshield/rds" + gomock "go.uber.org/mock/gomock" +) + +// MockDBGetter is a mock of DBGetter interface. +type MockDBGetter struct { + ctrl *gomock.Controller + recorder *MockDBGetterMockRecorder +} + +// MockDBGetterMockRecorder is the mock recorder for MockDBGetter. +type MockDBGetterMockRecorder struct { + mock *MockDBGetter +} + +// NewMockDBGetter creates a new mock instance. +func NewMockDBGetter(ctrl *gomock.Controller) *MockDBGetter { + mock := &MockDBGetter{ctrl: ctrl} + mock.recorder = &MockDBGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDBGetter) EXPECT() *MockDBGetterMockRecorder { + return m.recorder +} + +// GetDBMap mocks base method. +func (m *MockDBGetter) GetDBMap(ctx context.Context) (*model.Result, map[string]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDBMap", ctx) + ret0, _ := ret[0].(*model.Result) + ret1, _ := ret[1].(map[string]bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetDBMap indicates an expected call of GetDBMap. +func (mr *MockDBGetterMockRecorder) GetDBMap(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDBMap", reflect.TypeOf((*MockDBGetter)(nil).GetDBMap), ctx) +} + +// MockSubscriptionGetter is a mock of SubscriptionGetter interface. +type MockSubscriptionGetter struct { + ctrl *gomock.Controller + recorder *MockSubscriptionGetterMockRecorder +} + +// MockSubscriptionGetterMockRecorder is the mock recorder for MockSubscriptionGetter. +type MockSubscriptionGetterMockRecorder struct { + mock *MockSubscriptionGetter +} + +// NewMockSubscriptionGetter creates a new mock instance. +func NewMockSubscriptionGetter(ctrl *gomock.Controller) *MockSubscriptionGetter { + mock := &MockSubscriptionGetter{ctrl: ctrl} + mock.recorder = &MockSubscriptionGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubscriptionGetter) EXPECT() *MockSubscriptionGetterMockRecorder { + return m.recorder +} + +// GetEventSubscription mocks base method. +func (m *MockSubscriptionGetter) GetEventSubscription(ctx context.Context) ([]rds.EventSubscription, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEventSubscription", ctx) + ret0, _ := ret[0].([]rds.EventSubscription) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEventSubscription indicates an expected call of GetEventSubscription. +func (mr *MockSubscriptionGetterMockRecorder) GetEventSubscription(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEventSubscription", reflect.TypeOf((*MockSubscriptionGetter)(nil).GetEventSubscription), ctx) +} diff --git a/rds/tableprint.go b/rds/tableprint.go index 575db3d..cf207c7 100644 --- a/rds/tableprint.go +++ b/rds/tableprint.go @@ -3,35 +3,200 @@ package rds import ( "fmt" "strings" - "text/tabwriter" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/klouddb/klouddbshield/model" ) -type tablePrinter struct { - *tabwriter.Writer - lines []string +type rdsPrinter struct { + table.Writer + failuresLines []string + listOfResults []*model.Result + Sb strings.Builder +} + +func NewRDSPrinter(listOfResults []*model.Result) *rdsPrinter { + sb := strings.Builder{} + tablePrinter := &rdsPrinter{} + tablePrinter.Sb = sb + + tablePrinter.Writer = table.NewWriter() + tablePrinter.SetOutputMirror(&tablePrinter.Sb) + tablePrinter.SetStyle(table.StyleLight) + tablePrinter.listOfResults = listOfResults + return tablePrinter +} + +func (t *rdsPrinter) Print() string { + t.AppendRow(table.Row{"Ctrl", "Title", "Status"}) + t.AppendSeparator() + for _, row := range t.listOfResults { + t.AppendRow(table.Row{row.Control, row.Title, row.Status}) + t.AppendSeparator() + + if row.Status != "Pass" { + var ok bool + //nolint + var failReason string + failReasonHeader := "\n\nFailure Report\n\n" + row.Control + " " + row.Title + switch ty := row.FailReason.(type) { + case string: + failReason, ok = row.FailReason.(string) + if !ok { + failReason = "" + } + case []map[string]interface{}: + failReason = "" + for _, n := range ty { + for key, value := range n { + failReason += fmt.Sprintf("%s:%v, ", key, value) + } + failReason += "\n" + } + default: + failReason = "" + // var r = reflect.TypeOf(sp) + // failReason = fmt.Sprintf("Other:%v\n", r) + } + t.failuresLines = append(t.failuresLines, failReasonHeader+failReason) + + } + } + t.AppendSeparator() + tableOutput := "\n\n" + t.Writer.Render() + "\n" + for _, fl := range t.failuresLines { + tableOutput += fl + "\n" + } + return tableOutput +} + +func (t *rdsPrinter) SectionPrint() string { + t.AppendRow(table.Row{"Ctrl", "Title", "Status"}) + t.AppendSeparator() + for _, row := range t.listOfResults { + t.AppendRow(table.Row{row.Control, row.Title, row.Status}) + t.AppendSeparator() + } + t.AppendSeparator() + tableOutput := "\n\n" + t.Writer.Render() + "\n" + return tableOutput +} + +type rdsInstancePrinter struct { + table.Writer + lines []table.Row Sb strings.Builder } -func NewTablePrinter() *tablePrinter { +func NewRDSInstancePrinter() *rdsInstancePrinter { sb := strings.Builder{} - tablePrinter := &tablePrinter{} + tablePrinter := &rdsInstancePrinter{} tablePrinter.Sb = sb - tablePrinter.Writer = tabwriter.NewWriter(&sb, 1, 1, 1, ' ', 0) - tablePrinter.lines = append(tablePrinter.lines, "\nInstance Status Current Value") + tablePrinter.Writer = table.NewWriter() + tablePrinter.SetOutputMirror(&tablePrinter.Sb) + tablePrinter.SetStyle(table.StyleLight) + tablePrinter.lines = append(tablePrinter.lines, table.Row{"Instance", "Status", "Current Value"}) return tablePrinter } -func (t *tablePrinter) AddInstance(instance, status, value string) { - tableLine := fmt.Sprintf("%s %s %s", instance, status, value) - t.lines = append(t.lines, tableLine) +func (t *rdsInstancePrinter) AddInstance(instance, status, value string) { + t.lines = append(t.lines, table.Row{instance, status, value}) + t.AppendSeparator() +} +func (t *rdsInstancePrinter) Print() string { + for _, row := range t.lines { + t.AppendRow(row) + t.AppendSeparator() + } + // t.AppendFooter(table.Row{"", "", ""}) + t.AppendSeparator() + return "\n\n" + t.Writer.Render() + "\n" + // return t.Sb.String() } -func (t *tablePrinter) Print() string { - for _, line := range t.lines { - fmt.Fprintln(&t.Sb, line) +type sectionPrinter struct { + table.Writer + result *model.Result + lines []table.Row + Sb strings.Builder +} + +func NewSectionPrinter(result *model.Result) *sectionPrinter { + + sp := §ionPrinter{} + sp.Sb = strings.Builder{} + + sp.Writer = table.NewWriter() + sp.SetOutputMirror(&sp.Sb) + sp.SetStyle(table.StyleLight) + sp.result = result + return sp +} + +func (sp *sectionPrinter) Print() string { + + // status := fmt.Sprintf("Overall Status for %s is %s. Please check detailed status by instance below", sp.result.Control, sp.result.Status) + // description := fmt.Sprintf("Description - %s", sp.result.Title) + + // var failReason string + // var ok bool + // switch ty := sp.result.FailReason.(type) { + // case string: + // failReason, ok = sp.result.FailReason.(string) + // if !ok { + // failReason = "" + // } + // case []map[string]interface{}: + // failReason = "" + // for _, n := range ty { + // for key, value := range n { + // failReason += fmt.Sprintf("%s:%v, ", key, value) + // } + // failReason += "\n" + + // } + // default: + // failReason = "" + // // var r = reflect.TypeOf(sp) + // // failReason = fmt.Sprintf("Other:%v\n", r) + // } + // return status + "\n" + description + failReason + + sp.AppendSeparator() + sp.AppendRow(table.Row{"Title", sp.result.Title}) + + sp.AppendSeparator() + sp.AppendRow(table.Row{"Control", sp.result.Control}) + + if sp.result.Status == "Pass" { + sp.AppendSeparator() + sp.AppendRow(table.Row{"Status", sp.result.Status}) + } else { + sp.AppendSeparator() + sp.AppendRow(table.Row{"Status", sp.result.Status}) + sp.AppendSeparator() + // switch ty := sp.result.FailReason.(type) { + + // case string: + // sp.AppendRow(table.Row{"Fail Reason", sp.result.FailReason}) + // case []map[string]interface{}: + // failReason := "" + // for _, n := range ty { + // for key, value := range n { + // failReason += fmt.Sprintf("%s:%v, ", key, value) + // } + // failReason += "\n" + + // } + // sp.AppendRow(table.Row{"Fail Reason", failReason}) + // default: + // var r = reflect.TypeOf(sp) + // fmt.Printf("Other:%v\n", r) + // } + } - t.Writer.Flush() - return t.Sb.String() + sp.SetStyle(table.StyleLight) + return sp.Render() } diff --git a/rds/tableprint_test.go b/rds/tableprint_test.go deleted file mode 100644 index 85942d5..0000000 --- a/rds/tableprint_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package rds - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewTablePrinter(t *testing.T) { - tp := NewTablePrinter() - tp.AddInstance("db1", "Pass", "true") - tp.AddInstance("db2", "Fail", "false") - assert.Equal(t, tp.Print(), "Instance\tStatus\tCurrent Value\t\ndb1\tPass\ttrue\t\ndb2\tFail\tfalse\t\n") -}