package rulestest import ( "bytes" "fmt" "net/http" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "edge-infra.dev/pkg/lib/fog" rulesengine "edge-infra.dev/pkg/sds/emergencyaccess/rules" "edge-infra.dev/pkg/sds/emergencyaccess/rules/server" "edge-infra.dev/pkg/sds/emergencyaccess/rules/storage/file" "edge-infra.dev/test/f2" "edge-infra.dev/test/f2/x/postgres" ) func setupRulesEngineNoDB(t *testing.T, dir string) (*gin.Engine, *bytes.Buffer) { gin.SetMode(gin.TestMode) router := gin.New() buf := new(bytes.Buffer) log := fog.New(fog.To(buf)) ds, err := file.New(log, dir) require.NoError(t, err) re := rulesengine.New(ds) res, err := server.New(router, re, log) require.NoError(t, err) return res.GinEngine, buf } func TestValidateNoDB(t *testing.T) { var ( testJSONDataDir = createTestDataDir(t, testdata) reng *gin.Engine ) feat := f2.NewFeature("Validate no DB"). Setup("Setup Rules Engine No DB", func(ctx f2.Context, t *testing.T) f2.Context { reng, _ = setupRulesEngineNoDB(t, testJSONDataDir) return ctx }). Test("Test Validate Command Default Rule", validateDefaultRuleCommand(&reng)). Test("Test Validate Command Banner Rule", validateBannerRuleCommand(&reng)). Test("Test Validate Command Wrong role", validateCommandWrongRole(&reng)). Test("Test Validate Command Not Listed", validateCommandNotListed(&reng)). Test("Test Validate Command Wrong Banner", validateCommandWrongBanner(&reng)). Feature() f.Test(t, feat) } func TestValidateWithDB(t *testing.T) { var ( reng *gin.Engine buf *bytes.Buffer ) feat := f2.NewFeature("Validation with DB"). Setup("Create Rules Engine server", func(ctx f2.Context, t *testing.T) f2.Context { var db = postgres.FromContextT(ctx, t).DB() reng, buf = setupRulesEngine(t, db) return ctx }). Setup("Add some data", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, validationData) require.NoError(t, err) return ctx }). Test("Test Validate Command Default Rule", validateDefaultRuleCommand(&reng)). Test("Test Validate Command Banner Rule", validateBannerRuleCommand(&reng)). Test("Test Validate Command Wrong role", validateCommandWrongRole(&reng)). Test("Test Validate Command Not Listed", validateCommandNotListed(&reng)). Test("Test Validate Command Wrong Banner", validateCommandWrongBanner(&reng)). Test("Test Validate Command No Rule", validateCommandNoRuleDB(&reng)). Test("Test Validate Executable Default Rule", validateDefaultRuleExecutable(&reng)). Test("Test Validate Unknown Request Type", validateUnknownCommandType(&reng)). Feature() f.Test(t, feat) fmt.Println(buf) } const validationData = ` INSERT INTO ea_rules_commands (command_id, name, type) VALUES ('78587bb1-6ca2-4d2d-a223-1ee642514b97', 'ls', 'command'), ('c818a4cd-225d-4f60-9382-f96348da7af0', 'myScript', 'executable'), ('35cc70eb-689d-49d4-8bd8-fa1cb8b0928f','mv', 'command') ; INSERT INTO banners (banner_edge_id, banner_name) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', 'myBanner') ; INSERT INTO ea_rules_privileges (privilege_id, name) VALUES ('a7c379ea-6e34-4017-8e86-eb545d7856a3', 'ea-read'), ('caedabee-ea7a-4421-a608-ec04106e61da', 'ea-write') ; INSERT INTO ea_rules_default (command_id, privilege_id) VALUES ('78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3'), ('c818a4cd-225d-4f60-9382-f96348da7af0', 'a7c379ea-6e34-4017-8e86-eb545d7856a3') ; INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da') ; ` func validateDefaultRuleCommand(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, body: strings.NewReader(`{ "command": { "name": "ls", "type": "command" }, "identity":{"userid":"user@ncr.com","earoles":["ea-read"]}, "target":{"bannerID":"2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a"}}`), expectedStatus: http.StatusOK, expectedOut: `{ "valid":true }`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateBannerRuleCommand(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, body: strings.NewReader(`{ "command": { "name": "ls", "type": "command" }, "identity":{"userid":"user@ncr.com","earoles":["ea-write"]}, "target":{"bannerID":"2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a"}}`), expectedStatus: http.StatusOK, expectedOut: `{ "valid":true }`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateCommandWrongRole(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, body: strings.NewReader(`{ "command": { "name": "ls", "type": "command" }, "identity":{"userid":"user@ncr.com","earoles":["not-a-role"]}, "target":{"bannerID":"2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a"}}`), expectedStatus: http.StatusOK, expectedOut: `{ "valid":false }`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateCommandNotListed(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, // mkdir is not listed anywhere so will fail body: strings.NewReader(`{ "command": { "name": "mkdir", "type": "command" }, "identity":{"userid":"user@ncr.com","earoles":["ea-write"]}, "target":{"bannerID":"2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a"}}`), expectedStatus: http.StatusOK, expectedOut: `{ "valid":false }`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateCommandWrongBanner(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, // this banner ID doesnt exist in the ds so only default roles will be returned body: strings.NewReader(`{ "command": { "name": "mv", "type": "command" }, "identity":{"userid":"user@ncr.com","earoles":["ea-read"]}, "target":{"bannerID":"35cc70eb-689d-49d4-8bd8-fa1cb8b0928f"}}`), expectedStatus: http.StatusOK, expectedOut: `{ "valid":false }`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateCommandNoRuleDB(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, // this command exists in the DB but doesnt have a role associated to it body: strings.NewReader(`{ "command": { "name": "mv", "type": "command" }, "identity":{"userid":"user@ncr.com","earoles":["ea-read"]}, "target":{"bannerID":"2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a"}}`), expectedStatus: http.StatusOK, expectedOut: `{ "valid":false }`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateDefaultRuleExecutable(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, body: strings.NewReader(` { "command": { "name": "myScript", "type": "executable" }, "identity": { "userid": "user@ncr.com", "earoles":["ea-read"] }, "target": { "bannerID": "2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a" } } `), expectedStatus: http.StatusOK, expectedOut: ` { "valid": true } `, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } } func validateUnknownCommandType(reng **gin.Engine) func(ctx f2.Context, t *testing.T) f2.Context { return func(ctx f2.Context, t *testing.T) f2.Context { tc := testCase{ url: `/validatecommand`, method: http.MethodPost, body: strings.NewReader(` { "command": { "name": "myScript", "type": "unknownRequestType" }, "identity": { "userid": "user@ncr.com", "earoles":["ea-read"] }, "target": { "bannerID": "2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a" } } `), // Currently we expect this to fail with an Internal Server Error. // In future we may want to update this to be an BadRequest, however // as this api is currently only exposed to authservice which // validates the request type, an unknown request type here // indicates a bug that must be fixed in authservice, so 500 is // acceptable for now. expectedStatus: http.StatusInternalServerError, expectedOut: `null`, } ctx = testEndpoint(ctx, t, *reng, tc) return ctx } }